背景
作為程序猿,定位問題是我們的日常工作,而日志是我們定位問題非常重要的依據。傳統方式定位問題時,往往是如下步驟:
- 將日志級別設低,例如 DEBUG ;
- 重啟應用;
- 復現問題,觀察日志;
那么問題就來了,可不可以動態修改日志級別呢?(無需重啟應用,就能立刻刷新)
答案是肯定的!
下面提供幾個思路給大家參考。
使用 LoggingSystem 自行開發修改日志級別的接口
不廢話,直接上代碼
@Resource
private LoggingSystem loggingSystem;
@PostMApping("/changeLogLevel")
public void changeLogLevel(@RequestParam("name") String name, @RequestParam("level") String level) {
LogLevel logLevel = LogLevel.valueOf(level.toUpperCase());
loggingSystem.setLogLevel(name, logLevel);
}
what?這么簡單?是的,就是這么簡單。
LoggingSystem 這個抽象類就是關鍵,其實后面所要介紹的幾個修改思路(actuator,Apollo,mq)的底層也是基于它進行修改的。
如果大家對LoggingSystem這個類在底層究竟是如何實現動態修改日志級別感興趣的話,請評論區留言,我抽時間再寫一篇文章來詳細說一下。
然后再說一下這種方式的優缺點吧。
優點:簡單!
缺點:也很明顯,只適合單機/生產機器不多的服務。如果你的服務有上百個節點,用這種方式來修改。。。
那有朋友會問,有沒有適合多機集群的服務的修改方式?
那必須有啊,下面介紹一下思路二。
使用 Apollo + LoggingSystem
這種方式的前提是系統接入了Apollo。
也不廢話,直接上代碼吧。代碼里也有注釋。
@Configuration
public class LogLevelRefresher {
private final static Logger log = LoggerFactory.getLogger(com.dylan.config.LoggingLevelRefresher.class);
private static final String PREFIX = "logging.level.";
private static final String ROOT = LoggingSystem.ROOT_LOGGER_NAME;
@Resource
private LoggingSystem loggingSystem;
/**
* 支持類配置
*/
@PostConstruct
private void init() {
//要修改日志級別的key(包路徑/類路徑)
String keyStr = ConfigCenterService.getAppProperty("log.changeKey", "logging.level.root,logging.level.com.dylan.config");
Set<String> changedKeys = Arrays.stream(keyStr.split(",")).collect(Collectors.toSet());
refreshLoggingLevels(changedKeys);
}
/**
* 修改Apollo配置后的回調方法
*/
@ApolloConfigChangeListener
private void onChange(ConfigChangeEvent changeEvent) {
refreshLoggingLevels(changeEvent.changedKeys());
}
private void refreshLoggingLevels(Set<String> changedKeys) {
for (String key : changedKeys) {
// key may be : logging.level.com.example.web
if (StringUtils.startsWithIgnoreCase(key, PREFIX)) {
String loggerName = PREFIX.equalsIgnoreCase(key) ? ROOT : key.substring(PREFIX.length());
String strLevel = ConfigCenterService.getProperty(key, parentStrLevel(loggerName));
LogLevel level = LogLevel.valueOf(strLevel.toUpperCase());
loggingSystem.setLogLevel(loggerName, level);
//打印一下信息,可以不用
log(loggerName, strLevel);
}
}
}
private String parentStrLevel(String loggerName) {
String parentLoggerName = loggerName.contains(".") ? loggerName.substring(0, loggerName.lastIndexOf(".") : ROOT;
return loggingSystem.getLoggerConfiguration(parentLoggerName).getEffectiveLevel().name();
}
/**
* 獲取當前類的Logger對象有效日志級別對應的方法,進行日志輸出。舉例:
* 如果當前類的EffectiveLevel為WARN,則獲取的Method為 `org.slf4j.Logger#warn(JAVA.lang.String, java.lang.Object, java.lang.Object)`
* 目的是為了輸出`changed {} log level to:{}`這一行日志
*/
private void log(String loggerName, String strLevel) {
try {
LoggerConfiguration loggerConfiguration = loggingSystem.getLoggerConfiguration(log.getName());
Method method = log.getClass().getMethod(loggerConfiguration.getEffectiveLevel().name().toLowerCase(), String.class, Object.class, Object.class);
method.invoke(log, "changed {} log level to:{}", loggerName, strLevel);
} catch (Exception e) {
log.error("changed {} log level to:{} error", loggerName, strLevel, e);
}
}
}
大家可以看到,Apollo的方式最終也是LoggingSystem 這個類進行修改日志級別的操作。
那可能大家會問,Apollo在這里的作用是什么?
如果大家用過Apollo的話就會發現,在Apollo可視化管理系統中,每個系統都有一個實例列表,里面就是我們具體的應用地址。所以在這里你可以認為Apollo有類似注冊中心的作用,在我們應用啟動的時候,Apollo后臺就會記錄下來。
所以Apollo能實現集群的日志級別動態修改的原理就在這。是不是也很簡單呢?
使用 MQ + LoggingSystem
如果你們的系統沒有接入Apollo的話,那應該如何實現集群的日志級別動態修改呢?
MQ就是其中一個選擇。我簡單說一下實現思路吧,具體實現也很簡單,就留給大家去動手實踐啦。
- 暴露一個修改日志級別的接口
- 這個接口要做的是使用producer來發送一個廣播類型的MQ,注意了,是廣播類型的
- 在consumer里面通過LoggingSystem進行日志級別的修改即可。
是不是很簡單呢?
使用Springboot的 actuator 組件
其實這種方法和方式一是差不多的,只是actuator把接口通過端點Endpoints 的方式暴露出來。
至于什么是端點(Endpoints),我簡單介紹一下吧。
- 什么是端點
Endpoints 是 Actuator 的核心部分,它用來監視應用程序及交互,spring-boot-actuator中已經內置了非常多的Endpoints(health、info、beans、httptrace、shutdown等等),同時也允許我們擴展自己的端點。
- 端點的分類
Endpoints 分成兩類:原生端點和用戶自定義端點;自定義端點主要是指擴展性,用戶可以根據自己的實際應用,定義一些比較關心的指標,在運行期進行監控。
原生端點是在應用程序里提供的眾多 restful api 接口,通過它們可以監控應用程序運行時的內部狀況。
原生端點又可以分成三類:
- 應用配置類:可以查看應用在運行期間的靜態信息:例如自動配置信息、加載的spring bean信息、yml文件配置信息、環境信息、請求映射信息;
- 度量指標類:主要是運行期間的動態信息,例如堆棧、請求鏈、一些健康指標、metrics信息等
- 操作控制類:主要是指shutdown,用戶可以發送一個請求將應用的監控功能關閉。
我們這里修改配置文件用到的就是應用配置類的端點。
查看當前應用各包/類的日志級別
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.dylan.logging.TestController
可看到類似如下的結果:
{"configuredLevel":null,"effectiveLevel":"INFO"}
修改日志級別
POST方式,json格式的參數
example:http://localhost:8080/actuator/loggers/com.dylan.controller.IncreaseAgentController
actuator修改日志級別
但這種方式和方式一有同樣的局限性,就是只適合單機或者開發環境。如果想用這種方式的話可以接入Spring Boot Admin。通過后臺的方式進行管理。