logback源码阅读-配置文件解析过程(六)
前面介绍了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进行处理