概要
SpringBoot内部使用CommonsLogging,并依赖与具体的底层日志库实现。默认支持Java Util Logging,Log4j2, Logback.
控制台输出日志
可以通过命令行指定:java -jar myapp.jar —debug 或在application.properties 配置:
debug=true
注:通过指定debug=true,只会对SpringBoot打印的日志生效,不会对应用自身的日志生效。
文件输出
默认情况下,SpringBoot只会输出日志到控制台,如果要输出到文件,需要在application.properties单独配置:
logging.file.name=demo.log logging.file.path=/Users/xiele/logs/springboot/
日志级别
logging.level.root = info
自定义日志配置
logging.config=单独的日志配置文件路径
logging.config配置可以指定自定义的日志配置。日志系统的加载在Spring容器创建之前,如果未配置logging.config, 根据实际依赖的日志Starter,SpringBoot会加载classpath下如下的文件配置:
Logging System | Customization |
---|---|
Logback | logback-spring.xml , logback-spring.groovy , logback.xml , or logback.groovy |
Log4j2 | log4j2-spring.xml or log4j2.xml |
JDK (Java Util Logging) | logging.properties |
Logback 扩展
SpringBoot建议,日志命名为:xx-spring.xml
由于logback或log4j2的标准日志会提前加载,无法使用Spring的扩展配置,因此建议命名-spring.xml,这样是让Spring来负责加载日志系统文件,并委托为具体的日志库。
SpringBoot使用Log4j2
排除logback引入Log4j2
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
配置log4j2的XML文件
默认情况下,Spring会扫描classpath下的log4j2.xml,log4j2.json,log4j2.yaml. 可以单独定义配置文件,例如命名为log4j2-spring.xml. 通过logging.config来指定路径
logging.config=classpath:log4j2/log4j2-spring.xml (也可以加入到某个目录中)
如果log4j2-spring.xml在classpath下,默认可被springboot加载。
log4j2-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- status表示log4j2本身的日志信息打印级别,和下面的level,不是一个概念 -->
<!--TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF-->
<Configuration status="OFF" monitorInterval="30">
<Properties>
<Property name="LOG_PATTERN">
%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${hostName} --- [%15.15t] %-40.40c{1.} : %m%n%ex
</Property>
<Property name="LOG_FILE_PATH">/Users/xiele/logs/springboot</Property>
</Properties>
<Appenders>
<Console name="ConsoleAppender" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
<!-- Rolling File Appender -->
<RollingFile name="FileAppender" fileName="${LOG_FILE_PATH}/spring-boot-log4j2-demo.log"
filePattern="${LOG_FILE_PATH}/spring-boot-log4j2-demo-%d{yyyy-MM-dd}-%i.log">
<PatternLayout>
<Pattern>${LOG_PATTERN}</Pattern>
</PatternLayout>
<Filters>
<!-- 只记录INFO级别日志信息,程序打印的其他信息不会被记录 -->
<!-- 此level设置的日志级别,是过滤日志文件中打印出的日志信息,和Root的level有所区别 -->
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<Policies>
<SizeBasedTriggeringPolicy size="10MB"/>
<!-- 每天创建一个日志文件 -->
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
<!--最大保留10个日志文件-->
<DefaultRolloverStrategy max="10"/>
</RollingFile>
</Appenders>
<Loggers>
<!-- 此level设置的日志级别,是过滤项目中输出的日志信息,和ThresholdFilter的level有所区别 -->
<Root level="info">
<AppenderRef ref="ConsoleAppender"/>
<AppenderRef ref="FileAppender"/>
</Root>
</Loggers>
</Configuration>
原理
spring-boot-starter的主模块里,内置的spring.factories文件里配置了很多Listener.
SpringBoot先通过ApplicationListener发布启动事件,然后再启动Spring容器,也就是说日志的初始化在容器之前。
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
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
构造方法中,先把配置的所有ApplicationListener放到SpringBoot#listeners中。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
然后SpringApplication#run方法中,获取配置的:EventPublishingRunListener
然后执行listeners.starting()
然后会执行:LoggingApplicationListener#onApplicationStartingEvent
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
this.loggingSystem.beforeInitialize();
}
进一步初始化日志系统。
日志系统通过预定义的一些依赖的类,并放到Map中,然后尝试加载,并决定初始化何种日志系统。典型的策略模式:
static {
Map<String, String> systems = new LinkedHashMap<>();
systems.put("ch.qos.logback.core.Appender", "org.springframework.boot.logging.logback.LogbackLoggingSystem");
systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory",
"org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem");
SYSTEMS = Collections.unmodifiableMap(systems);
}
当前类路径下,排除logback后,引入log4j2,最终初始化Log4J2LoggingSystem。
Log4J2LoggingSystem初始化时,默认查找如下文件:
如果不存在,则在上述文件后追加xxx-spring.xml(json)等。
如果找到则委托给Log4j2做日志系统初始化。
如果配置了logging.config,则直接在Listener中直接初始化