Spring Boot 系列:日志動態配置詳解


世界上最快的捷徑,就是腳踏實地,本文已收錄架構技術專欄關注這個喜歡分享的地方。

開源項目:

一、簡介

Spring Boot 版本: 2.3.4.RELEASE

不知道大家有沒有過當線上出現問題的時候,需要某些DEBUG日志,但奈何當前使用時INFO。

如果想啟用DEBUG就需要重新打包發版,但某些場景下重啟有可能問題就不會復現了,真是腦闊疼啊。

今天我們就來說下Spring Boot 下的日志配置動態調整,讓你的日志級別隨心而動。

Spring Boot的日志

在Spring Boot 內部使用的其實是Commons Logging, 而基於Spring Boot的配置加載機制為我們提供了Java Util Logging、Log4j2、Logback幾種日志方式。

Logback是其默認的日志框架,如果沒有特殊的必要真不建議更換了。(不要說性能了)

日志格式

不要小瞧格式這種東西,在實際應用的時候是賊拉重要的一件事。

不知道大家的公司有沒有統一的日志基礎組件,當然沒有也大概會有統一的日志配置文件吧。

想想如果你的日志格式不統一的話,如果每個項目都有自己的風格的話,你叫你的運維小伙伴怎么幫你切分日志?幫你報警呢?那真是正則寫到死,完全靠愛發電了。(比如我們使用的Loghub 不統一要被運維打死的)

來看下我們的日志格式配置,這里只放PATTERN

-|%d{yyyy-MM-dd HH:mm:ss.SSS}|%-5level|%X{tid}|%thread|%logger{36}.%M:%L-%msg%n

先解釋下各個位置:

  • %d{yyyy-MM-dd HH:mm:ss.SSS} :時間
  • %-5level : 日志級別
  • %X{tid} : 我們自定義的分布式追蹤ID
  • %thread : 線程
  • %logger{36}.%M:%L :class的全名(36代表最長字符).信息 行號
  • %msg%n : 輸出信息 換行

這里不知道大家能否理解前面有一個 -|是為了什么? 其實是為了在正則切分的時候方便區分換行的日志,如異常堆棧的信息。

幾個知識點

再說幾個其他Spring Boot使用的小點,我們就來進入正題

  • 在這里Logback 沒有FATAL 級別,被歸到ERROR里面了

  • 可以在application.properties 里面配置 debug=true來開啟debug模式,你也可以配置trace=true 開啟 trace模式

  • 可以再application.properties里使用logging.level.<logger-name>=<level>這種格式來配置各種日志級別,比如org.hibernate級別被設置為ERROR logging.level.org.hibernate=error

  • 日志組的概念,如果這么一個個配置煩死了,可以設定一個組給它整體配置。如:logging.group.tomcat=org.apache.catalina, org.apache.coyote配置級別為TRACE logging.level.tomcat=TRACE

  • 如果使用Logback配置文件,官方建議使用logback-spring.xml這樣的名稱,如果使用logback.xml這樣的,Spring的日志初始化就該有問題了。

  • 如果你控制日志配置,但又不想用logback.xml作為Logback配置的名字,可以在application.properties配置文件里面通過logging.config屬性指定自定義的名字:logging.config=classpath:xxx-log.xml

二、動態修改日志級別

下面我們就來說說在運行狀態下的Spring Boot應用是怎么進行動態日志級別變更的

Spring Boot Actuator

Actuator 想必了解過Spring Boot的都知道它的大名,監控、審計、度量信息等等。我們使用的優雅停機、健康檢查、日志信息都是通過這玩意來實現的。

在我們使用了Actuator 后,我們就可以使用其Logger的REST接口來操作我們的日志了,有如下三個

  • GET http://127.0.0.1:6080/actuator/loggers 返回當前應用全部的日志級別信息(想要啥有啥)
  • GET http://127.0.0.1:6080/actuator/loggers/{name} 返回{name}的日志級別
  • POST http://127.0.0.1:6080/actuator/loggers/{name} 配置參數{"configuredLevel":"INFO","effectiveLevel":"INFO"} 修改日志級別

使用Actuator 機制動態修改級別

1)、依賴必要的配置 Spring Boot Actuator (如果你繼承了parent是不需要version的)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>2.3.4.RELEASE</version>
</dependency>

2)、配置文件

#注意,這里我只開啟了loggers,health兩個Actuator
management.endpoints.web.exposure.include=loggers,health
management.endpoint.loggers.enabled=true

3)、創建一個Controller

@RestController
@RequestMapping("/log")
public class TestLogController {

    private Logger log = LoggerFactory.getLogger(TestLogController.class);

    @GetMapping
    public String log() {
        log.trace("This is a TRACE level message");
        log.debug("This is a DEBUG level message");
        log.info("This is an INFO level message");
        log.warn("This is a WARN level message");
        log.error("This is an ERROR level message");
        return "See the log for details";
    }
}

4)、啟動應用

此時在你啟動應用后,如果你使用IDEA,就可以看到如下Mapping,有三個關於logger的REST接口

5)、測試

此時,我先執行GET http://127.0.0.1:6080/log,控制台打印如下信息

2020-10-15 23:14:51.204  INFO 52628 --- [nio-6080-exec-7] c.q.chapter1.failure.TestLogController   : This is an INFO level message
2020-10-15 23:14:51.220  WARN 52628 --- [nio-6080-exec-7] c.q.chapter1.failure.TestLogController   : This is a WARN level message
2020-10-15 23:14:51.221 ERROR 52628 --- [nio-6080-exec-7] c.q.chapter1.failure.TestLogController   : This is an ERROR level message

此時我們執行GET http://127.0.0.1:6080/actuator/loggers/ROOT返回{"configuredLevel":"INFO","effectiveLevel":"INFO"},代表此時我們應用的日志級別是INFO,ROOT表示的是根節點。

6)、修改級別

此時我們不想再看INFO信息,想將整個應用日志級別換位WARN的話,我們來執行POST http://127.0.0.1:6080/actuator/loggers/ROOT 參數為{"configuredLevel":"TRACE","effectiveLevel":"TRACE"}

此時我們再來執行GET http://127.0.0.1:6080/log,控制台打印如下信息

2020-10-15 23:24:11.481 TRACE 53552 --- [nio-6080-exec-3] c.q.chapter1.failure.TestLogController   : This is a TRACE level message
2020-10-15 23:24:11.481 DEBUG 53552 --- [nio-6080-exec-3] c.q.chapter1.failure.TestLogController   : This is a DEBUG level message
2020-10-15 23:24:11.481  INFO 53552 --- [nio-6080-exec-3] c.q.chapter1.failure.TestLogController   : This is an INFO level message
2020-10-15 23:24:11.481  WARN 53552 --- [nio-6080-exec-3] c.q.chapter1.failure.TestLogController   : This is a WARN level message
2020-10-15 23:24:11.481 ERROR 53552 --- [nio-6080-exec-3] c.q.chapter1.failure.TestLogController   : This is an ERROR level message

另幾種方法

  • 配置文件掃描: 就是Logback 自動掃描的特性修改級別,配合logback-spring.xml 開啟<configuration scan="true" scanPeriod="15 seconds">就可以實現動態修改logback-spring.xml內部日志級別的目的了,有興趣可以自己試試。

  • arthas 動態修改

  • 結合遠程配置中心,如Apollo實現級別動態修改

三、實現原理

這里我們主要使用的是Spring Boot Actuator Log ,所以我們也就來說說它的原理。

Endpoint的加載

首先我們從依賴spring-boot-actuator中找到我們的LoggersEndpoint(所有的Actuator都是這一個路數),如圖:

熟悉Spring Boot加載機制的朋友都了解,在每個actuator Endpoint的背后,必然還會存在一個xxxEndpointAutoConfiguration 來為我們進行Endpoint 的加載。

而這些加載機制就都存放在spring-boot-actuator-autoconfigure中,我們在其中可以找到LoggersEndpointAutoConfiguration用於加載LoggersEndpoint的配置類。

來看下它的核心:

@Bean
	@ConditionalOnBean(LoggingSystem.class)
	@Conditional(OnEnabledLoggingSystemCondition.class)
	@ConditionalOnMissingBean
	public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem,
			ObjectProvider<LoggerGroups> springBootLoggerGroups) {
		return new LoggersEndpoint(loggingSystem, springBootLoggerGroups.getIfAvailable(LoggerGroups::new));
	}

可以看到兩個重要的參數:

  • LoggingSystem:一個抽象頂級類
  • springBootLoggerGroups:存儲了當前日志分組數據

總結下:

1、我們依賴了spring-boot-starter-actuator 包后,里面依賴了spring-boot-actuator-autoconfigure

2、在啟動掃描到spring-boot-actuator-autoconfigure 下的META-INF/spring.factories 時,LoggersEndpointAutoConfiguration會被加載到

3、LoggersEndpointAutoConfiguration 內又聲明了LoggersEndpoin 並賦值LoggingSystemspringBootLoggerGroups 作為其參數

4、項目啟動后我們通過LoggersEndpoint 接口進行日志數據訪問

LoggingSystem

LoggingSystem的繼承關系

通過上面可以了解到 LoggingSystem就是日志操作管理的核心了,所以我們先來看下他的Diagrams

通過繼承關系一眼可以看到,LogbackLoggingSystem就是我們的正主了,雖然說知道了正主那我們的LoggingSystem到底是怎么加載的呢?

LoggingSystem的加載

主要參與類說下:

  • LoggingApplicationListener
  • ApplicationStartingEvent
  • LogbackLoggingSystem
  • LoggingSystem

不多說,先上圖

1、應用使用SpringApplication.run(Chapter1Application.class, args);啟動

2、發送啟動事件ApplicationStartingEventApplicationEnvironmentPreparedEvent

3、LoggingApplicationListener接收事件進行事件分發

4、LoggingApplicationListener接收事件ApplicationStartingEvent

5、LoggingApplicationListener調用內部onApplicationStartingEvent(ApplicationStartingEvent event) 方法,使用LoggingSystem.get(classloader)初始化LoggingSystem。

6、然后調用LogbackLoggingSystem.beforeInitialize()(因為這里我們用的是logback)

7、LoggingApplicationListener接收事件ApplicationEnvironmentPreparedEvent

8、LoggingApplicationListener調用內部onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) 方法

9、onApplicationEnvironmentPreparedEvent調用initialize(ConfigurableEnvironment environment, ClassLoader classLoader)方法,會根據環境變量配置進行logging system的初始化

LoggersEndpoint

最后,我們就來看看LoggersEndpoint就可以了,這里為了方便解讀我就不全拷貝過來了,需要哪里選哪里

首先看我們對日志操作的三個接口:

  • GET /actuator/loggers 對應 public Map<String, Object> loggers() 方法

  • GET /actuator/loggers/{name} 對應 public LoggerLevels loggerLevels(@Selector String name) 方法

  • POST /actuator/loggers/{name} 對應 public void configureLogLevel(@Selector String name, @Nullable LogLevel configuredLevel) 方法

這里拿一個public Map<String, Object> loggers() 方法舉例說明,其他的差不多

@ReadOperation
	public LoggerLevels loggerLevels(@Selector String name) {
		Assert.notNull(name, "Name must not be null");
		LoggerGroup group = this.loggerGroups.get(name);
		if (group != null) {
			return new GroupLoggerLevels(group.getConfiguredLevel(), group.getMembers());
		}
		LoggerConfiguration configuration = this.loggingSystem.getLoggerConfiguration(name);
		return (configuration != null) ? new SingleLoggerLevels(configuration) : null;
	}

代碼其實很簡單,就不做過多解讀了。

四、絮叨

其實日志在我們的系統應用中很重要,對於問題的排查也是重要的憑證。根據經驗我們系統的日志最好能做到幾個點:

  • 日志格式統一化

  • 最好提供統一的日志組件,比如我們就使用了公共的logback-spring.xml組件。如果我們需要修改日志某些特性,比如加APM日志等等,只需要改一個點,我們系統的所以日志狀態都會發生改變。

  • 最好使用動態配置,調整日志配置,比如Apollo

    世界上最快的捷徑,就是腳踏實地,本文已收錄架構技術專欄關注這個喜歡分享的地方。

開源項目:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM