承接前文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,則進行輸出,反之不輸出
見本文的分析,經過源碼加深咱們對配置的理解,心中多一份踏實