读前问题:
1:log4j的日志流程是怎么样的?
2:log4j的配置文件各项都是什么含义?特别是有些配置里面有包名到底有什么用?
3:log4j的输出都是什么含义?具体见log4j conversion pattern各格式含义
一。log4j全局说明
log4j采用logger,appender模式,其中logger含有层级信息,顶级logger为rootLogger,其他logger在在解析时会以.(英文点号)为分隔符
划分层级,在获取logger时名字决定了要获取的logger实例
二。log4j配置文件解析
解析流程:
代码语言:javascript复制解析全局配置 => 解析log4j.rootLogger => 解析log4j.appender.* =>解析logFactory => 解析log4j.logger.* => 解析renderers
2.1:解析全局配置
全局配置包含:
代码语言:javascript复制log4j.debug=true : Defining this value makes log4j print log4j-internal debug statements to System.out.
log4j.reset=true : If property set to true, then hierarchy will be reset before configuration
log4j.threshold=INFO #全局级别设定,不超过这个级别的一律不输出
2.2:解析log4j.rootLogger
格式:log4j.rootLogger=priority,appenderName1,appenderName2
说明:priority是日志级别,包含由低到高DEBUG,INFO,WARN,ERROR,FATAL五个级别
appenderName是自己定义的名称,只要与后面的一致就可以了,标示了在这个logger下面的输出器
ps:根logger必须有,是所有其余logger的顶级parent,他自身parent为null
rootLogger的名称为root,默认的
2.3:解析log4j.appender.* 格式:
代码语言:javascript复制log4j.appender.appenderName=appenderClass
log4j.appender.appenderName.threshold=priority
log4j.appender.appenderName.layout=layoutClass
log4j.appender.appenderName.layout.ConversionPattern=conversionPattern
log4j.appender.appenderName.filter.filterName=filterClass
log4j.appender.appenderName.errorhandler=errorhandlerClass
log4j.appender.appenderName.errorhandler.root-ref=[true|false]
log4j.appender.appenderName.errorhandler.logger-ref=loggerName
说明:上面只是列出了一部分,像filter跟errorhandler平常我们基本不会用到,其实只要记住log4j.appender.appenderName后面的配置都是appender的属性就可以了
譬如自己继承了AppenderSkeleton这个类,里面有一个attr的属性 可以用log4j.appender.appenderName.attr=anyValue
appender自身也有级别过滤日志是否输出,可以用log4j.appender.appenderName.threshold=ERROR 这样就设定这个appender只输出ERROR级别的日志
2.4:解析logFactory
格式:log4j.loggerFactory=loggerFactoryClass
就是讲log4j默认的日志工厂替换为自定义的,基本没有必要这么做
2.5:解析log4j.logger.*
格式:log4j.logger.loggerName=priority,appenderName1,appenderName2
说明:这个logger的解析流程与rootLogger的基本一致,不同点在于logger是有层级的
假设我们有这几个配置:log4j.logger.com.oschina.net=DEBUG,appenderName
log4j.logger.com.oschina.net.web.action=INFO,appenderName
log4j.logger.com=ERROR,appenderName
log4j解析规则为:
解析loggerName = log4j.logger.后面的内容,譬如loggerName为com.oschina.net.web.action
解析上下级关系:com.oschina.net.web.action的有效parent为com.oschina.net,
com.oschina.net的有效parent为com
com的有效parent为root
ps:这里所说为有效parent,实际上com.oschina.net的直接parent为com.oschina,再上级的parent才是com,这里log4j做了一个处理,将com.oschina这一级
设定为一个占位符ProvisionNode,没有任何的appender
2.6:解析renderers
格式:log4j.renderer.renderedClass=renderingClass #定制对象显示 使用renderingClass来render renderedClass
说明:renderer就是为了解决我们在直接打印对象的时候如何输出值的问题,而不是直接调用toString方法
三:log4j的日志打印规则
通常我们都是这样来获取一个logger实例:
代码语言:javascript复制Logger logger = LoggerFactory.getLogger(ObjectClass.class);
这里面的获取逻辑为:
假设ObjectClass.class的全限定路径为com.oschina.net.web.action.controller,那么LoggerFactory会生成一个名称为com.oschina.net.web.action.controller的logger(会缓存起来,下次直接使用),并初始化他的parent,此时logger的层级关系为:
日志打印时会自底向上用各自的appender输出日志内容,贴一下代码:
代码语言:javascript复制 for (Category c = this; c != null; c = c.parent) {
// Protected against simultaneous call to addAppender, removeAppender,...
synchronized (c) {
if (c.aai != null) {
writes = c.aai.appendLoopOnAppenders(event);
}
if (!c.additive) {
break;
}
}
}
不过这里有一点需要说明,因为在日志打印的时候需要判断logger的threshold,但是生成的最下面的com.oschina.net.web.action.controller这个loggerthreshold为null,
log4j会自底向上找到第一个不为null的logger来判定是否可以输出当前日志,因此在logger级别设定的时候就需要注意parent的级别需要比child的级别低,不然很容易导致
日志文件中输出了2条或以上的日志信息
四。appender类图
由于Logger相关类只有两个(另一个是Category类),configure相关类和filter类继承相关都很简单,这里只贴一个自己做的appender相关的类图
在看到DailyRollingFileAppender时,看到里面判断时间使用的System.currentTimeMillis(),在高并发下场景下是很耗时的一个操作,因此自己写了一个
加缓存的DailyRollingFileAppenderWithCache实现,具体可见github
五。总结
总的来说已经熟悉了log4j的流程,对曾经的疑问也已经有了解答,欢迎大家留言交流。