SpringBoot中logback日志配置文件加载顺序&配置记录

SpringBoot中logback日志配置文件加载顺序&配置记录


springboot加载日志配置文件有两种,一种是加载logback自身的配置文件,另一种是加载具有spring特性的logback配置文件
尝试在 classpath 下查找文件logback自身的配置文件:
先查找logback-test.groovy;
如果文件不存在,则查找文件logback-test.xml;
如果文件不存在,则查找文件logback.groovy;
如果文件不存在,则查找文件logback.xml。
如果上述配置文件都不存在,则加载springboot自身具有spring特性的logback配置文件,加载顺序和logback自身配置文件一致。无外呼在每种配置文件的末尾加上“-spring.”。

  • 如果logback自身配置文件和具有spring特性的logback配置文件都存在,则优先加载logback自身的配置文件,xxx-spring.xml优先级最低。
  • 日志系统初始化之前日志输出有DEBUG,因为此刻logback-spring还未生效,spring boot默认日志级别是DEBUG。
  • 有些依赖的库日志输出不受控制,是因为最终使用的logger不是logback,比如使用的log4j。Spring Boot使用JUnit测试时使用SpringBootTest注解与否在日志系统初始化时有区别

加载流程

LoggingSystem
AbstractLoggingSystem
Slf4JLoggingSystem
LogbackLoggingSystem
Log4J2LoggingSystem

看源码

  • 在Spring应用启动时会先获取到对应的日志实例。
// org.springframework.boot.logging.LoggingApplicationListener
private void onApplicationStartedEvent(ApplicationStartedEvent event) {
	this.loggingSystem = LoggingSystem
			.get(event.getSpringApplication().getClassLoader());
	this.loggingSystem.beforeInitialize();
}
  • 检测和获取日志系统,检测的顺序定义在SYSTEMS中,最终通过反射创建LogbackLoggingSystem实例。
// org.springframework.boot.logging.LoggingSystem
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("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);
}
  • 然后在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自身配置文件的生效顺序。
// org.springframework.boot.logging.logback.LogbackLoggingSystem
@Override
protected String[] getStandardConfigLocations() {
	return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy",
			"logback.xml" };
}
  • 通过这里可以看到spring boot中支持的logback配置文件格式,就是在logback自配置文件(logback-test.xml, logback.xml等)基础上文件名后面加了“ -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.slf4j.impl.StaticLoggerBinder获取,如果在test/resource下面存在logback-test.xml则会生效,否则就使用系统默认的配置。如果配置了置SpringBootTest注解,则SpringBoot会正常的初始化,日志系统会正常加载。

一直在说具有spring特性的logback的配置文件,那到底具有什么特性呢?
可以使用application.*中属性。这样的神操作可以让我们在运行的springboot jar包外只使用application配置文件就可以对日志的输出路径及级别进行控制,不用再在jar包外配置日志配置文件了。
使用 springProfile 与 springProperty 提升 logback.xm 的能力:

  • 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中使用。如果你想将 application.properties在回读配置中访问文件中的值,这将非常有用
    2.标签的工作方式与Logback的标准 标签类似,但不是直接value 指定source属性(从Environment)指定。scope 如果需要将属性存储在local范围之外的其他位置,则可以使用该属性。如果您需要一个后备值,以防该属性未设置,则Environment可以使用该defaultValue属性。
<springProperty scope="context" name="fluentHost" source="myapp.fluentd.host" defaultValue="localhost"/>
<appender name="FLUENT" class="ch.qos.logback.more.appenders.DataFluentAppender">
    <remoteHost>${fluentHost}</remoteHost>
    
</appender>

例子

<!-- 读取spring.application.name中的属性来生成日志文件名 -->
<springProperty scope="context" name="logName" source="spring.application.name" defaultValue="localhost.log"/>

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>logs/${logName}.log</file>    <!-- 使用方法 -->
    <append>true</append>
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <fileNamePattern>logs/${logName}-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern>
        <maxFileSize>100MB</maxFileSize>
        <maxHistory>7</maxHistory>
        <totalSizeCap>3GB</totalSizeCap>
    </rollingPolicy>
    <encoder>
        <pattern>[%date{yyyy-MM-dd HH:mm:ss}] [%-5level] [%logger:%line] &#45;&#45;%mdc{client} %msg%n</pattern>
    </encoder>
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
        <level>DEBUG</level>
    </filter>
</appender>

将RelaxedPropertyResolver用于访问环境属性。如果使用虚线符号指定source(my-property-name)所有的变化都会被尝试(myPropertyName,MY_PROPERTY_NAME等)。

  • logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" debug="false">

    <springProperty scope="context" name="LEVEL" source="logging.level.root"/>
    <springProperty scope="context" name="LOG_HOME" source="logging.path"/>
    <springProperty scope="context" name="APP" source="logging.info.name"/>
    <!--<property name="APP" value="server" />-->
    <!--<property name="LOG_HOME" value="./target/logs" />-->

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[test] %d{yy-MM-dd.HH:mm:ss.SSS} [%-2t] %-5p %-22c{0} %X{ServiceId} - %m%n</pattern>
            <!--<pattern>[test] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>-->
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender" additivity="false">
        <!--<File>${LOG_HOME}/${APP}_detail.log</File>-->
        <File>${LOG_HOME}/${APP}.log</File>
        <encoder>
            <pattern>%d{yy-MM-dd.HH:mm:ss.SSS} [%-16t] %-5p %-22c{0} %X{ServiceId} - %m%n</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/${APP}_%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>

    <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender" additivity="false">
        <!--<File>${LOG_HOME}/${APP}_detail.log</File>-->
        <File>${LOG_HOME}/error.log</File>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <encoder>
            <pattern>%d{yy-MM-dd.HH:mm:ss.SSS} [%-16t] %-5p %-22c{0} %X{ServiceId} - %m%n</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/error_%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>

    <!--<logger name="com.test.cloud.server.domain" level="TRACE" />-->

    <!--
        additivity默认为true,会查找上一级logger(实际是按照包名查找上层的logger),
        找到后不再判断logger配置的级别要求,直接找到对应的appender,将日志内容输出。
        上一级logger指的是父级包,日志会根据additivity的继承关系无条件输出到上层logger中
    -->

    <!--<logger name="com.test.cloud" level="INFO">-->
        <!--<appender-ref ref="FILE" />-->
        <!--<appender-ref ref="ERROR" />-->
    <!--</logger>-->

    <!--<logger name="com.test.cloud.server" level="DEBUG">-->
        <!--<appender-ref ref="CONSOLE" />-->
    <!--</logger>-->

    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
        <appender-ref ref="ERROR" />
    </root>
</configuration>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章