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

本文详细探讨了logback的配置文件加载过程,展示了如何自定义复杂对象标签和简单对象处理,以及如何利用Logger进行异步日志打印。通过源码剖析,揭示了扩展点和关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文基于slf4j 1.7.25

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

    <?xml version="1.0" encoding="UTF-8"?>
    <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.log</File>
            <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>5MB</MaxFileSize>
                </TimeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
            <encoder>
                <pattern>${log.pattern}</pattern>
                <charset>UTF-8</charset>
            </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.log</File>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <testService class="com.zeng.utils.TestService">
            <url>http://xxx</url>
        </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来解析该标签。我们可以看到如下图解析出来的对象都已经赋值好了。

    在这里插入图片描述

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

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

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

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

    在这里插入图片描述

我们通过上面的图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.xml` 配置文件 #### 1. 基本结构 `logback.xml` 是 Logback 日志框架的核心配置文件,用于定义日志级别、输出目标以及格式等内容。以下是其基本结构: ```xml <configuration> <!-- 定义全局属性 --> <property name="LOG_PATH" value="./logs"/> <!-- 控制台输出 --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <!-- 文件输出 --> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>${LOG_PATH}/application.log</file> <append>true</append> <encoder> <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern> </encoder> </appender> <!-- 设置根日志级别并指定使用的 appender --> <root level="info"> <appender-ref ref="CONSOLE"/> <appender-ref ref="FILE"/> </root> </configuration> ``` 上述示例展示了如何设置控制台和文件两种日志输出方式,并指定了日志的格式。 --- #### 2. 动态加载不同环境的日志配置 为了支持多环境下的日志管理,可以通过 `<springProfile>` 标签实现动态加载[^1]。例如: ```xml <springProfiles active="dev, prod"> <springProfile name="dev"> <property name="LOG_PATH" value="./dev_logs"/> </springProfile> <springProfile name="prod"> <property name="LOG_PATH" value="./prod_logs"/> </springProfile> </springProfiles> ``` 在此基础上,可以根据当前激活的 Spring Profile 加载对应的日志路径配置。 --- #### 3. 读取外部配置文件中的参数 如果希望从 `application.properties` 或其他外部配置文件中读取日志相关参数,则需要注意加载顺序问题[^2]。由于 `logback.xml` 的加载早于 `application.yml`,因此无法直接通过 `${}` 占位符获取后者的内容。然而,它能够正常解析 `application.properties` 中的键值对。 假设 `application.properties` 包含如下内容: ```properties logging.file.name=./custom_log_file.log ``` 可以在 `logback.xml` 中这样引用: ```xml <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>${logging.file.name}</file> <append>true</append> <encoder> <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern> </encoder> </appender> ``` --- #### 4. 使用 Logger 实现精细化日志管理 除了全局日志配置外,还可以针对特定包或类单独设定日志级别[^3]。例如: ```xml <!-- 对 com.example.service 包启用 DEBUG 级别的日志 --> <logger name="com.example.service" level="debug" additivity="false"> <appender-ref ref="CONSOLE"/> </logger> <!-- 对 org.springframework 包仅保留 ERROR 级别日志 --> <logger name="org.springframework" level="error" additivity="true"> <appender-ref ref="FILE"/> </logger> ``` 以上配置实现了按需调整各模块的日志行为。 --- #### 5. 添加滚动策略 (Rolling Policy) 对于生产环境中长期运行的应用程序来说,建议引入滚动策略来防止单个日志文件过大。下面是一个基于时间切分的日志滚动方案: ```xml <appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>./logs/application.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>./logs/application-%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>30</maxHistory> <!-- 保存最近 30 天的日志 --> </rollingPolicy> <encoder> <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern> </encoder> </appender> ``` 此片段会每天生成一个新的日志文件,并自动清理超过 30 天的历史数据。 --- ### 总结 通过合理设计 `logback.xml`,不仅可以满足开发阶段的需求,还能适应复杂的生产场景。无论是动态切换环境还是优化性能表现,Logback 提供了丰富的功能选项以应对各种挑战。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值