Spring Boot 源码分析 - 日志系统
参考 知识星球 中 芋道源码 星球的源码解析,一个活跃度非常高的 Java 技术社群,感兴趣的小伙伴可以加入 芋道源码 星球,一起学习😄
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读
Spring Boot 版本:2.2.x
最好对 Spring 源码有一定的了解,可以先查看我的 《死磕 Spring 之 IoC 篇 - 文章导读》 系列文章
如果该篇内容对您有帮助,麻烦点击一下“推荐”,也可以关注博主,感激不尽~
该系列其他文章请查看:《精尽 Spring Boot 源码分析 - 文章导读》
概述
日志是一个系统必不可缺少的东西,记录了系统运行时的点点滴滴,便于我们了解自己系统的运行状态,在我们使用 Spring Boot 时,默认就已经提供了日志功能,使用 Logback 作为默认的日志框架。那么,接下来我们依赖来看看 Spring Boot 是如何初始化好日志系统的。
为什么 Spring Boot 默认的日志框架是 Logbasck 呢?
因为在 spring-boot-starter
模块中引入 spring-boot-starter-logging
模块,该 Starter 引入了 logback-classic
依赖。
Log 日志体系
在我们的日常工作中,可能看到项目中依赖的跟日志相关的 jar
包有很多,例如 commons-logging
、log4j
、log4j2
、sl4j
和 logback
等等,眼花缭乱。经常会碰到各种依赖冲入的问题,非常烦恼,例如这几个问题:
- Failed to load class org.slf4j.impl.StaticLoggerBinder,没找到日志实现,如果你觉得你已经添加了对应的日志实现依赖了,那应该检查一下版本是否兼容
- Multiple bindings,找到了多个日志实现,也可能是版本问题,
slf4j
会找其中一个作为日志实现
如果想要正确地使用它们,有必要先理清它们之间的关系,我们可以来看看 Log 的发展史,首先从 Java Log 的发展历程开始说起:
log4j
(作者Ceki Gülcü)出来后,被开发者们广泛的应用(注意,这里是直接使用),当初是 Java 日志事实上的标准,并成为了 Apache 的项目- Apache 要求把
log4j
并入到 jdk,SUN 表示拒绝,并在 jdk1.4 版本后增加了JUL
(java.util.logging
); - 毕竟是 JDK 自带的,
JUL
也被很多人使用。同时还有其他的日志组件,如 SimpleLog 等。这个时候如果有人想换成其他日志组件,如log4j
换成JUL
,因为 API 完全不同,就需要改动代码,当然很多人不愿意呀; - Apache 见此,开发了
JCL
(Jakarta Commons Logging),即commons-logging-xx.jar
。它只提供一套通用的日志接口 API,并不提供日志的实现。很好的设计原则嘛,依赖抽象而非实现。这样一来,我们的应用程序可以在运行时选择自己想要的日志实现组件; - 这样看上去也挺美好的,但是
log4j
的作者觉得JCL
不好用,自己开发出一套slf4j
,它跟JCL
类似,本身不替供日志的具体实现,只对外提供接口或门面。目的就是为了替代JCL
。同时,还开发出logback
,一个比log4j
拥有更高性能的组件,目的是为了替代log4j
; - Apache 参考了
logback
,并做了一系列优化,推出了一套log4j2
日志框架。
对于性能没什么特别高要求的使用 Spring Boot 中默认的 logback
就可以了,如果想要使用 log4j2
可以参考我的 《MyBatis 使用手册》 这篇文章,有提到过。
回顾
回到前面的 《SpringApplication 启动类的启动过程》 这篇文章,Spring Boot 启动应用的入口和主流程都是在 SpringApplication#run(String.. args)
方法中。
在启动 Spring 应用的整个过程中,到了不同的阶段会发布不同类型的事件,例如最开始会发布一个 应用正在启动 的事件,对于不同类型的事件都是通过 EventPublishingRunListener
事件发布器来发布,里面有一个事件广播器,封装了几个 ApplicationListener 事件监听器,如下:
其中有一个 LoggingApplicationListener
对象,监听到不同事件后,会对日志系统进行一些相关的初始化工作
提示:Spring Boot 的 LoggingSystem 日志系统的初始化过程有点绕,嵌套的方法有点多,可参考序号耐心查看
LoggingApplicationListener
org.springframework.boot.context.logging.LoggingApplicationListener
,Spring Boot 事件监听器,用于初始化日志系统
onApplicationEvent 方法
onApplicationEvent(ApplicationEvent
方法,处理监听到的事件
对于不同的事件调用不同的方法,事件的发布顺序也就是上面从上往下的顺序
1. onApplicationStartingEvent 方法
处理应用正在启动的事件
过程如下:
- 创建 LoggingSystem 对象,指定了类型则使用指定的,没有则尝试创建对应的对象,ClassLoader 中有对应的 Class 对象则创建(
logback
>log4j2
>java
logging
) - 调用 LoggingSystem 的
beforeInitialize()
方法,初始化前置处理
2. onApplicationEnvironmentPreparedEvent 方法
处理环境已准备事件
过程如下:
- 如果还未明确 LoggingSystem 类型,那么这里继续创建 LoggingSystem 对象
- 调用
initialize(..)
方法,初始化 LoggingSystem 对象,创建日志文件,设置日志级别
3. initialize 方法
初始过程如下:
-
根据 Environment 环境通过 LoggingSystemProperties 往 System 进行一些日志配置
-
根据 Environment 环境配置的日志名称和路径创建一个日志文件,默认情况没有配置,这个对象也为
null
,而是在打印第一个日志的时候会创建(如果不存在的话) -
往 System 添加日志文件的名称和路径
-
创建一个日志分组对象
-
初始化早期的 Spring Boot 日志级别(Debug 或者 Trace)
-
初始化 LoggingSystem 对象
-
初始化最终的 Spring Boot 日志级别,逐个设置 Environment 配置的日志级别
-
向 JVM 注册一个钩子,用于在 JVM 关闭时关闭日志系统
可以看到需要通过 LoggingSystem 日志系统对象来初始化,后面会讲到
4. onApplicationPreparedEvent 方法
处理应用已准备事件
LoggingSystem
org.springframework.boot.logging.LoggingSystem
抽象类,Spring Boot 的日志系统对象,每个日志框架,都会对应一个实现类。如下图所示:

1.1 get 方法
创建一个 LoggingSystem 日志系统对象,如下:
过程如下:
- 从系统参数
org.springframework.boot.logging.LoggingSystem
获得 LoggingSystem 类型 - 如果非空,说明配置了,那么创建一个该类型的 LoggingSystem 实例对象
- 否则,没有配置,则通过顺序依次尝试创建对应类型的 LoggingSystem 实例对象,也就是在
static
代码块中初始化好的集合,logback > log4j2 > java logging
1.2 beforeInitialize 方法
初始化的前置操作,抽象方法,交由子类实现
2. initialize 方法
初始化操作,空方法,由子类来重写
AbstractLoggingSystem
org.springframework.boot.logging.AbstractLoggingSystem
抽象类,继承 LoggingSystem 抽象类,作为一个基类
2.1 initialize 方法
重写父类的 initialize(..)
方法,提供模板化的初始化逻辑,如下:
有指定的配置文件,则调用 initializeWithSpecificConfig(..)
方法, 使用指定配置文件进行初始化
没有自定义的配置文件,则调用 initializeWithConventions(..)
方法,使用约定配置文件进行初始化
2.1.1 initializeWithSpecificConfig 方法
initializeWithSpecificConfig(LoggingInitializationContext, String, LogFile)
方法,使用指定配置文件进行初始化
先获取配置文件的路径(可能有占位符),然后调用 loadConfiguration(..)
抽象方法,加载配置文件到日志系统中
2.1.2 initializeWithConventions 方法
initializeWithConventions(LoggingInitializationContext, LogFile)
方法,使用约定配置文件进行初始化
过程如下
-
调用
getSelfInitializationConfig()
方法,尝试获得约定配置文件,例如 log4j2 约定的是 log4j2.xml -
如果找到了约定的配置文件,则调用
reinitialize(..)
抽象方法,自定义初始化,子类实现 -
调用
getSpringInitializationConfig(..)
方法,尝试获取约定的配置文件(带有-spring
),例如 log4j2 对应是 log4j2-spring.xml -
获取到了
-spring
配置文件,则调用loadConfiguration(..)
抽象方法,加载到日志系统中,子类实现 -
还没有找到到指定的配置文件,那么调用
loadDefaults(..)
抽象方法,加载默认配置,子类实现
整个过程就是尝试获取到各个日志框架约定好的配置文件名称,如果存在这个配置文件,则加载到日志系统中,否则使用默认的配置
Slf4JLoggingSystem
org.springframework.boot.logging.Slf4JLoggingSystem
,继承 AbstractLoggingSystem 抽象类,基于 Slf4J 的 LoggingSystem 的抽象基类
1.2.1 beforeInitialize 方法
初始化的前置操作
先调用父类的 beforeInitialize()
方法,然后调用 configureJdkLoggingBridgeHandler()
方法,配置 JUL
的桥接处理器,桥接到 slf4j
过程如下:
-
判断
JUL
是否桥接到slf4j
了 -
移除 JUL 桥接处理器
-
重新安装 SLF4JBridgeHandler
2.3 loadConfiguration 方法
重写 AbstractLoggingSystem 父类的方法,加载指定的日志配置文件到日志系统中
实际上就是将 Environment 中的日志配置往 System 中配置
LogbackLoggingSystem
org.springframework.boot.logging.logback.LogbackLoggingSystem
,继承 Slf4JLoggingSystem 抽象类,基于 logback
的 LoggingSystem 实现类
1.2.2 beforeInitialize 方法
重写 LoggingSystem 的方法,初始化前置操作
过程如下:
-
调用
getLoggerContext()
方法,获得 LoggerContext 日志上下文 -
如果 LoggerContext 已有 LoggingSystem,表示已经初始化,则直接返回
-
调用父方法
-
添加 FILTER 到其中,因为还未初始化,不打印日志
getStandardConfigLocations 方法
重写 AbstractLoggingSystem 的方法,获取 logback 标准的配置文件名称
2.2 initialize 方法
重写 LoggingSystem 的方法,初始化操作
过程如下:
-
调用
getLoggerContext()
方法,获得 LoggerContext 日志上下文 -
如果 LoggerContext 已有 LoggingSystem,表示已经初始化,则直接返回
-
调用父方法
-
移除之前添加的 FILTER,可以开始打印日志了
-
调用
markAsInitialized(..)
方法,标记为已初始化,往 LoggerContext 中添加一个 LoggingSystem 对象
2.4 loadConfiguration 方法
重写 AbstractLoggingSystem 的方法,加载指定的日志配置文件到日志系统中
过程如下:
-
调用父方法
-
重置 LoggerContext 对象,这里会添加一个 LevelChangePropagator 监听器,当日志级别被修改时会立即生效,而不用重启应用
-
读取配置文件并解析,配置到 LoggerContext 中
-
判断是否发生错误,有的话抛出 IllegalStateException 异常
reinitialize 方法
实现类 AbstractLoggingSystem 的方法,重新初始化
loadDefaults 方法
实现类 AbstractLoggingSystem 的方法,没有指定的配置文件,也没有约定的配置文件,那么加载默认的配置到日志系统
过程如下:
-
重置 LoggerContext 对象,这里会添加一个 LevelChangePropagator 监听器,当日志级别被修改时会立即生效,而不用重启应用
-
如果开启 debug 模式则添加一个 OnConsoleStatusListener 监听器
-
往 LoggerContext 中添加默认的日志配置
-
创建 DefaultLogbackConfiguration 对象,设置到
configurator
中,设置转换规则,例如颜色转换,空格转换 -
设置日志文件,按天切割
Log4J2LoggingSystem
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem
,继承 Slf4JLoggingSystem 抽象类,基于 log4j2
的 LoggingSystem 实现类
和 LogbackLoggingSystem
基本类似,感兴趣的小伙伴可以自己去瞧一瞧
JavaLoggingSystem
org.springframework.boot.logging.java.JavaLoggingSystem
,继承 AbstractLoggingSystem 抽象类,基于 jul
的 LoggingSystem 实现类
逻辑比较简单,感兴趣的小伙伴可以自己去瞧一瞧
总结
本文分析了 Sping Boot 初始化不同 LoggingSystem 日志系统的一个过程,同样是借助于 Spring 的 ApplicationListener 事件监听器机制,在启动 Spring 应用的过程中,例如会广播 应用正在启动的事件 和 应用环境已准备好,然后 LoggingApplicationListener
监听到不同的事件会进行不同的初始化操作。
LoggingSystem 日志系统主要分为 logback
、log4j2
和 JUL
三种,本文主要对 logback
的初始化过程进行了分析,因为它是 Spring Boot 的默认日志框架嘛。整个的初始化过程稍微有点绕,嵌套的方法有点多,主要的小节都标注了序号。
大致流程就是先配置 JUL
到 slf4j
的桥接器,然后尝试找到指定的配置文件对日志系统进行配置,可通过 logging.config
设置;没有指定则获取约定好的配置文件,例如 logback.xml
、log4j2.xml
;还没有获取到则 Spring 约定好的配置文件,例如 logback-spring.xml
、log4j2-spring.xml
;要是还没有找到配置文件,那只能尝试加载默认的配置了。
__EOF__

本文链接:https://www.cnblogs.com/lifullmoon/p/14957824.html
关于博主:本着学习与分享的目的,将持续不断的进行知识分享。望各位到访看客如有喜欢的文章,可以点击一下“推荐”,若有不同建议或者意见,也请不吝赐教,博主感激不尽。另外,欢迎转载博主的文章,请务必依据文章下方的版权声明转载。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义