本文记录 SpringBoot 与 Logback 是如何工作的,即记录 SpringBoot 中 Logback 是怎么一步一步初始化的。用以测试的 SpringBoot 版本是 1.5.16, 而非最新的 SpringBoot 2。关于 SpringBoot 日志的官方文档在 Logging , 但不太详细或透彻。本文不承诺说理解得更有深度,只是为官方文档提供更多方面的参考。
SpringBoot 默认使用 Slf4J + Logback 来记录日志,对于一个基本的依赖于
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
的项目,它依赖了 spring-boot-starter-logging 组件,而该组件引入了以下几个依赖
相当于把其他的日志框架全桥接到了 Slf4J + Logback 上去了。
那么 SpringBoot Web 项目是怎么样子的呢?spring-boot-starter-web 依赖于 spring-boot-starter,所以日志框架选用上就没有一点区别了。
从一个最简单的 SpringBoot 应用程序来感受它的配置日志输出,一个 Maven 项目,最基本的配置是
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.16.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> </dependencies>
注意: spring-boot-starter-parent 的 pom.xml 文件值得瞧一瞧的。
application.properties 文件为空,并且没有任何的 logback 配置文件在 resources 目录中。
来个最简单的程序
@SpringBootApplication
public class Application {
private static final Logger logger = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
logger.info("aaa");
SpringApplication.run(Application.class, args);
logger.info("bbb");
}
}
在 SpringApplication.run(...) 前后各调用 logger.info(...) 输出信息
前面 logger.info("aaa") 和 logger.info("bbb") 的输出用红线标示出来了,可以非常感性的认识到
在 Logging 一文中提到了 Spring Boot 有一个 LoggingSystem 抽象来负责配置日志系统,并且 Logback 是首选。 LoggingSystem 是一个抽象类,它的实现层次如下
既然说 Logback 是首先,那么 SpringBoot 最终是要用到 LogbackLoggingSystem 这个类的,那我们从源代码跟踪一下 SpringBoot 的 Spring 上下文是如何与 Logback 衔接起来的。
能与 Spring 上下文进行交互的一般来说是 ApplicationEvent, 这里是 org.springframework.boot.logging.LoggingApplicationListener , 它实现了 ApplicationListener , 看 LoggingApplicationListener 的事件方法
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationStartingEvent) {
onApplicationStartingEvent((ApplicationStartingEvent) event); // #1
}
else if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent( // #2
(ApplicationEnvironmentPreparedEvent) event);
}
else if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event); // #3
}
......
#1 找到 LoggingSystem 的实现类,在 LoggingSystem.get(classloader) 方法中,如果配置了系统属性 org.springframework.boot.logging.LoggingSystem 对应的实现类的话,就用它,指定为 none 值就用 NoOpLogginSystem 实现,即没有任何日志输出。如果没有指定 org.springframework.boot.logging.LoggingSystem 系统属性, LoggingSystem 则尝试以下的顺序找实现类
而显然 LogbackLoggingSystem 对应的类 ch.qos.logback.core.Appender 是存在于 springboot starter 中的,所以在 #1 中可以确定是用 LogbackLoggingSystem 实现
#3 先说这最后一步,如果初始化好,把 LoggingSystem 的实例(此处为 LogbackLoggingSystem 实例) 注册名为 springBootLogginSystem 的 Spring Bean
#2 对日志进行配置,具体实现在 LoggingApplicationListener.initialize(environment, classLoader) 和 LogbackLoggingSystem.initialize(...) 方法中。不列出实际代码来了,只解翻译一下过程
logging.file 和 logging.path 值,如果有的话,分别映射为 LOG_FILE 和 LOG_PATH 系统属性值,这可以在 logback.xml 的配置中用 ${LOG_FILE} 引用到 debug=true 则为 LogLevel.DEBUG, 如果配置了 trace=true 则为 LogLevel.TRACE。后面还会专为某些包预设一些日志级别,并且最后的日志级别可在 Spring 属性中用 logging.level.logger_name=DEBUG 来配置,如 logging.level.org.springframework=DEBUG logging.config 指定了配置文件,则使用该配置文件初始化 Logback 的 LoggerFactory,否则 LogbackLoggingSystem 将会以下面的顺序来查找 Logback 配置文件 logback-test.groovy, logback-test.xml, logback.groovy, logback.xml
logback-test-spring.xml, logback-test-spring.xml, logback-spring.groovy, logback-spring.xml (AbstractLoggingSystem.getSpringConfigLocations() 方法)
注意:SpringBoot 会忽略掉普通 Logback 应用的系统属性 logback.configurationFile 设定配置文件的方法
if (StringUtils.hasText(System.getProperty(CONFIGURATION_FILE_PROPERTY))) {
getLogger(LogbackLoggingSystem.class.getName()).warn(
"Ignoring '" + CONFIGURATION_FILE_PROPERTY + "' system property. "
+ "Please use 'logging.config' instead.");
}
LogbackLoggingSystem.loadDefaults(initializationContext, logFile) 来配置默认的日志。 这块其实是上面步骤 #2 中的一个子步骤,因其重要才将其单独列出,来看看 SpringBoot 在没有加载到任何的配置文件时如何配置默认 Logback 的 LoggerFactory。入口就是 LogbackLoggingSystem.loadDefaults(initializationContext, logFile) 。
首先,默认显示日志级别的格式是: ${logging.pattern.level:${LOG_LEVEL_PATTERN:%5p}} , 可用 Spring 属性 logging.pattern.level 或系统属性 LOG_LEVEL_PATTERN ,默认为 %5p 。
其他的默认配置就要参考类 org.springframework.boot.logging.logback.DefaultLogbackConfiguation
不管有没有配置 logging.file 或 logging.path ,SpringBoot 都会初始化 consoleAppender, 并且默认的输出模式是
private static final String CONSOLE_LOG_PATTERN = "%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} "
+ "%clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} "
+ "%clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} "
+ "%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}";
该模式可用 Spring 属性 logging.pattern.console 进行覆盖设置。注意,SpringBoot 还为我们定义了 clr , wEx 这两个 Converter 。
如果配置了 Spring 属性 logging.file 和 logging.path 其中一个或两个,就会在 consoleAppender 的基础上再加一个 fileAppender , 它的输出模式是
private static final String FILE_LOG_PATTERN = "%d{yyyy-MM-dd HH:mm:ss.SSS} "
+ "${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}";
该模式可以用 Spring 属性 logging.pattern.file 进行覆盖。
日志文件是 10MB 大小不断滚动的,不会删除旧文件。
日志文件路径如何决定的
logging.file , 就是 logging.file 所指定的文件,文件名可以是绝对文件路径,或者相对路径 logging.path , 那么日志文件是 logging.path 下的 spring.log 文件 logging.file 是有用的,与 #1 同 无论是对于 consoleAppender 还是 fileAppender , 都是设置 INFO 为默认日志级别,并且预设了一些 logger 的日志输出级别。
理解了 SpringBoot 是如何初始化 Logback 日志配置后,我们来看一下项目中几种最简的日志配置方式。
依据 SpringBoot 加载 Logback 配置文件的顺序,我们可以在 classpath 下放 logback-spring.xml 或 logback.xml , 注意是 logback.xml 被优先选择。内容如下
<?xml version="1.0" encoding="UTF-8"?> <configuration> <include resource="org/springframework/boot/logging/logback/base.xml"/> <logger name="org.springframework.web" level="DEBUG"/> </configuration>
设置 Spring 属性 logging.file 或系统属性 LOG_FILE 来指定日志输出文件名。或者用 Spring 属性 logging.path 或系统属性 LOG_PATH 指定 spring.log 的路径。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}"/>
<include resource="org/springframework/boot/logging/logback/file-appender.xml" />
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</configuration>
用上面相同的方式指定日志文件的路径。
就更简单了,可以什么配置文件也不要,并且不要配置 logging.file 和 logging.path 。而且效果与下面的配置是一样的。
<?xml version="1.0" encoding="UTF-8"?> <configuration> <include resource="org/springframework/boot/logging/logback/defaults.xml" /> <include resource="org/springframework/boot/logging/logback/console-appender.xml" /> <root level="INFO"> <appender-ref ref="CONSOLE" /> </root> </configuration>
logging.file , logging.path 或系统属性 LOG_FILE , LOG_PATH 的情况下只会输出日志到控制台 logging.pattern.file 等 logging.config 来指定外部 logback 配置文件,但忽略 Logback 默认用系统属性 logback.configurationFile 指定配置文件的方式 abc ,有四种方式:1) application.properties 文件中的 abc=xxx , 2)环境变量 export abc=xxx , 3) 启动参数 --abc=xxx ,4) 系统属性 -Dabc=xxx logback-spring.xml 或 logback.xml ,但是 logback.xml 优先加载,并未遵循先特殊再普通的原则。(Logback 1.3.0 之后由于支持 Java 9,但是 Groovy 与 Java 9 未处理好关系,所以 Logback 1.3.0 不能支持 .groovy 的配置文件) logback.xml 或 logback-spring.xml 配置文件,二选一了。我没发现这两个文件有什么不同。