本文记录 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 项目,上面组件依赖了 spring-boot-starter-logging 组件,而该组件又引入了以下几个依赖
- logback-classic: 依赖了 Slf4J
- jcl-over-slf4j
- jul-to-slf4j
- log4j-over-slf4j
相当于把其他的日志框架全桥接到了 Slf4J + Logback 上去了。
那么 Spring Boot Web 项目是怎么样子的呢?spring-boot-starter-web 依赖于 spring-boot-starter,所以日志框架选用上就没有一点区别了。
Spring Boot 应用默认日志输出
从一个最简单的 Spring Boot 应用程序来感受它的配置日志输出,一个 Maven 项目,最基本的配置是
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | <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 目录中时,
来个最简单的程序
| 1 2 3 4 5 6 7 8 9 10 | @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") 的输出用红线标示出来了,可以非常感性的认识到
- 它们输出样式不同,所以使用了不同的日志配置
- logger.info("aaa") 发生在 Spring 上下文初始化之前,以 Logback 的默认行为初始化的 LoggerFactory
- logger.info("bbb") 发生在 Spring 上下文初始化之后,因此 Spring Boot 又重新配置了 Logback 的 LoggerFactory
- #2 不是我们这里探讨的范畴,它和普通 Java 应用使用 Logback 是一致的,主要看 #3 怎么来的,并且默认的 Log 是怎么样的配置
Spring Boot 是如何初始化 Logback 的
在 Logging 一文中提到了 Spring Boot 有一个 LoggingSystem 抽象来负责配置日志系统,并且 Logback 是首选。LoggingSystem 是一个抽象类,它的实现层次如下
 既然说 Logback 是首选,那么 Spring Boot 最终是要用到
既然说 Logback 是首选,那么 Spring Boot 最终是要用到 LogbackLoggingSystem 这个类的,那我们从源代码跟踪一下 Spring Boot 的 Spring 上下文是如何与 Logback 衔接起来的。
能与 Spring 上下文进行交互的一般来说是 ApplicationEvent, 这里是 org.springframework.boot.logging.LoggingApplicationListener, 它实现了 ApplicationListener, 看 LoggingApplicationListener 的事件方法
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | @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 则尝试以下的顺序找实现类
- ch.qos.logback.core.Appender => org.springframework.boot.logging.logback.LogbackLoggingSystem
- ora.apache.logging.log4j.core.impl.Log4jContextFactory => org.springframework.boot.logging.log4j2.Log4J2LoggingSystem
- java.util.logging.LogManager => org.springframework.boot.logging.java.JavaLoggingSystem
而显然 LogbackLoggingSystem 对应的类 ch.qos.logback.core.Appender 是存在于 springboot starter 中的,所以在 #1 中可以确定是用 LogbackLoggingSystem 实现
#3 先说这最后一步,如果初始化好,把 LoggingSystem 的实例(此处为 LogbackLoggingSystem 实例) 注册名为 springBootLogginSystem 的 Spring Bean
#2 对日志进行配置,具体实现在 LoggingApplicationListener.initialize(environment, classLoader) 和 LogbackLoggingSystem.initialize(...) 方法中。不列出实际代码来了,只解翻译一下过程
- 试图从 Spring 属性(包括配置在 Spring 属性文件,--logging.file= 或 -Dlogging.file= 这样的配置)读出配置的 logging.file和logging.path值,如果有的话,分别映射为LOG_FILE和LOG_PATH系统属性值,这可以在 logback.xml 的配置中用 ${LOG_FILE} 引用到
- 初始日志级别的设置,如果 Spring 属性中配置了  debug=true则为 LogLevel.DEBUG, 如果配置了trace=true则为 LogLevel.TRACE。后面还会专为某些包预设一些日志级别,并且最后的日志级别可在 Spring 属性中用logging.level.logger_name=DEBUG来一一配置,如logging.level.org.springframework=DEBUG
- 如果用 Spring 属性 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设定配置文件的方法12345if (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)来配置默认的日志。
Spring Boot 没有任何日志配置文件时配置
这块其实是上面步骤 #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,Spring Boot 都会初始化 consoleAppender, 并且默认的输出模式是
| 1 2 3 4 | 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 进行覆盖设置。注意,Spring Boot 还为我们定义了 clr, wEx 这两个 Converter。
如果配置了 Spring 属性 logging.file 和 logging.path 其中一个或两个,就会在 consoleAppender 的基础上再加一个 fileAppender, 它的输出模式是
| 1 2 | 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 大小不断滚动的,不会删除旧文件。
日志文件路径如何决定的
- 如果只设置了 Spring 属性 logging.file, 就是logging.file所指定的文件,文件名可以是绝对文件路径,或者相对路径
- 如果只设置了 Spring 属性 logging.path, 那么日志文件是logging.path下的spring.log文件
- 如果以上两个属性同时指定了,则只有 logging.file是有用的,与 #1 同
无论是对于 consoleAppender 还是 fileAppender, 都是设置 INFO 为默认日志级别,并且预设了一些 logger 的日志输出级别。
简单的 Spring Boot 日志文件配置
理解了 SpringBoot 是如何初始化 Logback 日志配置后,我们来看一下项目中几种最简的日志配置方式。通常我们都会在项目的类路径下放置 logback.xml 或 logback-spring.xml 文件,而不是用 apprication.properties 的  loggin.aa.bb 等属性来配置日志。
日志同时输出到控制台和文件
依据 Spring Boot 加载 Logback  配置文件的顺序,我们可以在 classpath 下放 logback-spring.xml 或 logback.xml, 注意是 logback.xml 被优先选择。内容如下
| 1 2 3 4 5 | <?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 的路径。
日志只输出到文件
| 1 2 3 4 5 6 7 8 9 | <?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。而且效果与下面的配置是一样的。
| 1 2 3 4 5 6 7 8 | <?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> | 
小结:
- Spring Boot 基础的依赖于 starter 的项目默认采用 Slf4J + Logback 来输出日志
- 没有任何配置文件,也没有配置 Spring 属性 logging.file,logging.path或系统属性LOG_FILE,LOG_PATH的情况下只会输出日志到控制台
- 没有任何配置文件,且有配置 #2 中任何一个属性的情况下将会同时输出日志到控制台与文件,文件以 10MB 大小滚动,不删除旧文件
- 不采用外部配置文件的情况下,可以用一些 Spring 属些来进行简单的日志配置,如 logging.pattern.file等
- 可以设置 Spring 属性 logging.config来指定外部 Logback 配置文件,Logback 默认用系统属性logback.configurationFile指定配置文件的方式在 Spring Boot 项目中不再生效
- 以上叙述的 Spring 属性,可用多种设置方式,如 Spring 属性 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 的输出,我们通常都会在类路径下旋转 logback.xml或logback-spring.xml配置文件,二选一了。我没发现这两个文件有什么不同。
链接:
本文链接 https://yanbin.blog/springboot-work-with-logback/, 来自 隔叶黄莺 Yanbin Blog
[版权声明]  本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
