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 动态管理

相关推荐
m0_587383001 小时前
广州 24 小时自助健身房解决方案技术实现与实战指南
java·spring·小程序·架构·需求分析
·云扬·2 小时前
Prompt 工程与安全:从 System 设计到注入防御
java·人工智能·安全·ai·prompt
代码不停3 小时前
自动化测试(java)
java·开发语言·selenium·自动化
唠点键盘之外的3 小时前
26 向量数据库实战:从 0 搭一条能用的检索链路(pgvector + Spring AI)
数据库·人工智能·spring
鬼手点金4 小时前
opencode-rules 插件
java·开发语言·前端·人工智能·chrome·git·github
paopaokaka_luck5 小时前
滑雪场管理系统(AI智能助手流式对话,ECharts 运营数据分析、教练、雪具与场地一体化预约,钱包支付退款、订单核销与服务评价)
java·人工智能·spring boot·网络协议·数据分析·echarts
SL_staff5 小时前
JVS混合架构实践:能力引擎+动态模型如何支撑企业级可持续交付
spring boot·低代码·全栈
迪飞特科技6 小时前
智慧物业平台实战:Spring Boot 整合小程序实现报修巡检费用全流程
spring boot·后端·小程序
考虑考虑6 小时前
@JsonInclude注解使用
spring boot·后端·spring
Wang's Blog6 小时前
Java 项目实战: 交友项目 - Dubbo 的内部结构与一次调用的完整链路
java·dubbo