log4j 源码解析_log4j源码解析-文件解析

承接前文log4j源码解析,前文主要介绍了log4j的文件加载方式以及Logger对象创建。本文将在此基础上具体看下log4j是如何解析文件并输出我们所常见的日志格式

附例

文件的加载方式,我们就选举log4j.properties作为分析的文件例子,并附上相应的通用配置

log4j.rootLogger=info,stdout,logfile,errorfile

log4j.logger.org.apache=DEBUG

log4j.logger.java.sql.Connection=DEBUG

log4j.logger.java.sql.Statement=DEBUG

log4j.logger.java.sql.PreparedStatement=DEBUG

log4j.logger.java.sql.ResultSet=INFO

log4j.logger.freemarker.core=error

#standout log appender #

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n

#common log appender #

log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender

log4j.appender.logfile.File=../logs/appender-test/info.log

log4j.appender.logfile.append=true

log4j.appender.logfile.encoding=GB18030

log4j.appender.logfile.layout=org.apache.log4j.PatternLayout

log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

#error log appender #

log4j.appender.errorfile=org.apache.log4j.DailyRollingFileAppender

log4j.appender.errorfile.File=../logs/appender-test/error.log

log4j.appender.errorfile.Threshold=WARN

log4j.appender.errorfile.append=true

log4j.appender.errorfile.encoding=GB18030

log4j.appender.errorfile.layout=org.apache.log4j.PatternLayout

log4j.appender.errorfile.layout.ConversionPattern=%d %p [%c] - %m%n

此处不详解,我们直接看源码方面是如何处理,从代码层面来通用理解下上述的配置

OptionConverter

操作log4j配置的主要工具类,所有的读取配置并封装成对象均以此类作为入口,其在LogManager的调用方式为

OptionConverter.selectAndConfigure(url,configuratorClassName,LogManager.getLoggerRepository());

入参作下简单的展示

url - 文件路径

clazz - log4j.configuratorClass属性对应的class,默认为null

hierarchy - log4j的层级管理类,存储log4j的通用配置,默认为Hierarchy类

OptionConverter#selectAndConfigure-解析配置入口

简单看下源码

static public void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) {

Configurator configurator = null;

String filename = url.getFile();

//xml格式的文件则采用DOMConfigurator解析类,表明默认采用xml格式的解析方式

if(clazz == null && filename != null && filename.endsWith(".xml")) {

clazz = "org.apache.log4j.xml.DOMConfigurator";

}

if(clazz != null) {

LogLog.debug("Preferred configurator class: " + clazz);

configurator = (Configurator) instantiateByClassName(clazz,

Configurator.class,

null);

if(configurator == null) {

LogLog.error("Could not instantiate configurator ["+clazz+"].");

return;

}

} else {

//最后一种方式则为properties解析方式

configurator = new PropertyConfigurator();

}

configurator.doConfigure(url, hierarchy);

}

从简单的注释中我们可以得出log4j只支持两种方式的解析方式

1.DOMConfigurator-xml格式的解析器,默认

2.PropertyConfigurator-properties格式的解析器

本文则着重讲解.properties配置文件的解析,即关注PropertyConfigurator解析器

PropertyConfigurator#doConfigure-解析properties配置文件

读取文件的方式就不分析了,很常见的采用Properties类来存储数据,递上重要的逻辑片段代码

public void doConfigure(Properties properties, LoggerRepository hierarchy) {

repository = hierarchy;

// 读取log4j.debug配置,值为boolean型,表明内部log是否支持debug模式

String value = properties.getProperty(LogLog.DEBUG_KEY);

if(value == null) {

value = properties.getProperty("log4j.configDebug");

if(value != null)

LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");

}

if(value != null) {

LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));

}

//读取log4j.reset的boolean值,true代表使用默认的配置

String reset = properties.getProperty(RESET_KEY);

if (reset != null && OptionConverter.toBoolean(reset, false)) {

hierarchy.resetConfiguration();

}

//log4j.threshold阈值配置,也就是告警级别配置

String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,

properties);

if(thresholdStr != null) {

hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,

(Level) Level.ALL));

LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"].");

}

// 配置根分类,也就是rootLogger

configureRootCategory(properties, hierarchy);

// 配置Logger工厂

configureLoggerFactory(properties);

// 解析非root的其他配置

parseCatsAndRenderers(properties, hierarchy);

LogLog.debug("Finished configuring.");

// 清空下缓存

registry.clear();

}

按照上面的解析顺序作下备注

1.解析log4j.debug/log4j.configDebug(boolean)

是否让log4j的内部输出源支持debug模式,其实也就是是否调用System.out.println()方法,默认不支持debug模式,支持warn/error模式。(支持System.err.println()方法)

2.解析log4j.reset(boolean)

是否重新设置log4j配置,默认不重新设置(Optional,作用微小)

3.解析log4j.threshold(String)

trace/debug/info/warn/error/fatal配置告警级别,表明对所有的输出源,低于该等级则不输出

4.解析根节点rootLogger

5.解析日志工厂

6.解析非根节点

我们对后三步的操作作下简单的分析,加深我们对通用配置的理解

PropertyConfigurator#configureRootCategory-解析根节点

首先简单的看下里面的操作逻辑

void configureRootCategory(Properties props, LoggerRepository hierarchy) {

// log4j.rootLogger或者log4j.rootCategory,支持${}系统变量取值

String effectiveFrefix = ROOT_LOGGER_PREFIX;

String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);

if(value == null) {

value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);

effectiveFrefix = ROOT_CATEGORY_PREFIX;

}

if(value == null)

LogLog.debug("Could not find root logger information. Is this OK?");

else {

Logger root = hierarchy.getRootLogger();

synchronized(root) {

// 关键代码

parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);

}

}

}

承接上述的关键代码分析,此处的logger参数为rootLogger

/**

** This method must work for the root category as well.

*/

void parseCategory(Properties props, Logger logger, String optionKey,

String loggerName, String value) {

LogLog.debug("Parsing for [" +loggerName +"] with value=[" + value+"].");

// ,分隔符解析

StringTokenizer st = new StringTokenizer(value, ",");

if(!(value.startsWith(",") || value.equals(""))) {

if(!st.hasMoreTokens())

return;

String levelStr = st.nextToken();

LogLog.debug("Level token is [" + levelStr + "].");

// If the level value is inherited, set category level value to

// null. We also check that the user has not specified inherited for the

// root category.

if(INHERITED.equalsIgnoreCase(levelStr) ||

NULL.equalsIgnoreCase(levelStr)) {

if(loggerName.equals(INTERNAL_ROOT_NAME)) {

LogLog.warn("The root logger cannot be set to null.");

} else {

logger.setLevel(null);

}

} else {

logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG));

}

LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());

}

// 删除所有的输出源对象

logger.removeAllAppenders();

Appender appender;

String appenderName;

while(st.hasMoreTokens()) {

appenderName = st.nextToken().trim();

if(appenderName == null || appenderName.equals(","))

continue;

LogLog.debug("Parsing appender named \"" + appenderName +"\".");

appender = parseAppender(props, appenderName);

if(appender != null) {

logger.addAppender(appender);

}

}

}

由以上的代码可以简单的得知log4j.rootLogger对应的配置项为{level},{appenderNames}

1.{level} - 日志等级,设置根日志的日志等级,应用于所有的输出源

2.{appenderNames} - 可配置多个输出源,以,为分隔符。并由此属性解析log4j.appender开头的配置项

再而分析了解下PropertyConfigurator#parseAppender()方法解析输出源,为了防止代码展示过多,我们截取主要的代码片段进行分析

Appender parseAppender(Properties props, String appenderName) {

....

// log4j.appender.{appenderName}

String prefix = APPENDER_PREFIX + appenderName;

// log4j.appender.{appenderName}.layout

String layoutPrefix = prefix + ".layout";

// 首先根据log4j.appender.{appenderName}解析得到Appender对象

appender = (Appender) OptionConverter.instantiateByKey(props, prefix,

org.apache.log4j.Appender.class,

null);

...

if(appender instanceof OptionHandler) {

if(appender.requiresLayout()) {

// 解析得到Layout对象,代表该输出源的输出格式

Layout layout = (Layout) OptionConverter.instantiateByKey(props,

layoutPrefix,

Layout.class,

null);

....

// 解析log4j.appender.{appenderName}.errorhandler

final String errorHandlerPrefix = prefix + ".errorhandler";

String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);

if (errorHandlerClass != null) {

ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(props,

errorHandlerPrefix,

ErrorHandler.class,

null);

if (eh != null) {

appender.setErrorHandler(eh);

LogLog.debug("Parsing errorhandler options for \"" + appenderName +"\".");

// 解析ErrorHandler对象

parseErrorHandler(eh, errorHandlerPrefix, props, repository);

...

}

}

...

}

// 解析log4j.appender.{appenderName}.filter配置

parseAppenderFilters(props, appenderName, appender);

registryPut(appender);

return appender;

}

具体解析的过程就不讲解了,只在此处作下罗列,此处假定appenderName为console

1.log4j.appender.console - 对应的输出源的类名

2.log4j.appender.console.layout - 对应输出源的日志展示类名,通用为log4j.appender.{appenderName}.layout

3.log4j.appender.console.errorhandler-对应输出源的错误信息处理类名

4.log4j.appender.console.filter-输出源过滤类,支持配置多个。格式为log4j.appender.console.filter.{filterName}={filterClass}

5.log4j.appender.console.encoding/threshold-此类的额外参数,其会利用反射的机制调用相应的setter方法进行设置

PropertyConfigurator#configureLoggerFactory-解析日志工厂

解析的为log4j.loggerFactory配置,其可以指定logger工厂的实现类,默认为DefaultCategoryFactory,其内部就一个方法makeNewLoggerInstance()用于创建日志类Logger。用户可自定义实现

PropertyConfigurator#parseCatsAndRenderers-解析非根节点

解析的为log4j.logger/log4j.category/log4j.additivity/log4j.renderer/log4j.throwableRenderer配置,具体解析读者可自行分析,此处作下总结

1.log4j.logger/log4j.category以此为开头的配置,其会解析为Logger对象,方式与log4j.rootLogger配置一致,多用于对指定的类进行特定级别的输出,默认继承根Logger对象的输出源配置

2.log4j.additivity以此开头的配置,表明对特定的Logger对象只输出自己所拥有的Appenders,不采用根Logger对象的Appenders

3.log4j.renderer/log4j.throwableRenderer开头的配置,前者主要配置对普通输出信息的渲染处理,后者对异常信息的渲染处理。默认均有实现,一般不指定

4.对输出源的日志级别输出与否比较规则作下总结(以基于com.jing.test.Application类调用info()方法输出举个例子):

首先判断是否>=log4j.threshold属性指定的日志级别Level,如果不满足,则不输出,反之继续往下走。eg.log4j.threshold=WARN则输出源无法输出

然后根据loggerName获取log4j.category/log4j.logger对应的Level(如果loggerName以.分隔,则当前loggerName找不到会向父级获取,没定义则应用rootLogger),判断是否>=Level,否则不输出,反之继续往下走

eg. 比如定义了log4j.logger.com.jing.test=INFO,但没定义log4j.logger.com.jing.test.Application则其会应用com.jing.test中的Level,即Level=INFO

最后获取输出源Appender指定的日志级别Level,即${appenderName}.Threshold属性,如果>=指定的Level,则进行输出,反之不输出

总结

见本文的分析,通过源码加深我们对配置的理解,心中多一份踏实

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值