本文记录 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
配置文件,二选一了。我没发现这两个文件有什么不同。