本文基於:Spring Boot 2.1.3,理論支持Spring Boot 2.x全部版本。java
做爲程序猿,定位問題是咱們的平常工做,而日誌是咱們定位問題很是重要的依據。傳統方式定位問題時,每每是以下步驟:git
DEBUG
;若是能動態修改日誌級別(無需重啓應用,就能馬上刷新),那絕對 如貓添翼
。事實上,從 Spring Boot 1.5
開始,Spring Boot Actuator
組件就已提供動態修改日誌級別的能力。github
TIPSweb
- 其實更低版本也只需簡單擴展,便可實現動態修改日誌級別。
- 對Spring Boot Actuator感到陌生的童鞋,可先前往 Spring Boot Actuator 瞭解基礎用法。
廢話很少說了,亮代碼吧。spring
加依賴apache
<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
不是必須的,只是下面測試代碼要用到。json
寫代碼app
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"; } }
寫配置:curl
management: endpoints: web: exposure: include: 'loggers'
因爲Spring Boot 2.x默認只暴露 /health
以及 /info
端點,而日誌控制須要用到 /loggers
端點,故而須要設置將其暴露。ide
代碼編寫完成啦。
/loggers
端點提供了 查看
以及 修改
日誌級別的能力。
訪問 http://localhost:8080/actuator/loggers
,可看到相似以下的結果:
{ "levels": ["OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"], "loggers": { "ROOT": { "configuredLevel": "INFO", "effectiveLevel": "INFO" }, "com.itmuch.logging.TestController": { "configuredLevel": null, "effectiveLevel": "INFO" } } // ...省略 }
訪問 http://localhost:8080/actuator/loggers/com.itmuch.logging.TestController
,可看到相似以下的結果:
{"configuredLevel":null,"effectiveLevel":"INFO"}
由測試不難發現,想看哪一個包/類的日誌,只需構造 /actuator/loggers/包名類名全路徑
去訪問便可。
在 TestController
類中,筆者編寫設置了一條日誌 LOGGER.debug("這是一個debug日誌...");
,而由測試1,默認的日誌級別是INFO,因此不會打印。下面來嘗試將該類的日誌級別設爲DEBUG。
curl -X POST http://localhost:8080/actuator/loggers/com.itmuch.logging.TestController \ -H "Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8" \ --data '{"configuredLevel":"debug"}'
如上,只需發送一個POST請求,並將請求body設爲:{"configuredLevel":"debug"}
便可。
此時,訪問 localhost:8080/test
會看到相似以下的日誌:
2019-03-28 16:24:04.513 DEBUG 19635 --- [nio-8080-exec-7] com.itmuch.logging.TestController : 這是一個debug日誌...
而且,此時再訪問 http://localhost:8080/actuator/loggers/com.itmuch.logging.TestController
,可看到相似以下的結果:
{"configuredLevel":"DEBUG","effectiveLevel":"DEBUG"}
說明已成功動態修改日誌級別。
TIPS
本節着重分析如何實現動態修改。
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
實現日誌級別的修改。
你可能會好奇,LoggingSystem有這麼多實現類,Spring Boot怎麼知道什麼狀況下用什麼LoggingSystem呢?可在 org.springframework.boot.logging.LoggingSystem
找到相似以下代碼:
public abstract class LoggingSystem { private static final Map<String, String> SYSTEMS; static { Map<String, String> systems = new LinkedHashMap<>(); 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); } /** * Detect and return the logging system in use. Supports Logback and Java Logging. * @param classLoader the classloader * @return the logging system */ public static LoggingSystem get(ClassLoader classLoader) { String loggingSystem = System.getProperty(SYSTEM_PROPERTY); if (StringUtils.hasLength(loggingSystem)) { if (NONE.equals(loggingSystem)) { return new NoOpLoggingSystem(); } return get(classLoader, loggingSystem); } return SYSTEMS.entrySet().stream() .filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader)) .map((entry) -> get(classLoader, entry.getValue())).findFirst() .orElseThrow(() -> new IllegalStateException( "No suitable logging system located")); } // 省略不相關內容... }
由代碼不難發現,其實就是構建了一個名爲 SYSTEMS
的map,做爲各類日誌系統的字典;而後在 get
方法中,看應用是否加載了map中的類;若是加載了,就經過反射,初始化響應 LoggingSystem
。例如:Spring Boot發現當前應用加載了 ch.qos.logback.core.Appender
,就去實例化 org.springframework.boot.logging.logback.LogbackLoggingSystem
。
本文是使用 curl
手動發送 POST
請求手動修改日誌級別的,該方式不適用生產,由於很麻煩,容易出錯。生產環境,建議根據Actuator提供的RESTful API定製界面,或使用 Spring Boot Admin
,可視化修改日誌級別,以下圖所示:
想修改哪一個包/類的日誌級別,直接點擊便可。
GitHub:https://github.com/eacdy/spring-boot-study/tree/master/spring-boot-logging-change-logging-level
Gitee:https://gitee.com/itmuch/spring-boot-study/tree/master/spring-boot-logging-change-logging-level
http://www.itmuch.com/spring-boot/change-logger-level/