SpringBoot中logback日志配置文件加载顺序&配置记录
springboot加载日志配置文件有两种,一种是加载logback自身的配置文件,另一种是加载具有spring特性的logback配置文件
尝试在 classpath 下查找文件logback自身的配置文件:
先查找;
如果文件不存在,则查找文件;
如果文件不存在,则查找文件;
如果文件不存在,则查找文件。
如果上述配置文件都不存在,则加载springboot自身具有spring特性的logback配置文件,加载顺序和logback自身配置文件一致。无外呼在每种配置文件的末尾加上“-spring.”。
- 如果logback自身配置文件和具有spring特性的logback配置文件都存在,则优先加载logback自身的配置文件,优先级最低。
- 日志系统初始化之前日志输出有DEBUG,因为此刻logback-spring还未生效,spring boot默认日志级别是DEBUG。
- 有些依赖的库日志输出不受控制,是因为最终使用的logger不是logback,比如使用的log4j。Spring Boot使用JUnit测试时使用SpringBootTest注解与否在日志系统初始化时有区别
加载流程
看源码
- 在Spring应用启动时会先获取到对应的日志实例。
//
private void onApplicationStartedEvent(ApplicationStartedEvent event) {
this.loggingSystem = LoggingSystem
.get(event.getSpringApplication().getClassLoader());
this.loggingSystem.beforeInitialize();
}
- 检测和获取日志系统,检测的顺序定义在SYSTEMS中,最终通过反射创建LogbackLoggingSystem实例。
//
public static LoggingSystem get(ClassLoader classLoader) {
String loggingSystem = System.getProperty(SYSTEM_PROPERTY);// null
if (StringUtils.hasLength(loggingSystem)) {
if (NONE.equals(loggingSystem)) {
return new NoOpLoggingSystem();
}
return get(classLoader, loggingSystem);
}
for (Map.Entry<String, String> entry : SYSTEMS.entrySet()) {
if (ClassUtils.isPresent(entry.getKey(), classLoader)) {
return get(classLoader, entry.getValue());
}
}
throw new IllegalStateException("No suitable logging system located");
}
/**
* A System property that can be used to indicate the {@link LoggingSystem} to use.
*/
public static final String SYSTEM_PROPERTY = LoggingSystem.class.getName();
private static final Map<String, String> SYSTEMS;
static {
Map<String, String> systems = new LinkedHashMap<String, String>();
systems.put("",
"");
systems.put("..Log4jContextFactory",
".log4j2.Log4J2LoggingSystem");
systems.put("",
"");
SYSTEMS = Collections.unmodifiableMap(systems);
}
- 然后在ApplicationEnvironmentPreparedEvent之后进行logback的初始化。
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
if (this.loggingSystem == null) {
this.loggingSystem = LoggingSystem
.get(event.getSpringApplication().getClassLoader());
}
initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}
/**
* Initialize the logging system according to preferences expressed through the
* {@link Environment} and the classpath.
* @param environment the environment
* @param classLoader the classloader
*/
protected void initialize(ConfigurableEnvironment environment,
ClassLoader classLoader) {
new LoggingSystemProperties(environment).apply();
LogFile logFile = LogFile.get(environment);
if (logFile != null) {
logFile.applyToSystemProperties();
}
initializeEarlyLoggingLevel(environment);
initializeSystem(environment, this.loggingSystem, logFile);
initializeFinalLoggingLevels(environment, this.loggingSystem);
registerShutdownHookIfNecessary(environment, this.loggingSystem);
}
- 重点看配置文件检测的过程。
private void initializeWithConventions(
LoggingInitializationContext initializationContext, LogFile logFile) {
// 获取logback自己支持的配置文件
String config = getSelfInitializationConfig();
if (config != null && logFile == null) {
// self initialization has occurred, reinitialize in case of property changes
reinitialize(initializationContext);
return;
}
// 如果没有则检测spring相关的logback配置
if (config == null) {
config = getSpringInitializationConfig();
}
if (config != null) {
loadConfiguration(initializationContext, config, logFile);
return;
}
loadDefaults(initializationContext, logFile);
}
- logback自身配置文件的生效顺序。
//
@Override
protected String[] getStandardConfigLocations() {
return new String[] { "", "", "",
"" };
}
- 通过这里可以看到spring boot中支持的logback配置文件格式,就是在logback自配置文件(, 等)基础上文件名后面加了“ -spring ”,如logback-test-spring, logback-spring等。
/**
* Return the spring config locations for this system. By default this method returns
* a set of locations based on {@link #getStandardConfigLocations()}.
* @return the spring config locations
* @see #getSpringInitializationConfig()
*/
protected String[] getSpringConfigLocations() {
String[] locations = getStandardConfigLocations();
for (int i = 0; i < locations.length; i++) {
String extension = StringUtils.getFilenameExtension(locations[i]);
locations[i] = locations[i].substring(0,
locations[i].length() - extension.length() - 1) + "-spring."
+ extension;
}
return locations;
}
注意:Spring Boot使用JUnit时,如果没有配置SpringBootTest注解,日志系统根本不会得到初始化,会使用org.获取,如果在test/resource下面存在则会生效,否则就使用系统默认的配置。如果配置了置SpringBootTest注解,则SpringBoot会正常的初始化,日志系统会正常加载。
一直在说具有spring特性的logback的配置文件,那到底具有什么特性呢?
可以使用application.*中属性。这样的神操作可以让我们在运行的springboot jar包外只使用application配置文件就可以对日志的输出路径及级别进行控制,不用再在jar包外配置日志配置文件了。
使用 springProfile 与 springProperty 提升 的能力:
- springProfile
<springProfile>
标签允许我们更加灵活配置文件,可选地包含或排除配置部分。元素中的任何位置均支持轮廓部分。使用该name属性指定哪个配置文件接受配置。可以使用逗号分隔列表指定多个配置文件。
<springProfile name="dev">
<!-- 开发环境时激活 -->
</springProfile>
<springProfile name="dev,test">
<!-- 开发,测试的时候激活-->
</springProfile>
<springProfile name="!prod">
<!-- 当 "生产" 环境时,该配置不激活-->
</springProfile>
例子
<!-- 开发环境日志级别为DEBUG -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="FILE"/>
<appender-ref ref="STDOUT"/>
</root>
</springProfile>
<!-- 测试环境日志级别为INFO -->
<springProfile name="test">
<root level="INFO">
<appender-ref ref="FILE"/>
<appender-ref ref="STDOUT"/>
</root>
</springProfile>
- springProperty
1.<springProperty>
标签允许我们从Spring中显示属性,Environment 以便在Logback中使用。如果你想将 在回读配置中访问文件中的值,这将非常有用
2.标签的工作方式与Logback的标准 标签类似,但不是直接value 指定source属性(从Environment)指定。scope 如果需要将属性存储在local范围之外的其他位置,则可以使用该属性。如果您需要一个后备值,以防该属性未设置,则Environment可以使用该defaultValue属性。
<springProperty scope="context" name="fluentHost" source="" defaultValue="localhost"/>
<appender name="FLUENT" class="">
<remoteHost>${fluentHost}</remoteHost>
</appender>
例子
<!-- 读取中的属性来生成日志文件名 -->
<springProperty scope="context" name="logName" source="" defaultValue=""/>
<appender name="FILE" class="">
<file>logs/${logName}.log</file> <!-- 使用方法 -->
<append>true</append>
<rollingPolicy class="">
<fileNamePattern>logs/${logName}-%d{yyyy-MM-dd}.%</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>7</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>[%date{yyyy-MM-dd HH:mm:ss}] [%-5level] [%logger:%line] --%mdc{client} %msg%n</pattern>
</encoder>
<filter class="">
<level>DEBUG</level>
</filter>
</appender>
将RelaxedPropertyResolver用于访问环境属性。如果使用虚线符号指定source(my-property-name)所有的变化都会被尝试(myPropertyName,MY_PROPERTY_NAME等)。
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" debug="false">
<springProperty scope="context" name="LEVEL" source=""/>
<springProperty scope="context" name="LOG_HOME" source=""/>
<springProperty scope="context" name="APP" source=""/>
<!--<property name="APP" value="server" />-->
<!--<property name="LOG_HOME" value="./target/logs" />-->
<appender name="CONSOLE" class="">
<encoder>
<pattern>[test] %d{:mm:} [%-2t] %-5p %-22c{0} %X{ServiceId} - %m%n</pattern>
<!--<pattern>[test] %d{yyyy-MM-dd HH:mm:} [%thread] %-5level %logger{50} - %msg%n</pattern>-->
</encoder>
</appender>
<appender name="FILE" class="" additivity="false">
<!--<File>${LOG_HOME}/${APP}_detail.log</File>-->
<File>${LOG_HOME}/${APP}.log</File>
<encoder>
<pattern>%d{:mm:} [%-16t] %-5p %-22c{0} %X{ServiceId} - %m%n</pattern>
</encoder>
<rollingPolicy class="">
<fileNamePattern>${LOG_HOME}/${APP}_%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<appender name="ERROR" class="" additivity="false">
<!--<File>${LOG_HOME}/${APP}_detail.log</File>-->
<File>${LOG_HOME}/</File>
<filter class="">
<level>ERROR</level>
</filter>
<encoder>
<pattern>%d{:mm:} [%-16t] %-5p %-22c{0} %X{ServiceId} - %m%n</pattern>
</encoder>
<rollingPolicy class="">
<fileNamePattern>${LOG_HOME}/error_%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<!--<logger name="" level="TRACE" />-->
<!--
additivity默认为true,会查找上一级logger(实际是按照包名查找上层的logger),
找到后不再判断logger配置的级别要求,直接找到对应的appender,将日志内容输出。
上一级logger指的是父级包,日志会根据additivity的继承关系无条件输出到上层logger中
-->
<!--<logger name="" level="INFO">-->
<!--<appender-ref ref="FILE" />-->
<!--<appender-ref ref="ERROR" />-->
<!--</logger>-->
<!--<logger name="" level="DEBUG">-->
<!--<appender-ref ref="CONSOLE" />-->
<!--</logger>-->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
<appender-ref ref="ERROR" />
</root>
</configuration>