(1). 概述

最近要对MDC进行扩展,需要看下Spring是如何加载logback的.

(2). 先看下logback的xml配置文件(logback-spring.xml)

<?xml version="1.0" encoding="UTF-8"?>
<configuration scanPeriod="10 seconds">
    <property name="log.path" value="${APPLICATION_LOG_DIR}"/>
    <property name="serverName" value="${spring.application.name}"/>
    <springProperty scope="context"  name="service-name" source="spring.application.name"/>

    <property name="STDOUT_PATTEN"
              value='[%d{yyyy-MM-dd HH:mm:ss.SSS}] %highlight([%level]) [${PID:- }] [%thread] [${service-name}] [%X{traceId:-0}] [%X{tenantId:-0}] [%logger{5}\:%line#%M] %highlight([%msg]) %n'/>
    <!--    [时间][日志等级][进程PID][线程名][服务名称][TraceID][租户ID][包首字母.类名][日志信息]-->
    <property name="FILE_PATTEN"
              value='[%d{yyyy-MM-dd HH:mm:ss.SSS}][%level][${PID:-0}][%thread][${service-name}][%X{traceId:-0}][%X{tenantId:-0}][%logger{5}\:%line#%M][%msg]%n'/>
    <!--    [时间][日志等级][进程PID][线程名][服务名称][TraceID][租户ID][包首字母.类名][日志信息]-->

    <!-- 彩色日志 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />

    <!-- 时间滚动输出 level为 DEBUG 日志 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/${service-name}.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <Pattern>${FILE_PATTEN}</Pattern>
            <charset>UTF-8</charset> <!-- 设置字符集 -->
            <immediateFlush>false</immediateFlush>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志归档 -->
            <fileNamePattern>${log.path}/log-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
    </appender>

    <!--为了防止进程退出时,内存中的数据丢失,请加上此选项-->
    <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
    <!-- 可用来获取StatusManager中的状态 -->
    <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>

    <!-- 解决debug模式下循环发送的问题 -->
    <logger name="org.apache.http.impl.conn.Wire" level="WARN"/>

    <root level="info">
        <appender-ref ref="FILE"/>
    </root>
	
    <logger name="org.springframework" level="info"/>
    <logger name="org.apache" level="info"/>
</configuration>

(3). 加载日志的入口在哪?

答案: spring-boot-2.4.2.jar/META-INF/spring.factories

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.context.logging.LoggingApplicationListener

# Logging Systems
org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory

(4). LoggingApplicationListener

public void onApplicationEvent(ApplicationEvent event) {
	if (event instanceof ApplicationStartingEvent) {
		// 1. 启动事件
		onApplicationStartingEvent((ApplicationStartingEvent) event);
	}
	else if (event instanceof ApplicationEnvironmentPreparedEvent) {
		// 2. 事件准备,会加载logback.xml文件
		onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
	}
	else if (event instanceof ApplicationPreparedEvent) {
		onApplicationPreparedEvent((ApplicationPreparedEvent) event);
	}
	else if (event instanceof ContextClosedEvent
			&& ((ContextClosedEvent) event).getApplicationContext().getParent() == null) {
		onContextClosedEvent();
	}
	else if (event instanceof ApplicationFailedEvent) {
		onApplicationFailedEvent();
	}
} // end onApplicationEvent

(5). 创建LogbackLoggingSystem

// 1.1  通过LoggingSystemFactory.fromSpringFactories()加载:LoggingSystemFactory的实现,Spring提供的SPI.
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
	// 我这里以:LogbackLoggingSystem为例
	this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
	// 1.1.1 触发初始化
	this.loggingSystem.beforeInitialize();
} // end onApplicationStartingEvent

(6). Env准备事件

// 2.1 触发准备事件,即加载classpath下的logback.xml
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
	if (this.loggingSystem == null) {
		this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
	}
	// 初始化
	initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
} // end onApplicationEnvironmentPreparedEvent

(7). 准备加载xml

protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
	getLoggingSystemProperties(environment).apply();
	this.logFile = LogFile.get(environment);
	if (this.logFile != null) {
		this.logFile.applyToSystemProperties();
	}
	this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS);
	initializeEarlyLoggingLevel(environment);
	// 初始化,加载xml文件
	initializeSystem(environment, this.loggingSystem, this.logFile);
	initializeFinalLoggingLevels(environment, this.loggingSystem);
	registerShutdownHookIfNecessary(environment, this.loggingSystem);
} // end initialize

(8). 最终委托给LogbackLoggingSystem加载xml

private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) {
	String logConfig = StringUtils.trimWhitespace(environment.getProperty(CONFIG_PROPERTY));
	try {
		LoggingInitializationContext initializationContext = new LoggingInitializationContext(environment);
		if (ignoreLogConfig(logConfig)) {
			system.initialize(initializationContext, null, logFile);
		}
		else {
			system.initialize(initializationContext, logConfig, logFile);
		}
	}
	catch (Exception ex) {
		Throwable exceptionToReport = ex;
		while (exceptionToReport != null && !(exceptionToReport instanceof FileNotFoundException)) {
			exceptionToReport = exceptionToReport.getCause();
		}
		exceptionToReport = (exceptionToReport != null) ? exceptionToReport : ex;
		// NOTE: We can't use the logger here to report the problem
		System.err.println("Logging system failed to initialize using configuration from '" + logConfig + "'");
		exceptionToReport.printStackTrace(System.err);
		throw new IllegalStateException(ex);
	}
} // end initializeSystem

(9). 总结

SpringBoot加载日志的入口为:LoggingApplicationListener,通过SPI加载LoggingSystemFactory的不同实现来实现日志的加载.