做爲程序猿,定位問題是咱們的平常工做,而日誌是咱們定位問題很是重要的依據。傳統方式定位問題時,每每是以下步驟:java
•將日誌級別設低,例如 DEBUG
;web
•重啓應用;•復現問題,觀察日誌;spring
若是能動態修改日誌級別(無需重啓應用,就能馬上刷新),那絕對如虎添翼。事實上,從 Spring Boot 1.5
開始,Spring Boot Actuator
組件就已提供動態修改日誌級別的能力。json
1.加依賴app
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
這裏的 spring-boot-starter-web
不是必須的,只是下面測試代碼要用到。ide
2.寫代碼spring-boot
package com.itmuch.logging; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @author itmuch.com */ @RestController public class TestController { private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class); @GetMapping("/test") public String simple() { LOGGER.debug("這是一個debug日誌..."); return "test"; } }
3.寫配置測試
management: endpoints: web: exposure: include: 'loggers'
因爲Spring Boot 2.x默認只暴露 /health
以及 /info
端點,而日誌控制須要用到 /loggers
端點,故而須要設置將其暴露。this
代碼編寫完成啦。編碼
測試
/loggers
端點提供了 查看
以及 修改
日誌級別的能力。
測試1:查看當前應用各包/類的日誌級別
{ "levels": ["OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"], "loggers": { "ROOT": { "configuredLevel": "INFO", "effectiveLevel": "INFO" }, "com.itmuch.logging.TestController": { "configuredLevel": null, "effectiveLevel": "INFO" } } // ...省略 }
測試2:查看指定包/類日誌詳情
{"configuredLevel":null,"effectiveLevel":"INFO"}
由測試不難發現,想看哪一個包/類的日誌,只需構造 /actuator/loggers/包名類名全路徑
去訪問便可。
測試3:修改日誌級別
在 TestController
類中,筆者編寫設置了一條日誌 LOGGER.debug("這是一個debug日誌...");
,而由測試1,默認的日誌級別是INFO,因此不會打印。下面來嘗試將該類的日誌級別設爲DEBUG。
修改完了之後再次訪問那個端點,發現日誌級別已經被修改爲功了
Actuator有約定, /actuator/xxx
端點的定義代碼在 xxxEndpoint
中。故而,找到類 org.springframework.boot.actuate.logging.LoggersEndpoint
,可看到相似以下的代碼:
@Endpoint(id = "loggers") public class LoggersEndpoint { private final LoggingSystem loggingSystem; @WriteOperation public void configureLogLevel(@Selector String name, @Nullable LogLevel configuredLevel) { Assert.notNull(name, "Name must not be empty"); this.loggingSystem.setLogLevel(name, configuredLevel); } // ...其餘省略 }
其中, Endpoint
、WriteOperation
、@Selector
都是Spring Boot 2.0開始提供的新註解。
@Endpoint(id = "loggers")
用來描述Spring Boot Actuator
的端點,這樣就會產生一個/actuator/loggers
的路徑,它相似於Spring MVC的 @RequestMapping("loggers")
。
@WriteOperation
表示這是一個寫操做,它相似於Spring MVC的 @PostMapping
。Spring Boot Actuator還提供了其餘操做,以下表:
Operation | HTTP method |
@ReadOperation | GET |
@WriteOperation | POST |
@DeleteOperation | DELETE |
@Selector
用於篩選 @Endpoint
註解返回值的子集,它相似於Spring MVC的 @PathVariable
。
這樣,上面的代碼就很好理解了—— configureLogLevel
方法裏面就一行代碼 :this.loggingSystem.setLogLevel(name, configuredLevel);
,發送POST請求後,name就是咱們傳的包名或者類名,configuredLevel就是咱們傳的消息體。
怎麼實現動態修改的呢?不妨點進去看看,而後發現代碼以下:
// org.springframework.boot.logging.LoggingSystem#setLogLevel public void setLogLevel(String loggerName, LogLevel level) { throw new UnsupportedOperationException("Unable to set log level"); }
嘿嘿,沒事,確定有實現類, 該方法在以下實現類被實現:
# 適用於java.util.logging的LoggingSystem org.springframework.boot.logging.java.JavaLoggingSystem # 適用於Log4j 2的LoggingSystem org.springframework.boot.logging.log4j2.Log4J2LoggingSystem # 適用於logback的LoggingSystem org.springframework.boot.logging.logback.LogbackLoggingSystem # 啥都不幹的LoggingSystem org.springframework.boot.logging.LoggingSystem.NoOpLoggingSystem
Spring Boot 2.x中,默認使用Logback,所以進入到 LogbackLoggingSystem
中,代碼以下:
@Override public void setLogLevel(String loggerName, LogLevel level) { ch.qos.logback.classic.Logger logger = getLogger(loggerName); if (logger != null) { logger.setLevel(LEVELS.convertSystemToNative(level)); } }
至此,就真相大白了。其實根本沒有黑科技,Spring Boot本質上仍是使用了Logback的API,ch.qos.logback.classic.Logger.setLevel
實現日誌級別的修改。