4.JoranConfigurator解析logbak.xml

文章目录

一、前言

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

二、源码解析

拿出我们上一节的继承图

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

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

GenericXMLConfigurator

java 复制代码
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-127
            urlConnection.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跳过来看这个方法即可

java 复制代码
public final void doConfigure(final InputSource inputSource) throws JoranException {
        // 发布配置开始事件
        context.fireConfigurationEvent(newConfigurationStartedEvent(this));
        long threshold = System.currentTimeMillis();

        // 1.解析日志配置文件; 例如logback.xml
        SaxEventRecorder recorder = populateSaxEventRecorder(inputSource);
        // 获取解析节点的结果; 每个节点都会解析成StartEvent, BodyEvent, EndEvent
        List<SaxEvent> saxEvents = recorder.getSaxEventList();
        if (saxEvents.isEmpty()) {
            addWarn("Empty sax event list");
            return;
        }
        // 2.根据xml节点的解析生成对应的model对象, top默认是configuration的model
        Model top = buildModelFromSaxEventList(recorder.getSaxEventList());
        if (top == null) {
            addError(ErrorCodes.EMPTY_MODEL_STACK);
            return;
        }
        // 3.语法检查
        sanityCheck(top);
        // 4.解析model节点(核心)
        processModel(top);

        // no exceptions at this level
        StatusUtil 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. 发布解析完成事件(成功/失败)

logback.xml解析

populateSaxEventRecorder

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

SaxEventRecorder

java 复制代码
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也是个DefaultHandler
            saxParser.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解析框架的对比如下

特性 DOM4J SAX JSOUP
解析方式 基于树模型,加载整个文档到内存 基于事件驱动,逐行解析 类似 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

java 复制代码
// 通过节点的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, 默认是SimpleRuleStore
    RuleStore rs = getRuleStore();
    // 2.将路径和对应的解析对象action绑定
    addElementSelectorAndActionAssociations(rs);
    // 3.构建saxEvent解析器
    this.saxEventInterpreter = new SaxEventInterpreter(context, rs, initialElementPath(), saxEvents);
    // 给saxEvent解析器上下文添加context
    SaxEventInterpretationContext 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节点

java 复制代码
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 context
    ReentrantLock configurationLock = context.getConfigurationLock();

    try {
        configurationLock.lock();
        // 开始解析model
        defaultProcessor.process(model);
    } finally {
        configurationLock.unlock();
    }
}

方法小结

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

默认标签的属性对应的类

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

属性 默认值
AppenderBase layout PatternLayout.class
UnsynchronizedAppenderBase layout PatternLayout.class
AppenderBase encoder PatternLayoutEncoder
UnsynchronizedAppenderBase encoder PatternLayoutEncoder
SSLComponent ssl SSLConfiguration
SSLConfiguration parameters SSLParametersConfiguration
SSLConfiguration keyStore KeyStoreFactoryBean
SSLConfiguration trustStore KeyStoreFactoryBean
SSLConfiguration keyManagerFactory KeyManagerFactoryFactoryBean
SSLConfiguration trustManagerFactory TrustManagerFactoryFactoryBean
SSLConfiguration secureRandom SecureRandomFactoryBean

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

xml 复制代码
<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文件中所有可以定义的节点了。

标签节点路径 解析路径节点的Action action解析之后生成的model 解析model的Handler
configuration ConfigurationAction ConfigurationModel ConfigurationModelHandlerFull
configuration/contextName ContextNameAction ContextNameModel ContextNameModelHandler
configuration/contextListener LoggerContextListenerAction LoggerContextListenerModel LoggerContextListenerModelHandler
configuration/insertFromJNDI InsertFromJNDIAction InsertFromJNDIModel InsertFromJNDIModelHandler
configuration/logger LoggerAction LoggerModel LoggerModelHandler
configuration/logger/level LevelAction LevelModel LevelModelHandler
configuration/root RootLoggerAction RootLoggerModel RootLoggerModelHandler
configuration/root/level RootLoggerAction RootLoggerModel RootLoggerModelHandler
configuration/logger/appender-ref AppenderRefAction AppenderRefModel AppenderRefModelHandler
configuration/root/appender-ref AppenderRefAction AppenderRefModel AppenderRefModelHandler
configuration/include IncludeAction IncludeModel IncludeModelHandler
configuration/propertiesConfigurator PropertiesConfiguratorAction PropertiesConfiguratorModel PropertiesConfiguratorModelHandler
configuration/consolePlugin ConsolePluginAction
configuration/receiver ReceiverAction ReceiverModel ReceiverModelHandler
*/variable PropertyAction PropertyModel PropertyModelHandler
*/property PropertyAction PropertyModel PropertyModelHandler
configuration/import ImportAction ImportModel ImportModelHandler
configuration/timestamp TimestampAction TimestampModel TimestampModelHandler
configuration/shutdownHook ShutdownHookAction ShutdownHookModel ShutdownHookModelHandler
configuration/sequenceNumberGenerator SequenceNumberGeneratorAction SequenceNumberGeneratorModel SequenceNumberGeneratorModelHandler
configuration/serializeModel SerializeModelAction SerializeModelModel SerializeModelModelHandler
configuration/define DefinePropertyAction DefineModel DefineModelHandler
configuration/evaluator EventEvaluatorAction EventEvaluatorModel EventEvaluatorModelHandler
configuration/conversionRule ConversionRuleAction ConversionRuleModel ConversionRuleModelHandler
configuration/statusListener StatusListenerAction StatusListenerModel StatusListenerModelHandler
*/appender AppenderAction AppenderModel AppenderModelHandler
configuration/appender/appender-ref AppenderRefAction AppenderRefModel AppenderRefModelHandler
configuration/newRule NewRuleAction
*/param ParamAction ParamModel ParamModelHandler
*/if IfAction IfModel IfModelHandler
*/if/then ThenAction ThenModel ThenModelHandler
*/if/else ElseAction ElseModel ElseModelHandler
*/appender/sift SiftAction SiftModel SiftModelHandler
其它属性标签 ImplicitModelAction ImplicitModel ImplicitModelHandler

DefaultProcessor解析model

java 复制代码
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)
相关推荐
m0_748235611 天前
【SpringBoot】【log】 自定义logback日志配置
java·spring boot·logback
青灯文案11 天前
SpringBoot 项目中配置日志系统文件 logback-spring.xml 原理和用法介绍
spring boot·spring·logback
Mr_sun.1 天前
Day09-后端Web实战——部门管理开发&Logback日志技术
前端·logback
极客先躯2 天前
Flink控制台任务提交的时候, SLF4J 多个绑定问题.
大数据·flink·异常处理·常见问题·slf4j
uncleqiao3 天前
2.slf4j入口
slf4j
七禾页话5 天前
Spring Boot 3.x 整合 Logback 日志框架(支持异步写入)
spring boot·logback
LXMXHJ9 天前
Java-日志-Slf4j-Log4j-logback
java·log4j·logback
NullPointerExpection10 天前
java 中 main 方法使用 KafkaConsumer 拉取 kafka 消息如何禁止输出 debug 日志
java·kafka·log4j·slf4j
一二小选手12 天前
【SpringBoot】日志处理-异常日志(Logback)
java·spring boot·后端·logback