DEBUG 模式运行
log4j
的配置文件中配置log4j.debug=true
即可开启
概述
log4j
的looger
是层级结构的,例如com.li
是com.li.springboot
的父类logger
可使用如下方式取出logger
1 | package com.li.springboot.advice.log4j; |
getLogger
根据参数name
,在任意处取出的logger
都是同一个 root logger
是所有logger
的父类,一定存在但是它不能直接使用getLogger
通过name
取出。可使用如下方式取出
1 | Logger.getRootLogger() |
可使用的日志级别org.apache.log4j.Level
TRACE
,DEBUG
,INFO
,WARN
,ERROR
andFATAL
当指定name
的logger
日志请求时,同时会将该请求转发至父类logger
当logger
没有对应的配置时,会找最近的父类配置,默认情况下logger
配置会继承父类的配置,可通过设置log4j.additivity.xxx=false
使其不继承(xxx 是 logger 的 name)
配置
初始化 Logger 容器 Hierarchy,设置根节点为 RootLogger
初始 LoggerRepositorySelector(容器选择器)为默认的 DefaultRepositorySelector,容器为 Hierarchy
读取系统属性 log4j.defaultInitOverride,如果没有设置或者为 false 进行初始化,否则跳过初始化
读取系统属性 log4j.configuration(log4j 文件路径配置),如果存在对应的文件,则得到 URL.如果没有对应的文件,首先检查是否存在 log4j.xml 文件,如果存在,得到 Log4j 配置文件 URL,如果不存在 log4j.xml,继续检查是否存在 log4j.properties 文件,如果存在该文件,得到 log4j 配置文件的 URL,否则提示没有发现配置文件。
读取系统属性 log4j.configuratorClass(自定义 Configurator 配置类全路径,一般不自定义)
调用 OptionConverter.selectAndConfigure(url, configuratorClassName,LogManager.getLoggerRepository()),初始化 logger 容器
扩展配置
可使用BasicConfigurator.resetConfiguration()
重置配置 可使用PropertyConfigurator.configure
指定其他配置文件
tomcat
下的log4j
当log4j
的jar
包在tomcat
目录下的时候,使用BasicConfigurator.resetConfiguration()
重置配置时,会修改tomcat
下所有应用的日志打印,一般情况下 我们在主应用里做配置,忽略其他应用的配置即可。但是当你发布其他应用时,触发log4j
的初始化配置,则会影响到主应用,可能造成主应用日志不打印。这个时候我们通过HierarchyEventListener
来监听log4j
的配置是否被修改,来在其他应用重置配置时,重新触发主应用的配置加载过程即可。
1 | static { |
MDC
打造日志链路,MDC
类似ThreadLocal
类,根据线程存入一些数据,以供打印日志的时候输出(%X{name}
)
1 | MDC.clear(); |
1 | %X{session} %m%n = |
问题
日志输出问题
应用中需要将多个logger
的日志输出到同一个文件中,且需要根据时间每天自动分割文件。我们使用DailyRollingFileAppender
配置如下
1 | # 开启log4j的日志 |
测试代码我使用Spring
的Scheduled
1 | package com.li.springboot.util; |
*/1 * * * * *
表示每秒执行一次
log4j.appender.fuck1.DatePattern='.'-yyyy-MM-dd-HH-mm
表示每分钟分割一次文件
在执行定时任务到底切割点时,我们可以观察到日志输出
log4j
自身的日志一定输出在System.out
中
1 | 2019-09-05 21:16:59 DEBUG l2:22 - --------------------44 |
我们观察下源码分析下这个过程
1 | protected void subAppend(LoggingEvent event) { |
logger
的日志在logger2
之前,因此先触发rollOver
,此时没有文件log4j.log.-2019-09-05-21-16
,将log4j.log
重命名为log4j.log.-2019-09-05-21-16
,并将logger
的日志流重定向为log4j.log
紧接着
logger2
的日志流触发rollOver
,此时会将log4j.log.-2019-09-05-21-16
删除,同时将log4j.log
重命名为log4j.log.-2019-09-05-21-16
,并将logger2
的日志流重定向为log4j.log
。此时logger
的日志流就的文件名被改名了。我们可以看出第一轮的日志被
logger2
触发的rollOver
删除了,而logger
的日志流输出到上一轮
解决方案
根据分析,我们确保target.delete()
和ile.renameTo(target)
只被执行一次,且其他logger
在指定时间重新将日志流指向到最新的log4j.log
即可。
比如说简单的重写DailyRollingFileAppender
,在rollOver
代码处稍作修改
1 | File target = new File(scheduledFilename); |