承接前文log4j源码解析,前文主要介绍了log4j的文件加载方式以及Logger对象建立。本文将在此基础上具体看下log4j是如何解析文件并输出咱们所常见的日志格式html
文件的加载方式,咱们就选举log4j.properties
做为分析的文件例子,并附上相应的通用配置java
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
此处不详解,咱们直接看源码方面是如何处理,从代码层面来通用理解下上述的配置sql
操做log4j配置的主要工具类,全部的读取配置并封装成对象均以此类做为入口,其在LogManager的调用方式为
OptionConverter.selectAndConfigure(url,configuratorClassName,LogManager.getLoggerRepository());
入参做下简单的展现apache
url - 文件路径 clazz - log4j.configuratorClass属性对应的class,默认为null hierarchy - log4j的层级管理类,存储log4j的通用配置,默认为Hierarchy类
简单看下源码缓存
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
只支持两种方式的解析方式app
1.DOMConfigurator-xml格式的解析器,默认 2.PropertyConfigurator-properties格式的解析器
本文则着重讲解.properties
配置文件的解析,即关注PropertyConfigurator
解析器工具
读取文件的方式就不分析了,很常见的采用Properties
类来存储数据,递上重要的逻辑片断代码ui
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(); }
按照上面的解析顺序做下备注this
1.解析
log4j.debug/log4j.configDebug(boolean)
url是否让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.解析非根节点
咱们对后三步的操做做下简单的分析,加深咱们对通用配置的理解
首先简单的看下里面的操做逻辑
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方法进行设置
解析的为log4j.loggerFactory配置,其能够指定logger工厂的实现类,默认为
DefaultCategoryFactory
,其内部就一个方法makeNewLoggerInstance()
用于建立日志类Logger。用户可自定义实现
解析的为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对象的Appenders3.
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,则进行输出,反之不输出
见本文的分析,经过源码加深咱们对配置的理解,心中多一份踏实