概述
日志是一个系统必不可缺少的东西,记录了系统运行时的点点滴滴,便于我们了解自己系统的运行状态,在我们使用 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 事件监听器,如下:
# Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.ClearCachesApplicationListener,\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ org.springframework.boot.context.config.ConfigFileApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\ org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\ org.springframework.boot.context.logging.LoggingApplicationListener,\ org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
其中有一个 LoggingApplicationListener
对象,监听到不同事件后,会对日志系统进行一些相关的初始化工作
提示:Spring Boot 的 LoggingSystem 日志系统的初始化过程有点绕,嵌套的方法有点多,可参考序号耐心查看
LoggingApplicationListener
org.springframework.boot.context.logging.LoggingApplicationListener
,Spring Boot 事件监听器,用于初始化日志系统
onApplicationEvent 方法
onApplicationEvent(ApplicationEvent
方法,处理监听到的事件
@Override public void onApplicationEvent(ApplicationEvent event) { // 应用正在启动的事件 if (event instanceof ApplicationStartingEvent) { onApplicationStartingEvent((ApplicationStartingEvent) event); } // Environment 环境已准备事件 else if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); } // 应用已准备事件 else if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent((ApplicationPreparedEvent) event); } // Spring 上下文关闭事件 else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event).getApplicationContext().getParent() == null) { onContextClosedEvent(); } // 应用启动失败事件 else if (event instanceof ApplicationFailedEvent) { onApplicationFailedEvent(); } }
对于不同的事件调用不同的方法,事件的发布顺序也就是上面从上往下的顺序
1. onApplicationStartingEvent 方法
处理应用正在启动的事件
private void onApplicationStartingEvent(ApplicationStartingEvent event) { // <1> 创建 LoggingSystem 对象 // 指定了类型则使用指定的,没有则尝试创建对应的对象,ClassLoader 中有对应的 Class 对象则创建(logback > log4j2 > java logging) this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader()); // <2> LoggingSystem 的初始化前置处理 this.loggingSystem.beforeInitialize(); }
过程如下:
- 创建 LoggingSystem 对象,指定了类型则使用指定的,没有则尝试创建对应的对象,ClassLoader 中有对应的 Class 对象则创建(
logback
>log4j2
>java
logging
) - 调用 LoggingSystem 的
beforeInitialize()
方法,初始化前置处理
2. onApplicationEnvironmentPreparedEvent 方法
处理环境已准备事件
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) { // <1> 如果还未明确 LoggingSystem 类型,那么这里继续创建 LoggingSystem 对象 if (this.loggingSystem == null) { this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader()); } // <2> 初始化 LoggingSystem 对象,创建日志文件,设置日志级别 initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader()); }
过程如下:
- 如果还未明确 LoggingSystem 类型,那么这里继续创建 LoggingSystem 对象
- 调