javascript
Spring Boot 2动态修改日志级别
本文基于:Spring Boot 2.1.3,理論支持Spring Boot 2.x所有版本。
作為程序猿,定位問題是我們的日常工作,而日志是我們定位問題非常重要的依據。傳統方式定位問題時,往往是如下步驟:
?將日志級別設低,例如?DEBUG?;?重啟應用;?復現問題,觀察日志;
如果能動態修改日志級別(無需重啟應用,就能立刻刷新),那絕對?如貓添翼?。事實上,從?Spring Boot 1.5?開始,Spring Boot Actuator?組件就已提供動態修改日志級別的能力。
TIPS
?其實更低版本也只需簡單擴展,即可實現動態修改日志級別。?對Spring Boot Actuator感到陌生的童鞋,可先前往?Spring Boot Actuator[1]?了解基礎用法。
廢話不多說了,亮代碼吧。
編碼
1 加依賴
<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?不是必須的,只是下面測試代碼要用到。
2 寫代碼
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?端點,故而需要設置將其暴露。
代碼編寫完成啦。
測試
/loggers?端點提供了?查看?以及?修改?日志級別的能力。
測試1:查看當前應用各包/類的日志級別
訪問?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"}}// ...省略 }???????測試2:查看指定包/類日志詳情
訪問?http://localhost:8080/actuator/loggers/com.itmuch.logging.TestController?,可看到類似如下的結果:
{"configuredLevel":null,"effectiveLevel":"INFO"}由測試不難發現,想看哪個包/類的日志,只需構造?/actuator/loggers/包名類名全路徑?去訪問即可。
測試3:修改日志級別
在?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;@WriteOperationpublic 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?中,代碼如下:
@Overridepublic 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[2]
- Gitee[3]
總結
以上是生活随笔為你收集整理的Spring Boot 2动态修改日志级别的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GitHub 近 100,000 程序员
- 下一篇: Spring StateMachine,