logback源码解析及自定义Appender、自定义logback.xml标签

本文基于slf4j 1.7.25

目录

      • 0. 基本概念介绍
      • 1. 简单实用示例
      • 2. 加载解析配置logback配置文件源码解析
      • 3. 加载解析配置文件拓展点
        • 3.1 标签对应复杂对象,默认用NestedComplexPropertyIA解析执行
        • 3.2 标签对应的是简单对象,默认用NestedBasicPropertyIA解析执行
      • 4. 使用Logger打印流程解析及拓展点
      • 5. 总结

logback妙用之自定义Converter、异步打印日志
logback源码解析分为两条线:一、加载解析配置文件;二、使用Logger打印日志。

0. 基本概念介绍

  1. Appender:定义日志的格式和输出目的地。
  2. Logger:输出日志的对象,关联上Appender执行相关操作。(一个Logger可以关联多个Appender)

1. 简单实用示例

​ 一般我们会配置一个logback.xml,在程序中使用。

  1. 代码示例

    public class Test {
    
        private final static Logger LOGGER = LoggerFactory.getLogger(Test.class);
        
        public void test(){
            LOGGER.info("xxx");
        }
    }
    
  2. logback.xml

    
    <configuration scan="false">
        <springProperty scope="context" name="logLevel" source="logging.level.root"/>
        <property name="log.pattern"
                  value="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%t] [%level] [%logger{80}] [%X{transactionId}] [%X{spanId}] [%X{parentId}] [%X{serviceId}] [%X{protocol}] [%X{logType}] - %m%n"/>
        <property name="additivity.value" value="false"/>
    
        
        <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
            <layout class="ch.qos.logback.classic.PatternLayout">
                <pattern>${log.pattern}pattern>
            layout>
        appender>
        
        <appender name="accessapender" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <File>${log.access}/access.logFile>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <FileNamePattern>
                    ${log.access}/access.log.%d{yyyy-MM-dd-HH}.%i
                FileNamePattern>
                <TimeBasedFileNamingAndTriggeringPolicy
                                                        class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <MaxFileSize>5MBMaxFileSize>
                TimeBasedFileNamingAndTriggeringPolicy>
            rollingPolicy>
            <encoder>
                <pattern>${log.pattern}pattern>
                <charset>UTF-8charset>
            encoder>
        appender>
    
        <logger name="access" additivity="${additivity.value}" level="${logLevel}">
            <appender-ref ref="accessapender"/>
        logger>
        
        <root level="${logLevel}">
            <appender-ref ref="accessapender"/>
        root>
        
    configuration>
    

2. 加载解析配置logback配置文件源码解析

  1. 我们先从private final static Logger LOGGER = LoggerFactory.getLogger(Test.class);跟进去

    LoggerFactory

    public final class LoggerFactory {
        
        public static Logger getLogger(Class<?> clazz) {
            //跟进去,内部方法
            Logger logger = getLogger(clazz.getName());
            return logger;
        }
    
        public static Logger getLogger(String name) {
            //获取日志工厂,我们看一下
            ILoggerFactory iLoggerFactory = getILoggerFactory();
            return iLoggerFactory.getLogger(name);
        }
    
        public static ILoggerFactory getILoggerFactory() {
            //判断状态,如果没初始化,先初始化,不过多关注
            if (INITIALIZATION_STATE == UNINITIALIZED) {
               //...
            }
            switch (INITIALIZATION_STATE) {
                case SUCCESSFUL_INITIALIZATION:
                    //初始化成功,获取日志工厂.我们看看StaticLoggerBinder这个单例类
                    return StaticLoggerBinder.getSingleton().getLoggerFactory();
                    //...
            }
            throw new IllegalStateException("Unreachable code");
        }
        
    }
    
  2. StaticLoggerBinder

    public class StaticLoggerBinder implements LoggerFactoryBinder {
        //日志上下文
        private LoggerContext defaultLoggerContext = new LoggerContext();
        
        //该类有静态代码块,先执行
        static {
            SINGLETON.init();
        }
        
        void init() {
    		//ContextInitializer,初始化日志上下文,解析logback.xml文件,我们看看autoConfig
            new ContextInitializer(defaultLoggerContext).autoConfig();
            contextSelectorBinder.init(defaultLoggerContext, KEY);
            initialized = true;
        }
    }
    
  3. ContextInitializer

    public class ContextInitializer {
    	public void autoConfig() throws JoranException {
            StatusListenerConfigHelper.installIfAsked(loggerContext);
            // 从classpath下获取logback配置文件的地址,默认按顺序查找logback-test.xml,logback.groovy,logback.xml
            // 如果为空,则默认返回一个(
            // SpringBoot的是自定义了一个查找解析配置文件的类,继承了logback的,所以此处初始化解析类流程有些许不同
            URL url = findURLOfDefaultConfigurationFile(true);
            if (url != null) {
                //加载解析配置文件,看看
                configureByResource(url);
            } else {
                //...
            }
        }
            
        public void configureByResource(URL url) throws JoranException {
      		//...        
            
            //如果是xml后缀的logback文件,利用JoranConfigurator来解析(SpringBoot是继承了该类来解析)
            else if (urlString.endsWith("xml")) {
                JoranConfigurator configurator = new JoranConfigurator();
                configurator.setContext(loggerContext);
                //我们就不进去看了
                configurator.doConfigure(url);
            }
        }
    }
    
  4. 加载解析配置文件后续不看了,但是我们总结一下加载解析配置文件(GenericConfigurator类):

    1. 用sax解析xml,并将解析事件转换成SaxEvent(StartEvent:开始标签,BodyEvent:标签属性,EndEvent:结束标签),放入saxEventList。

    2. 创建Interpreter,遍历saxEventList,匹配相应SaxEvent执行Interpreter中相应的动作(标签中的class实例化成对象,子标签会当成父标签的属性设置进去;标签里面的属性也会设置到标签对象中

    3. 不同的标签会对应不同的Action(AppenderAction,ConfigurationAction,LoggerAcction,RootLoggerAction,PropertyAction),JoranConfigurator中也注册了很多Action(保存在RuleStore中)。执行相应Action的操作,我们可以看到Action中begin、body、end方法就是对应开始标签、标签属性、结束标签执行的动作。

      public abstract class Action extends ContextAwareBase {
          
          public abstract void begin(InterpretationContext ic, String name, Attributes attributes) throws ActionException;
      
          public void body(InterpretationContext ic, String body) throws ActionException {
          }
      
          public abstract void end(InterpretationContext ic, String name) throws ActionException;
      
      }
      
  5. 所以到此结束后,logback.xml文件解析完了。其中定义的类实例化了,属性也赋值了 。

  6. 获取Logger,我们从获取LoggerFactory代码可以看到获取的LoggerFactory其实就是LoggerContext。然后从LoggerContext获取相应的Logger。

3. 加载解析配置文件拓展点

上面我们知道每个xml标签都可以对应一个Action来处理,如果我们要自定义自己的标签,也可以对应一个Action来处理。

3.1 标签对应复杂对象,默认用NestedComplexPropertyIA解析执行
  1. logback.xml。如下自定义Appender,自定义标签testService。TestService有一个属性url,TestAppender有一个属性TestService。

    <appender name="test" class="com.zeng.utils.TestAppender">
        <File>${log.collection}/spring.logFile>
        <encoder>
            <pattern>${log.pattern}pattern>
        encoder>
        <testService class="com.zeng.utils.TestService">
            <url>http://xxxurl>
        testService>
    appender>
    
  2. TestService

    public class TestService {
    
        private String url;
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    }
    
  3. TestAppender

    public class TestAppender extends RollingFileAppender {
    
        private TestService testService;
    
        public TestService getTestService() {
            return testService;
        }
    
        public void setTestService(TestService testService) {
            this.testService = testService;
        }
    }
    
  4. 不是应该在RuleStore中添加一个testService标签对应一个Action吗?其实也不一定需要。如果testService标签对应是一个复杂对象,logback默认会使用NestedComplexPropertyIA来解析该标签。我们可以看到如下图解析出来的对象都已经赋值好了。

    logback源码解析及自定义Appender、自定义logback.xml标签_第1张图片

3.2 标签对应的是简单对象,默认用NestedBasicPropertyIA解析执行

​ 就不举例了,只需吧上面例子中TestService换成一个简单的String类型即可。

4. 使用Logger打印流程解析及拓展点

  1. 通过Logger打印日志,我们就不做解析了。画一个图解析一下

    logback源码解析及自定义Appender、自定义logback.xml标签_第2张图片

我们通过上面的图EnCoder.doEncoder用来编码,Layout.doLayout用来格式化日志。我们重点关注红色框中的subAppend。因为我们如果要自定义一个Appender,主要逻辑也是在subAppend中实现

  1. 示例(我们把上面的例子改一下)

    TestService

    public class TestService {
    
        private String url;
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        //发送http请求,代码省略
        public void httpRequest() {
    
        }
    }
    
    

    TestAppender

    public class TestAppender extends RollingFileAppender {
    
        private TestService testService;
    
    
        public TestService getTestService() {
            return testService;
        }
    
        public void setTestService(TestService testService) {
            this.testService = testService;
        }
    
        //重写subAppend方法,写日志时,发送请求,event中包含日志相关信息。
        @Override
        protected void subAppend(Object event) {
            testService.httpRequest();
            super.subAppend(event);
        }
    }
    

5. 总结

我们通过以上的源码解析,大致了解了logback的配置解析加载,和logback打印日志。

清楚了如果自定义logback.xml标签和自定义Appender。

你可能感兴趣的:(Logback,logback,java,xml)