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>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章