4.JoranConfigurator解析logbak.xml

embedded/2025/1/22 22:51:46/

文章目录

  • 一、前言
  • 二、源码解析
    • GenericXMLConfigurator
      • logback.xml解析
      • 通过SaxEvent构建节点model
      • 解析model节点
      • DefaultProcessor解析model
  • 三、总结

一、前言

上一篇介绍了logback模块解析logback.mxl文件的入口, 我们可以手动指定logback.xml文件的位置, 也可以使用其它的名字, 本节我们继续讨论logback是如何解析logback.xml文件的。

二、源码解析

拿出我们上一节的继承图
在这里插入图片描述

其中ContextAwareContextAwareBase是有关日志上下文LoggerContext注入与打印启动日志的, 我们不介绍。

看到这个Aware结尾的有的同学可能会感觉到很有亲切感, 没错, spring中有很多这种Aware结尾的类, 例如ApplicationContextAwareEnvironmentAware等xxxAware, 它们都会提供一个setXxx的方法, 用来在框架启动的时候注入一个xxx对象。

GenericXMLConfigurator

public abstract class GenericXMLConfigurator extends ContextAwareBase {/*** SaxEvent解析器*/protected SaxEventInterpreter saxEventInterpreter;/*** model解析器的上下文*/protected ModelInterpretationContext modelInterpretationContext;/*** 配置文件节点路径和action的映射* 默认是SimpleRuleStore*/private RuleStore ruleStore;public final void doConfigure(URL url) throws JoranException {InputStream in = null;try {// 1.给Context设置ConfigurationWatchList; 用于配置文件热更新informContextOfURLUsedForConfiguration(getContext(), url);URLConnection urlConnection = url.openConnection();// per http://jira.qos.ch/browse/LOGBACK-117  LBCORE-105// per http://jira.qos.ch/browse/LOGBACK-163  LBCORE-127urlConnection.setUseCaches(false);in = urlConnection.getInputStream();// 2.解析配置; url.toExternalForm():返回url表示的绝对路径的字符串形式doConfigure(in, url.toExternalForm());} catch (IOException ioe) {String errMsg = "Could not open URL [" + url + "].";// 打印解析异常日志addError(errMsg, ioe);throw new JoranException(errMsg, ioe);} finally {if (in != null) {try {// 3.关闭流in.close();} catch (IOException ioe) {String errMsg = "Could not close input stream";addError(errMsg, ioe);throw new JoranException(errMsg, ioe);}}}}
}

这个方法就是开始解析logback.xml文件真正的入口了public final void doConfigure(URL url) throws JoranException

  1. informContextOfURLUsedForConfiguration方法用来设置动态热加载的配置文件, 也就是我们<configuration debug="true" scan="true" scanPeriod="10 second"> 这里动态刷新的默认文件
  2. doConfigure: 进一步解析

doConfigure(final InputSource inputSource)

直接从doConfigure跳过来看这个方法即可

public final void doConfigure(final InputSource inputSource) throws JoranException {// 发布配置开始事件context.fireConfigurationEvent(newConfigurationStartedEvent(this));long threshold = System.currentTimeMillis();// 1.解析日志配置文件; 例如logback.xmlSaxEventRecorder recorder = populateSaxEventRecorder(inputSource);// 获取解析节点的结果; 每个节点都会解析成StartEvent, BodyEvent, EndEventList<SaxEvent> saxEvents = recorder.getSaxEventList();if (saxEvents.isEmpty()) {addWarn("Empty sax event list");return;}// 2.根据xml节点的解析生成对应的model对象, top默认是configuration的modelModel top = buildModelFromSaxEventList(recorder.getSaxEventList());if (top == null) {addError(ErrorCodes.EMPTY_MODEL_STACK);return;}// 3.语法检查sanityCheck(top);// 4.解析model节点(核心)processModel(top);// no exceptions at this levelStatusUtil statusUtil = new StatusUtil(context);// 5.发布配置解析结束事件if (statusUtil.noXMLParsingErrorsOccurred(threshold)) {// xml解析无异常addInfo("Registering current configuration as safe fallback point");registerSafeConfiguration(top);context.fireConfigurationEvent(newConfigurationEndedSuccessfullyEvent(this));} else {// xml解析发生异常context.fireConfigurationEvent(newConfigurationEndedWithXMLParsingErrorsEvent(this));}}

方法小结

这里就是解析logback.xml的整个流程了, 编排了5个点

  1. populateSaxEventRecorder方法用来解析文件, 然后返回解析对象
  2. 根据解析logback.xml的结果生成对应的model
  3. 检查语法(不介绍)
  4. 解析model节点(核心)
  5. 发布解析完成事件(成功/失败)

logbackxml_130">logback.xml解析

populateSaxEventRecorder

public SaxEventRecorder populateSaxEventRecorder(final InputSource inputSource) throws JoranException {SaxEventRecorder recorder = new SaxEventRecorder(context);// sax解析配置文件, 每一个节点都会解析成SaxEvent, 分为StartEvent, BodyEvent, EndEventrecorder.recordEvents(inputSource);return recorder;
}

SaxEventRecorder

public class SaxEventRecorder extends DefaultHandler implements ContextAware {// 节点路径final ElementPath elementPath;// 解析节点得到的结果对象List<SaxEvent> saxEventList = new ArrayList<SaxEvent>();public void recordEvents(InputSource inputSource) throws JoranException {// 创建sax解析器SAXParser saxParser = buildSaxParser();try {// sax解析; 当前类SaxEventRecorder也是个DefaultHandlersaxParser.parse(inputSource, this);return;} catch (xxxException ie) {// ...异常处理}throw new IllegalStateException("This point can never be reached");}// 解析节点的起始标签public void startElement(String namespaceURI, String localName, String qName, Attributes atts) {// ...}// 解析标签内容部分public void characters(char[] ch, int start, int length) {// ...}// 解析到标签结尾部分时触发public void endElement(String namespaceURI, String localName, String qName) {// ...   }
}

方法小结

  1. SaxEventRecorder对象用来封装解析logback.xml的逻辑, 同时它也是一个DefaultHandler对象, 负责处理每个节点的具体解析逻辑
  2. 使用sax解析logback.xml文件
  3. 每个节点解析结果存放在实例变量saxEventList中

关于常见的xml解析框架的对比如下

特性DOM4JSAXJSOUP
解析方式基于树模型,加载整个文档到内存基于事件驱动,逐行解析类似 DOM 树模型,专注于 HTML/XML
性能性能较高,但文件过大时内存消耗明显性能最高,适合大文件解析性能适中,适合中小型 XML 或 HTML 文档
内存占用较高,依赖于内存加载整个文档最低,仅在解析时占用较少内存较高,但通常适合处理网页等较小文件
功能支持强大的 XPath 支持,支持修改和生成 XML只支持读取,不能修改文档支持解析和修改文档,HTML/XML 均适用
易用性较高,API 友好较低,需要手动处理事件逻辑非常高,简洁的 API,类似 jQuery 操作
修改能力支持动态修改不支持修改支持动态修改,灵活度高
适用场景适合处理中小型 XML 文件适合处理超大文件或流式读取适合处理 HTML/XML 文件,尤其是网页解析
依赖性需要引入额外依赖(如 dom4j jar 包)无需额外依赖,Java 内置支持需要引入 jsoup jar 包
  • DOM4J:功能全面,支持 XPath,适合需要频繁修改 XML 的场景,但处理超大文件时会占用大量内存。

  • SAX:性能最佳,占用内存最少,适合超大文件的解析,但使用复杂且无法修改文档内容。

  • JSOUP:偏向网页内容解析,API 简单易用,支持 XML 和 HTML 的解析和修改,适合中小型文件处理。

通过SaxEvent构建节点model

buildModelFromSaxEventList

// 通过节点的saxEvent构建节点的model
public Model buildModelFromSaxEventList(List<SaxEvent> saxEvents) throws JoranException {// 构建saxEvent解析器buildSaxEventInterpreter(saxEvents);// 解析节点, 解析节点的顺序是StartEvent, BodyEvent, EndEvent, 最终使用playSaxEvents();Model top = saxEventInterpreter.getSaxEventInterpretationContext().peekModel();return top;
}// 构建saxEvent解析器, 并添加标签路径对应的action
protected void buildSaxEventInterpreter(List<SaxEvent> saxEvents) {// 1.创建ruleStore, 默认是SimpleRuleStoreRuleStore rs = getRuleStore();// 2.将路径和对应的解析对象action绑定addElementSelectorAndActionAssociations(rs);// 3.构建saxEvent解析器this.saxEventInterpreter = new SaxEventInterpreter(context, rs, initialElementPath(), saxEvents);// 给saxEvent解析器上下文添加contextSaxEventInterpretationContext interpretationContext = saxEventInterpreter.getSaxEventInterpretationContext();interpretationContext.setContext(context);// 4.给没有指定action的标签路径添加默认action; 这里是ImplicitModelAction, 用来解析ruleStore规则之外的标签, 给父标签对象添加属性用setImplicitRuleSupplier(saxEventInterpreter);
}

方法小结

  1. 创建ruleStore, 默认是SimpleRuleStore
  2. 将路径和对应的解析对象action绑定
  3. 构建saxEvent解析器
  4. 给没有指定action的标签路径添加默认action; 这里是ImplicitModelAction, 用来解析ruleStore规则之外的标签, 给父标签对象添加属性用

具体的解析逻辑在saxEvent解析器SaxEventInterpreter中, saxEvent经过action处理之后会得到对应标签节点的model对象

解析model节点

public void processModel(Model model) {// 1.创建ModelInterpretationContext并添加默认对象; 当一个标签没有指定class时, 会从这里尝试获取buildModelInterpretationContext();// configuration节点this.modelInterpretationContext.setTopModel(model);modelInterpretationContext.setConfiguratorHint(this);// 2.创建解析model的核心类DefaultProcessor defaultProcessor = new DefaultProcessor(context, this.modelInterpretationContext);// 3.将model与modelHandler和Analyser关联addModelHandlerAssociations(defaultProcessor);// disallow simultaneous configurations of the same contextReentrantLock configurationLock = context.getConfigurationLock();try {configurationLock.lock();// 开始解析modeldefaultProcessor.process(model);} finally {configurationLock.unlock();}
}

方法小结

  1. 构建model解析时的上下文ModelInterpretationContext, 并添加默认标签的class类(如下面的表格)
  2. 创建解析model的核心类DefaultProcessor
  3. 将model和对应的处理类(modelHandler)关联起来
  4. 使用DefaultProcessor解析model

默认标签的属性对应的类

通过buildModelInterpretationContext方法添加在ModelInterpretationContext.DefaultNestedComponentRegistry属性中

属性默认值
AppenderBaselayoutPatternLayout.class
UnsynchronizedAppenderBaselayoutPatternLayout.class
AppenderBaseencoderPatternLayoutEncoder
UnsynchronizedAppenderBaseencoderPatternLayoutEncoder
SSLComponentsslSSLConfiguration
SSLConfigurationparametersSSLParametersConfiguration
SSLConfigurationkeyStoreKeyStoreFactoryBean
SSLConfigurationtrustStoreKeyStoreFactoryBean
SSLConfigurationkeyManagerFactoryKeyManagerFactoryFactoryBean
SSLConfigurationtrustManagerFactoryTrustManagerFactoryFactoryBean
SSLConfigurationsecureRandomSecureRandomFactoryBean

例如下面的配置, 由于FileAppender是UnsynchronizedAppenderBase的子类, 并且encoder节点没有指定class, 而encoder是UnsynchronizedAppenderBase的一个属性, 所以这里取默认值PatternLayoutEncoder

<appender name="FILE" class="ch.qos.logback.core.FileAppender"><file>logs/app.log</file><!-- encoder不指定class的时候, 默认使用的是PatternLayoutEncoder --><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n</pattern></encoder>
</appender>

我们常用的也就appender标签下的这两个layout和encoder属性

各标签节点路径以及其对应的model和modelHandler如下表格

一般情况下我们看节点路径和action以及handler就行, 这个handler就是用来处理logback.xml中各个标签的类。
这里的节点路径就是我们logback.xml文件中所有可以定义的节点了。

标签节点路径解析路径节点的Actionaction解析之后生成的model解析model的Handler
configurationConfigurationActionConfigurationModelConfigurationModelHandlerFull
configuration/contextNameContextNameActionContextNameModelContextNameModelHandler
configuration/contextListenerLoggerContextListenerActionLoggerContextListenerModelLoggerContextListenerModelHandler
configuration/insertFromJNDIInsertFromJNDIActionInsertFromJNDIModelInsertFromJNDIModelHandler
configuration/loggerLoggerActionLoggerModelLoggerModelHandler
configuration/logger/levelLevelActionLevelModelLevelModelHandler
configuration/rootRootLoggerActionRootLoggerModelRootLoggerModelHandler
configuration/root/levelRootLoggerActionRootLoggerModelRootLoggerModelHandler
configuration/logger/appender-refAppenderRefActionAppenderRefModelAppenderRefModelHandler
configuration/root/appender-refAppenderRefActionAppenderRefModelAppenderRefModelHandler
configuration/includeIncludeActionIncludeModelIncludeModelHandler
configuration/propertiesConfiguratorPropertiesConfiguratorActionPropertiesConfiguratorModelPropertiesConfiguratorModelHandler
configuration/consolePluginConsolePluginAction
configuration/receiverReceiverActionReceiverModelReceiverModelHandler
*/variablePropertyActionPropertyModelPropertyModelHandler
*/propertyPropertyActionPropertyModelPropertyModelHandler
configuration/importImportActionImportModelImportModelHandler
configuration/timestampTimestampActionTimestampModelTimestampModelHandler
configuration/shutdownHookShutdownHookActionShutdownHookModelShutdownHookModelHandler
configuration/sequenceNumberGeneratorSequenceNumberGeneratorActionSequenceNumberGeneratorModelSequenceNumberGeneratorModelHandler
configuration/serializeModelSerializeModelActionSerializeModelModelSerializeModelModelHandler
configuration/defineDefinePropertyActionDefineModelDefineModelHandler
configuration/evaluatorEventEvaluatorActionEventEvaluatorModelEventEvaluatorModelHandler
configuration/conversionRuleConversionRuleActionConversionRuleModelConversionRuleModelHandler
configuration/statusListenerStatusListenerActionStatusListenerModelStatusListenerModelHandler
*/appenderAppenderActionAppenderModelAppenderModelHandler
configuration/appender/appender-refAppenderRefActionAppenderRefModelAppenderRefModelHandler
configuration/newRuleNewRuleAction
*/paramParamActionParamModelParamModelHandler
*/ifIfActionIfModelIfModelHandler
*/if/thenThenActionThenModelThenModelHandler
*/if/elseElseActionElseModelElseModelHandler
*/appender/siftSiftActionSiftModelSiftModelHandler
其它属性标签ImplicitModelActionImplicitModelImplicitModelHandler

DefaultProcessor解析model

public void process(Model model) {// 根节点为空, 直接异常if (model == null) {addError("Expecting non null model to process");return;}// 1.将LoggerContext添加到ModelInterpretationContext中, 这是第一个也是最底层的initialObjectPush();// 2.使用第一阶段过滤器过滤model, 将满足条件的model使用handler处理mainTraverse(model, getPhaseOneFilter());// 3.处理依赖analyseDependencies(model);// 4.使用第二阶段过滤器过滤model, 将满足条件的model使用handler处理traversalLoop(this::secondPhaseTraverse, model, getPhaseTwoFilter(), "phase 2");// 配置解析完成addInfo("End of configuration.");// 5.将LoggerContext从ModelInterpretationContext中弹出finalObjectPop();
}

方法小结

  1. 将日志上下文loggerContext放到model解析器中
  2. 使用第一阶段过滤器过滤model, 将满足条件的model使用handler处理, 不满足第一阶段过滤器的model有下面四个, 为什么这四个model这么特殊呢?? 因为它们需要依赖别的model(appender-ref标签)
  • LoggerModel
  • RootLoggerModel
  • AppenderModel
  • AppenderRefModel
  1. 确定依赖顺序
  2. 使用第二阶段过滤器过滤model, 将满足条件的model使用handler处理; 上面四个model将会在这里处理
  3. 弹出loggerContext节点

具体的model节点解析将会在下节挑出几个重点来介绍, 到这里, logback解析logback.xml文件的整体流程就介绍完了

关于一些细节以及JoranConfigurator和JoranConfiguratorBase中相关的内容没有详细介绍, 读者想要了解的更多建议看下源码

三、总结

  1. JoranConfigurator 是logback框架用来解析配置文件的核心类(logback.xml配置文件)
  2. logback.xml文件中每个节点都会被解析成一个saxEvent, 解析过程中借助解析器上下文SaxEventInterpretationContext保存相关信息
  3. RuleStore中映射了每个节点路径对应的action, action会创建对应节点的model
  4. DefaultProcessor中记录了每个model和modelHandler的映射关系
  5. DefaultProcessor借助上下文对象ModelInterpretationContext将model分为两个阶段使用modelHandler进行处理
  6. 最后将处理结果都放到了日志上下文LoggerContext中(这是我们打印日志的重要对象)
  7. 流程就是(eg. configuration/appender/encoder -> saxEvent -> action -> model -> modelHandler -> loggerContext)

http://www.ppmy.cn/embedded/156173.html

相关文章

第五篇 vue3 ref 与 reactive 对比

ref 若需要自动加载 .value ,那么就要在 底部 菜单 中 设置 选项 选择 vue 勾选 &#xff1a; Auto Insert: Dot Value Auto-complete Ref value with .value. 注意点&#xff1a; ref 不能写越过 value. 必须要在valeu 前面 进行定义 通过 reactive 来修改整体名称…

人类大脑与大规模神经网络的对比及未来展望

引言 随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;研究人员不断尝试构建更加复杂和强大的模型&#xff0c;以期实现与人类大脑相媲美的智能水平。本文将探讨当前大规模神经网络&#xff08;LLM, Large Language Models&#xff09;的发展现状&#xff0…

MySQL数据库入门到大蛇尚硅谷宋红康老师笔记 基础篇 part 5

第05章_排序与分页 排序 #第05章_排序与分页#1. 排序# 如果没有使用排序操作&#xff0c;默认情况下查询返回的数据是按照添加数据的顺序显示的。 SELECT * FROM employees;# 1.1 基本使用 # 使用 ORDER BY 对查询到的数据进行排序操作。 # 升序&#xff1a;ASC (ascend) # 降…

macOS安装Gradle环境

文章目录 说明安装JDK安装Gradle 说明 gradle8.5最高支持jdk21&#xff0c;如果使用jdk22建议使用gradle8.8以上版本 安装JDK mac系统安装最新&#xff08;截止2024.9.13&#xff09;Oracle JDK操作记录 安装Gradle 下载Gradle&#xff0c;解压将其存放到资源java/env目录…

Linux编译安装Netgen/NGSolve

本文记录Linux下编译安装Netgen/NGSolve的流程。 零、环境 操作系统Ubuntu 22.04.4 LTSVS Code1.92.1Git2.34.1GCC11.4.0CMake3.22.1oneAPI2024.2.1 一、安装依赖 1.1 VS Code 下载并安装VS Code&#xff0c;然后安装以下插件&#xff0c; Task Explorer Output Colorizer …

【JSqlParser】Java使用JSqlParser解析SQL语句总结

简述 Java解析SQL语句有很多工具都可以做到&#xff0c;比如Mybatis、Druid、目前用来用去最全面的仍然是Jsqlparser&#xff0c;它是一个Github上的开源项目&#xff0c;JSqlParser是一个用于解析SQL语句的Java库&#xff0c;它可以帮助开发者分析和操作SQL语句的结构。无论是…

mac 安装mongodb

本文分享2种mac本地安装mongodb的方法&#xff0c;一种是通过homebrew安装&#xff0c;一种是通过tar包安装 homebrew安装 brew tap mongodb/brew brew upate brew install mongodb-community8.0tar包安装 安装mongodb 1.下载mongodb社区版的tar包 mongdb tar包下载地址 2…

JDK长期支持版本(LTS)

https://blogs.oracle.com/java/post/the-arrival-of-java-23 jdk长期支持版本&#xff08;LTS&#xff09;&#xff1a;JDK 8、11、17、21&#xff1a;