(轉(zhuǎn)載) 微博 Qzone 微信 Spring Boot 2動態(tài)修改日志級別

作為程序猿,定位問題是我們的日常工作,而日志是我們定位問題非常重要的依據(jù)。傳統(tǒng)方式定位問題時,往往是如下步驟:

將日志級別設(shè)低,例如 DEBUG ;

重啟應(yīng)用;

復(fù)現(xiàn)問題,觀察日志;

如果能動態(tài)修改日志級別(無需重啟應(yīng)用,就能立刻刷新),那絕對 如貓?zhí)硪?。事實上,從 Spring Boot 1.5 開始,Spring Boot Actuator 組件就已提供動態(tài)修改日志級別的能力。

TIPS

其實更低版本也只需簡單擴展,即可實現(xiàn)動態(tài)修改日志級別。

對Spring Boot Actuator感到陌生的童鞋,可先前往 Spring Boot Actuator( http://www.itmuch.com/spring-cloud/finchley-3/ ) 了解基礎(chǔ)用法。

廢話不多說了,亮代碼吧。

編碼

1 加依賴

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-actuator

這里的 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 端點,故而需要設(shè)置將其暴露。

代碼編寫完成啦。

測試

/loggers 端點提供了 查看 以及 修改 日志級別的能力。

測試1:查看當(dāng)前應(yīng)用各包/類的日志級別。

訪問 http://localhost:8080/actuator/loggers ,可看到類似如下的結(jié)果:

{

"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 ,可看到類似如下的結(jié)果:

{"configuredLevel":null,"effectiveLevel":"INFO"}

由測試不難發(fā)現(xiàn),想看哪個包/類的日志,只需構(gòu)造 /actuator/loggers/包名類名全路徑 去訪問即可。

測試3:修改日志級別

在 TestController 類中,筆者編寫設(shè)置了一條日志 LOGGER.debug("這是一個debug日志..."); ,而由測試1,默認的日志級別是INFO,所以不會打印。下面來嘗試將該類的日志級別設(shè)為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"}'

如上,只需發(fā)送一個POST請求,并將請求body設(shè)為:{"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 ,可看到類似如下的結(jié)果:

{"configuredLevel":"DEBUG","effectiveLevel":"DEBUG"}

說明已成功動態(tài)修改日志級別。

原理分析

TIPS

本節(jié)著重分析如何實現(xiàn)動態(tài)修改。

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 的端點,這樣就會產(chǎn)生一個/actuator/loggers 的路徑,它類似于Spring MVC的 @RequestMapping("loggers") 。

@WriteOperation 表示這是一個寫操作,它類似于Spring MVC的 @PostMapping 。Spring Boot Actuator還提供了其他操作,如下表:

OperationHTTP method@ReadOperationGET@WriteOperationPOST@DeleteOperationDELETE

@Selector 用于篩選 @Endpoint 注解返回值的子集,它類似于Spring MVC的 @PathVariable 。

這樣,上面的代碼就很好理解了—— configureLogLevel 方法里面就一行代碼 :this.loggingSystem.setLogLevel(name, configuredLevel); ,發(fā)送POST請求后,name就是我們傳的包名或者類名,configuredLevel就是我們傳的消息體。

怎么實現(xiàn)動態(tài)修改的呢?不妨點進去看看,然后發(fā)現(xiàn)代碼如下:

// org.springframework.boot.logging.LoggingSystem#setLogLevel

public void setLogLevel(String loggerName, LogLevel level) {

throw new UnsupportedOperationException("Unable to set log level");

}

嘿嘿,沒事,肯定有實現(xiàn)類, 該方法在如下實現(xiàn)類被實現(xiàn):

# 適用于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本質(zhì)上還是使用了Logback的API,ch.qos.logback.classic.Logger.setLevel 實現(xiàn)日志級別的修改。

你可能會好奇

你可能會好奇,LoggingSystem有這么多實現(xiàn)類,Spring Boot怎么知道什么情況下用什么LoggingSystem呢?可在 org.springframework.boot.logging.LoggingSystem 找到類似如下代碼:

public abstract class LoggingSystem {

private static final Map SYSTEMS;

?

static {

Map 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"));

}

// 省略不相關(guān)內(nèi)容...

}

由代碼不難發(fā)現(xiàn),其實就是構(gòu)建了一個名為 SYSTEMS 的map,作為各種日志系統(tǒng)的字典;然后在 get 方法中,看應(yīng)用是否加載了map中的類;如果加載了,就通過反射,初始化響應(yīng) LoggingSystem 。例如:Spring Boot發(fā)現(xiàn)當(dāng)前應(yīng)用加載了ch.qos.logback.core.Appender ,就去實例化 org.springframework.boot.logging.logback.LogbackLoggingSystem 。

界面

本文是使用 curl 手動發(fā)送 POST 請求手動修改日志級別的,該方式不適用生產(chǎn),因為很麻煩,容易出錯。生產(chǎn)環(huán)境,建議根據(jù)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

原地址:https://www.toutiao.com/i6673790051039576580/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容