Spring Boot 源码解析之 Logging

目录

  1. 设计总览(Spring Boot Logging 模块抽象)
  2. LoggingSystem 加载机制源码分析
  3. LoggingApplicationListener 启动流程
  4. Log4J2LoggingSystem 主要源码解析
  5. logging.config、log4j2-spring.xml 加载逻辑
  6. 动态日志级别设置(Actuator 调用)

✅ 1️⃣ 设计总览

logging初始化流程总览:

复制代码
[Spring Boot 启动]
        |
        v
[LoggingApplicationListener]
        |
        v
[LoggingSystem.get(ClassLoader)]
        |
        v
[根据 classpath 判断日志系统]
        |
        v
[创建 Log4J2LoggingSystem 或 LogbackLoggingSystem 实例]
        |
        v
[LoggingSystem.initialize(...)]
        |
        v
[判断是否指定 logging.config]
    | Yes                          | No
    v                              v
initializeWithSpecificConfig()     initializeWithConventions()
                                      |
                                      v
                      getSelfInitializationConfig()
                           | Yes             | No
                           v                 v
                      reinitialize()     getSpringInitializationConfig()
                                             | Yes         | No
                                             v             v
                                    loadConfiguration()  loadDefaults()

Spring Boot 的日志系统抽象目标:

✔️ 极早期可用 :SpringApplication 启动前就能生效

✔️ 自动探测 :按 classpath 自动选择 logback、log4j2、jul

✔️ 可插拔 :支持外部定制实现

✔️ 统一配置:application.properties / yaml

核心抽象:

复制代码
public abstract class LoggingSystem {
    public abstract void beforeInitialize();
    public abstract void initialize(
        LoggingInitializationContext context,
        String configLocation,
        LogFile logFile
    );
    public abstract void setLogLevel(String loggerName, LogLevel level);
}

✅ 2️⃣ LoggingSystem 加载机制(源码细节)

🌟 关键点:不是 SPI,不是 spring.factories

它用的是 硬编码 Map + classpath 探测,简单高效。


源码

复制代码
	private static final Map<String, String> SYSTEMS;

	static {
		Map<String, String> systems = new LinkedHashMap<>();
		systems.put("ch.qos.logback.classic.LoggerContext",
				"org.springframework.boot.logging.logback.LogbackLoggingSystem");
		systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory",
				"org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
		systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem");
		SYSTEMS = Collections.unmodifiableMap(systems);
	}

重点方法:get()

复制代码
	/**
	 * Detect and return the logging system in use. Supports Logback and Java Logging.
	 * @param classLoader the classloader
	 * @return the logging system
	 */
	public static LoggingSystem get(ClassLoader classLoader) {
		String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
		if (StringUtils.hasLength(loggingSystem)) {
			if (NONE.equals(loggingSystem)) {
				return new NoOpLoggingSystem();
			}
			return get(classLoader, loggingSystem);
		}
		return SYSTEMS.entrySet().stream().filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader))
				.map((entry) -> get(classLoader, entry.getValue())).findFirst()
				.orElseThrow(() -> new IllegalStateException("No suitable logging system located"));
	}

✅ 加载流程:

1️⃣ -Dorg.springframework.boot.logging.LoggingSystem=xxx

→ 强制指定实现

2️⃣ -Dorg.springframework.boot.logging.LoggingSystem=none

→ 关闭日志

3️⃣ 否则

→ 遍历 SYSTEMS

→ classpath 上探测第一个可用实现


✔️ 例子:

  • 如果存在 org.apache.logging.log4j.core.impl.Log4jContextFactory → 选用 log4j2

✅ 3️⃣ LoggingApplicationListener 启动流程

🌟 日志系统是 Spring Boot 的「超早期」监听器。


🔎 注册

SpringApplication 里有:

private void configureInitialListeners() {

this.listeners.add(new LoggingApplicationListener());

}

✔️ LoggingApplicationListener 是核心入口。


🔎 关键事件

复制代码
	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationStartingEvent) {
			onApplicationStartingEvent((ApplicationStartingEvent) event);
		}
		else if (event instanceof ApplicationEnvironmentPreparedEvent) {
			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();
		}
	}

🌟 重点看前两个阶段:

✅ 1️⃣ ApplicationStartingEvent

  • 最早期调用

  • disable JUL bridge、打印 Spring Boot banner

✅ 2️⃣ ApplicationEnvironmentPreparedEvent

  • 环境准备好(可读取 application.properties)

  • 真正初始化日志配置

  • 加载 LoggingSystem


🔎 关键调用

复制代码
	private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
		if (this.loggingSystem == null) {
			this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
		}
		initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
	}

✔️ 这里就是调用我们上面说的 LoggingSystem.get()

✔️ 最终会初始化 Log4J2LoggingSystem


✅ 4️⃣ Log4J2LoggingSystem 主要源码

Spring Boot 2.x 的 log4j2 实现类是:

org.springframework.boot.logging.log4j2.Log4J2LoggingSystem


🔎 继承

public class Log4J2LoggingSystem extends AbstractLoggingSystem

✔️ 抽象父类里封装了很多通用工具

✔️ 子类只需实现 log4j2-specific 部分


🔎 核心方法:initialize

复制代码
	@Override
	public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
		if (StringUtils.hasLength(configLocation)) {
			initializeWithSpecificConfig(initializationContext, configLocation, logFile);
			return;
		}
		initializeWithConventions(initializationContext, logFile);
	}



	private void initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) {
		String config = getSelfInitializationConfig();
		if (config != null && logFile == null) {
			// self initialization has occurred, reinitialize in case of property changes
			reinitialize(initializationContext);
			return;
		}
		if (config == null) {
			config = getSpringInitializationConfig();
		}
		if (config != null) {
			loadConfiguration(initializationContext, config, logFile);
			return;
		}
		loadDefaults(initializationContext, logFile);
	}

✅ 逻辑分支:

是否指定配置文件 configLocation ?

├─ 是 → 初始化用户指定配置 (initializeWithSpecificConfig)

└─ 否 →

是否有 Log4j2 自己的初始化配置且无文件日志?

├─ 是 → 重新初始化 (reinitialize)

└─ 否 →

是否存在 Spring Boot 约定配置文件?

├─ 是 → 加载约定配置 (loadConfiguration)

└─ 否 → 加载默认内置配置 (loadDefaults)


🔎 reinitialize

复制代码
@Override
protected void reinitialize(LoggingInitializationContext initializationContext) {
    getLoggerContext().reconfigure();
}

✔️ 这里调用的是 log4j2 的官方 API

✔️ 支持热加载配置: reconfigure 会重新读取系统属性 log4j.configurationFile 指定的配置文件(或默认配置),完成配置热刷新.


🔎 默认配置优先级

复制代码
	@Override
	protected String[] getStandardConfigLocations() {
		return getCurrentlySupportedConfigLocations();
	}

	private String[] getCurrentlySupportedConfigLocations() {
		List<String> supportedConfigLocations = new ArrayList<>();
		addTestFiles(supportedConfigLocations);
		supportedConfigLocations.add("log4j2.properties");
		if (isClassAvailable("com.fasterxml.jackson.dataformat.yaml.YAMLParser")) {
			Collections.addAll(supportedConfigLocations, "log4j2.yaml", "log4j2.yml");
		}
		if (isClassAvailable("com.fasterxml.jackson.databind.ObjectMapper")) {
			Collections.addAll(supportedConfigLocations, "log4j2.json", "log4j2.jsn");
		}
		supportedConfigLocations.add("log4j2.xml");
		return StringUtils.toStringArray(supportedConfigLocations);
	}

Spring Boot 2.x 官方日志文件查找顺序说明:

By default, Spring Boot looks for a configuration file in the following locations (in order):

  • log4j2-test.properties (classpath root)

  • log4j2-test.xml (classpath root)

  • log4j2.properties (classpath root)

  • log4j2.xml (classpath root)

  • log4j2.yaml / log4j2.yml (if Jackson YAML is on classpath)

  • log4j2.json / log4j2.jsn (if Jackson is on classpath)

官方文档链接(Spring Boot 2.7 示例):


✅ 5️⃣ logging.config、log4j2-spring.xml 加载逻辑

✔️ 在 application.properties 中配置:

复制代码
logging.config=classpath:log4j2-spring.xml 

→ 会直接调用 reinitialize


✔️ 如果没指定

→ Spring Boot 会去 classpath 里按上面优先顺序找

→ 第一个找到的就加载


✔️ 推荐使用

log4j2-spring.xml

✅ 支持 spring profiles

✅ 支持占位符解析

✅ Spring 官方推荐


✅ 6️⃣ 动态日志级别设置(Actuator)

Spring Boot 2.x 的 Actuator 里有 endpoint:

POST /actuator/loggers/{loggerName} { "configuredLevel": "DEBUG" }


🌟 对应源码

复制代码
	@Override
	public void setLogLevel(String loggerName, LogLevel logLevel) {
		Level level = LEVELS.convertSystemToNative(logLevel);
		LoggerConfig logger = getLogger(loggerName);
		if (logger == null) {
			logger = new LoggerConfig(loggerName, level, true);
			getLoggerContext().getConfiguration().addLogger(loggerName, logger);
		}
		else {
			logger.setLevel(level);
		}
		getLoggerContext().updateLoggers();
	}

✔️ 通过 log4j2 API

✔️ 实现在线动态刷新

spring boot actuator 入门(1)-CSDN博客


Spring Boot 2.x 关于 logging 的常用配置参数及说明:

配置参数 类型 默认值 说明 备注
logging.config String 指定自定义日志配置文件的位置(支持classpath:和file:) 优先级最高,覆盖默认配置
logging.level.<logger> String INFO(root logger默认) 设置指定包或类的日志级别,如logging.level.org.springframework=DEBUG 可为具体类或包名
logging.file String 指定日志文件名称(已过时,建议用logging.file.name 兼容旧版
logging.path String 指定日志文件目录(已过时,建议用logging.file.path) 兼容旧版
logging.file.name String 指定日志文件的完整路径及名称 推荐使用
logging.file.path String 指定日志文件目录 Spring Boot 会在该目录生成默认文件名日志
logging.pattern.console String Spring Boot 默认格式 控制台日志输出格式 不同日志实现支持程度不一
logging.pattern.file String Spring Boot 默认格式 文件日志输出格式
logging.pattern.level String %5p 日志级别输出格式
logging.pattern.dateformat String yyyy-MM-dd HH:mm:ss.SSS 日志时间戳格式
logging.exception-conversion-word String %wEx 异常堆栈打印格式
logging.logback.rollingpolicy.file-name-pattern String Logback文件滚动策略配置文件名格式 仅对Logback有效
logging.logback.rollingpolicy.clean-history-on-start Boolean false 是否启动时清理旧日志文件 仅对Logback有效
logging.log4j2.context-selector String Log4j2上下文选择器类名 仅对Log4j2有效
logging.log4j2.config.location String Log4j2配置文件路径 仅对Log4j2有效
logging.log4j2.shutdown.timeout Duration/String 30s Log4j2关闭等待超时时间 仅对Log4j2有效
logging.level.root String INFO 根日志级别 常用配置

Spring Boot 2.7.x 官方参考手册 --- Logging


✅ 总结

✅ Spring Boot 2.x 的日志系统设计非常实用:

  • 超早期初始化

  • 自动探测实现

  • 支持外部配置和动态刷新

✅ 以 log4j2 为例:

  • 实现类 Log4J2LoggingSystem

  • 支持 log4j2-spring.xml

  • Actuator 动态管理

相关推荐
月堂4 分钟前
【无标题】
java·linux·windows
remCoding14 分钟前
Java大厂面试实录:从Spring Boot到AI大模型的深度技术拷问
java·spring boot·redis·spring cloud·ai·kafka·microservices
天天摸鱼的java工程师16 分钟前
扫码登录设计原理:扫码一笑,身份到手!
java·后端·面试
[听得时光枕水眠]35 分钟前
Spring Security 实践及源码学习
数据库·学习·spring
A了LONE37 分钟前
自定义btn按钮
java·前端·javascript
Dcs42 分钟前
Devstral Small 1.1 本地部署实战指南:打造你的专属 AI 编程助手
java
SimonKing43 分钟前
FastExcel:革新Java生态的高性能Excel处理引擎
java·后端·程序员
Code blocks1 小时前
Shiro实现多级权限的分页查询
java·spring boot·spring
bing_1581 小时前
Spring Boot 项目中数据同步之binlog和MQ
数据库·spring boot·数据同步
AirMan1 小时前
Park 打断大反转!一次 park 不阻塞,参数化日志竟成幕后黑手?
java·后端