前面介紹了logback源碼初始化過程是委托給ContextInitializer
StaticLoggerBinder
void init() { try { try {
//<1> (new ContextInitializer(this.defaultLoggerContext)).autoConfig(); } catch (JoranException var2) { Util.report("Failed to auto configure default logger context", var2); } if (!StatusUtil.contextHasStatusListener(this.defaultLoggerContext)) { StatusPrinter.printInCaseOfErrorsOrWarnings(this.defaultLoggerContext); } this.contextSelectorBinder.init(this.defaultLoggerContext, KEY); this.initialized = true; } catch (Exception var3) { Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", var3); } }
ContextInitializer
<1>autoConfig
org.slf4j.impl.StaticLoggerBinder#init
->
ch.qos.logback.classic.util.ContextInitializer#autoConfig
public void autoConfig() throws JoranException { /** * <1>這里配置監聽查找配置的消息 * 判斷系統變量是否有-Dlogback.statusListenerClass 參數 * 等於SYSOUT 則默認使用OnConsoleStatusListener 這個是控制台打印 * 否則當做配置類的全路徑(我們自己創建一個StatusListener實現類)
* 可以實現對日志輸出的監聽 比如監聽到之后分析 存入數據庫 */ StatusListenerConfigHelper.installIfAsked(this.loggerContext); /** *這里會依次查找 * <4>1.從系統變量查找配置文件-Dlogback.configurationFile={file} * 2.如果沒有配置則依次找logback-test.xml logback.groovy logback.xml 找到任意一個返回 */ URL url = this.findURLOfDefaultConfigurationFile(true); if (url != null) { //<5>找到配置文件則走配置文件解析配置 this.configureByResource(url); } else { /** * 這里主要是java的SPI擴展點ServiceLoader 如果想實現自己的配置文件定義 可以通過這個做擴展 */ Configurator c = (Configurator) EnvUtil.loadFromServiceLoader(Configurator.class); if (c != null) { try { c.setContext(this.loggerContext); c.configure(this.loggerContext); } catch (Exception var4) { throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass().getCanonicalName() : "null"), var4); } } else { //沒有SPI擴展 則使用默認的配置 SPI擴展可以參考介個 BasicConfigurator basicConfigurator = new BasicConfigurator(); basicConfigurator.setContext(this.loggerContext); basicConfigurator.configure(this.loggerContext); } } }
<1>處主要是配置查找日志文件的監聽器默認是控制台打印
<2>處
<4>
ch.qos.logback.classic.util.ContextInitializer#findURLOfDefaultConfigurationFile
public URL findURLOfDefaultConfigurationFile(boolean updateStatus) { //獲得當前對象的classLoader ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this); //先嘗試從系統變量logback.configurationFile 找file配置文件 URL url = this.findConfigFileURLFromSystemProperties(myClassLoader, updateStatus); if (url != null) { return url; } else { //沒有配置先找logback-test.xml url = this.getResource("logback-test.xml", myClassLoader, updateStatus); if (url != null) { return url; } else { //再找logback.groovy url = this.getResource("logback.groovy", myClassLoader, updateStatus); return url != null ? url : this.getResource("logback.xml", myClassLoader, updateStatus); } } }
<5>
ch.qos.logback.classic.util.ContextInitializer#configureByResource
public void configureByResource(URL url) throws JoranException { if (url == null) { throw new IllegalArgumentException("URL argument cannot be null"); } else { String urlString = url.toString(); //根據后綴判斷是groovy配置還是xml配置 創建不同的解析器 if (urlString.endsWith("groovy")) { if (EnvUtil.isGroovyAvailable()) { GafferUtil.runGafferConfiguratorOn(this.loggerContext, this, url); } else { StatusManager sm = this.loggerContext.getStatusManager(); sm.add(new ErrorStatus("Groovy classes are not available on the class path. ABORTING INITIALIZATION.", this.loggerContext)); } } else { if (!urlString.endsWith("xml")) { throw new LogbackException("Unexpected filename extension of file [" + url.toString() + "]. Should be either .groovy or .xml"); } JoranConfigurator configurator = new JoranConfigurator(); configurator.setContext(this.loggerContext); //<6>執行解析加載配置 configurator.doConfigure(url); } } }
JoranConfigurator
類圖
<6>
ch.qos.logback.core.joran.GenericConfigurator#doConfigure(java.net.URL)
public final void doConfigure(URL url) throws JoranException { InputStream in = null; boolean var12 = false; String errMsg; try { var12 = true; //暫時也不知道干啥的 informContextOfURLUsedForConfiguration(this.getContext(), url); //獲得一個連接對象 這里是FileURLConnection 可以想象是否可以支持http呢 就可以遠程配置文件了 URLConnection urlConnection = url.openConnection(); //不使用緩存 urlConnection.setUseCaches(false); //獲取流 in = urlConnection.getInputStream(); //<7>接下來看這個方法處理 this.doConfigure(in, url.toExternalForm()); var12 = false; } catch (IOException var15) { errMsg = "Could not open URL [" + url + "]."; this.addError(errMsg, var15); throw new JoranException(errMsg, var15); } finally { if (var12) { if (in != null) { try { in.close(); } catch (IOException var13) { String errMsg = "Could not close input stream"; this.addError(errMsg, var13); throw new JoranException(errMsg, var13); } } } } if (in != null) { try { in.close(); } catch (IOException var14) { errMsg = "Could not close input stream"; this.addError(errMsg, var14); throw new JoranException(errMsg, var14); } } }
<7>
ch.qos.logback.core.joran.GenericConfigurator#doConfigure#doConfigure
public final void doConfigure(InputSource inputSource) throws JoranException { long threshold = System.currentTimeMillis(); SaxEventRecorder recorder = new SaxEventRecorder(this.context); //<8>這里利用Sax解析xml 並封裝成SaxEvent recorder.recordEvents(inputSource); //<9>將封裝成java對象的SaxEvent進行配置處理 this.doConfigure(recorder.saxEventList); StatusUtil statusUtil = new StatusUtil(this.context); if (statusUtil.noXMLParsingErrorsOccurred(threshold)) { this.addInfo("Registering current configuration as safe fallback point"); this.registerSafeConfiguration(recorder.saxEventList); } }
<1>
<8>
ch.qos.logback.core.joran.event.SaxEventRecorder#recordEvents
public List<SaxEvent> recordEvents(InputSource inputSource) throws JoranException { SAXParser saxParser = this.buildSaxParser(); try { //這里因為當前類繼承了DefaultHandeler 所以通過這里將SAX解析成當前出席需要的對象 saxParser.parse(inputSource, this); return this.saxEventList; } catch (IOException var4) { this.handleError("I/O error occurred while parsing xml file", var4); } catch (SAXException var5) { throw new JoranException("Problem parsing XML document. See previously reported errors.", var5); } catch (Exception var6) { this.handleError("Unexpected exception while parsing XML document.", var6); } } //解析開始標簽 public void startElement(String namespaceURI, String localName, String qName, Attributes atts) { String tagName = this.getTagName(localName, qName); this.globalElementPath.push(tagName); ElementPath current = this.globalElementPath.duplicate(); this.saxEventList.add(new StartEvent(current, namespaceURI, localName, qName, atts, this.getLocator())); } //解析結束標簽 public void endElement(String namespaceURI, String localName, String qName) { this.saxEventList.add(new EndEvent(namespaceURI, localName, qName, this.getLocator())); this.globalElementPath.pop(); }
<9>
ch.qos.logback.core.joran.GenericConfigurator#doConfigure(java.util.List<ch.qos.logback.core.joran.event.SaxEvent>)
doConfigure
public void doConfigure(List<SaxEvent> eventList) throws JoranException { //<10>這里主要是初始化interpreter內部維護配置文件每個標簽解析action的關系 this.buildInterpreter(); synchronized(this.context.getConfigurationLock()) { //開始映射對應的action進行解析 this.interpreter.getEventPlayer().play(eventList); } }
<10>
ch.qos.logback.core.joran.GenericConfigurator#buildInterpreter
protected void buildInterpreter() { RuleStore rs = new SimpleRuleStore(this.context); //<11>添加action映射關系 action為對應標簽的解析器 this.addInstanceRules(rs); this.interpreter = new Interpreter(this.context, rs, this.initialElementPath()); InterpretationContext interpretationContext = this.interpreter.getInterpretationContext(); interpretationContext.setContext(this.context); this.addImplicitRules(this.interpreter); this.addDefaultNestedComponentRegistryRules(interpretationContext.getDefaultNestedComponentRegistry()); }
<11>
ch.qos.logback.classic.joran.JoranConfigurator#addInstanceRules
可以參考一下 實現自定義屬性
public void addInstanceRules(RuleStore rs) {
//<12>父類路由 比如Appender就在父類追加的 可以看到以下就是各個標簽的Action解析器 super.addInstanceRules(rs); rs.addRule(new ElementSelector("configuration"), new ConfigurationAction()); rs.addRule(new ElementSelector("configuration/contextName"), new ContextNameAction()); rs.addRule(new ElementSelector("configuration/contextListener"), new LoggerContextListenerAction()); rs.addRule(new ElementSelector("configuration/insertFromJNDI"), new InsertFromJNDIAction()); rs.addRule(new ElementSelector("configuration/evaluator"), new EvaluatorAction()); rs.addRule(new ElementSelector("configuration/appender/sift"), new SiftAction()); rs.addRule(new ElementSelector("configuration/appender/sift/*"), new NOPAction()); rs.addRule(new ElementSelector("configuration/logger"), new LoggerAction()); rs.addRule(new ElementSelector("configuration/logger/level"), new LevelAction()); rs.addRule(new ElementSelector("configuration/root"), new RootLoggerAction()); rs.addRule(new ElementSelector("configuration/root/level"), new LevelAction()); rs.addRule(new ElementSelector("configuration/logger/appender-ref"), new AppenderRefAction()); rs.addRule(new ElementSelector("configuration/root/appender-ref"), new AppenderRefAction()); rs.addRule(new ElementSelector("*/if"), new IfAction()); rs.addRule(new ElementSelector("*/if/then"), new ThenAction()); rs.addRule(new ElementSelector("*/if/then/*"), new NOPAction()); rs.addRule(new ElementSelector("*/if/else"), new ElseAction()); rs.addRule(new ElementSelector("*/if/else/*"), new NOPAction()); if (PlatformInfo.hasJMXObjectName()) { rs.addRule(new ElementSelector("configuration/jmxConfigurator"), new JMXConfiguratorAction()); } rs.addRule(new ElementSelector("configuration/include"), new IncludeAction()); rs.addRule(new ElementSelector("configuration/consolePlugin"), new ConsolePluginAction()); rs.addRule(new ElementSelector("configuration/receiver"), new ReceiverAction()); }
具體擴展可以參考一下appender實現 當我們需要自定義某個組件的時候也需要看一下對應action源碼 比如appenderAction 實現了某個接口就調用start方法 內部可能給我們預留很多擴展
<12>
ch.qos.logback.core.joran.JoranConfiguratorBase#addInstanceRules
protected void addInstanceRules(RuleStore rs) { rs.addRule(new ElementSelector("configuration/variable"), new PropertyAction()); rs.addRule(new ElementSelector("configuration/property"), new PropertyAction()); rs.addRule(new ElementSelector("configuration/substitutionProperty"), new PropertyAction()); rs.addRule(new ElementSelector("configuration/timestamp"), new TimestampAction()); rs.addRule(new ElementSelector("configuration/shutdownHook"), new ShutdownHookAction()); rs.addRule(new ElementSelector("configuration/define"), new DefinePropertyAction()); rs.addRule(new ElementSelector("configuration/contextProperty"), new ContextPropertyAction()); rs.addRule(new ElementSelector("configuration/conversionRule"), new ConversionRuleAction()); rs.addRule(new ElementSelector("configuration/statusListener"), new StatusListenerAction()); rs.addRule(new ElementSelector("configuration/appender"), new AppenderAction()); rs.addRule(new ElementSelector("configuration/appender/appender-ref"), new AppenderRefAction()); rs.addRule(new ElementSelector("configuration/newRule"), new NewRuleAction()); rs.addRule(new ElementSelector("*/param"), new ParamAction(this.getBeanDescriptionCache())); }
StatusListenerConfigHelper
<1>
ch.qos.logback.core.util.StatusListenerConfigHelper#installIfAsked
public static void installIfAsked(Context context) { //從系統變量logback.statusListenerClass找到class String slClass = OptionHelper.getSystemProperty("logback.statusListenerClass"); //如果有配置 if (!OptionHelper.isEmpty(slClass)) { //<2>載入 addStatusListener(context, slClass); } }
<2>
ch.qos.logback.core.util.StatusListenerConfigHelper#addStatusListener
private static void addStatusListener(Context context, String listenerClassName) { StatusListener listener = null; //如果配置的是SYSOUT 則默認使用OnConsoleStatusListener if ("SYSOUT".equalsIgnoreCase(listenerClassName)) { listener = new OnConsoleStatusListener(); } else { //<3>根據名字加載並初始化配置的Listener對象 listener = createListenerPerClassName(context, listenerClassName); } initAndAddListener(context, (StatusListener)listener); }
<3>
ch.qos.logback.core.util.StatusListenerConfigHelper#initAndAddListener
private static StatusListener createListenerPerClassName(Context context, String listenerClass) { try { return (StatusListener)OptionHelper.instantiateByClassName(listenerClass, StatusListener.class, context); } catch (Exception var3) { var3.printStackTrace(); return null; } }
總結
1.StaticLoggerBinder委托給ContextInitializer做初始化操作
2.首先會尋找-Dlogback.configurationFile={file} 是否有配置xml地址
3.沒有找到則會依次在classpath下尋找logback-test.xml logback.groovy logback.xml 的配置文件 找到任意一個返回
4.如果沒有找到會通過SPI擴展點看是否有自定義Config解析類
5.如果找到根據根據文件后綴走相應的解析類因為支持groovy格式文件 xml是走JoranConfigurator
6.JoranConfigurator會通過jdk SAX解析配置文件 然后自定義Handler將每個節點封裝成SAXEvent
6.內部通過Interpreter 將維護的每個節點SAXEvent 所映射的Action進行處理