动态刷新日志级别
概述
日志模塊是每個項目中必須的,用來記錄程序運行中的相關信息。一般在開發環境下使用DEBUG級別的日志輸出,為了方便查看問題,而在線上一般都使用INFO級別的日志,主要記錄業務操作的日志。那么問題來了,當線上環境出現問題希望輸出DEBUG日志信息輔助排查的時候怎么辦呢?修改配置文件,重新打包然后上傳重啟線上環境,但是這么做不優雅 而且可能會破壞現場。
本文介紹一種實現方案:通過Apollo配置中心來實現 動態調整線上日志級別。
日志級別
不同的日志框架支持不同的日志級別,其中比較常見的就是Log4j和Logback。
在Log4j中支持8種日志級別,優先級從高到低依次為:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL。
Logback中支持7種日志級別,優先級從高到低分別是:OFF、ERROR、WARN、INFO、DEBUG、TRACE、ALL。
可以看到常見的ERROR、WARN、INFO、DEBUG,這兩者都是支持的。
所謂設置日志的輸出級別表示的是輸出的日志的最低級別,也就是說,如果我們把級別設置成INFO,那么包括INFO在內以及比INFO優先級高的級別的日志都可以輸出。
Spring Boot對日志的支持
Spring Boot 對log做了統一封裝,代碼在 org.springframework.boot.logging 包中,結構如下:
其中 org.springframework.boot.logging.LoggingSystem 是SpringBoot對日志系統的抽象,是一個頂層的抽象類,有很多具體的實現:
通過上圖,我們可以發現目前SpringBoot目前支持3種類型的日志,分別是
LoggingSystem是個抽象類,內部有這幾個方法:
代碼
我們可以將日志級別配置保存在Apollo配置中心中, 當日志級別發生變更時,我們需要通過監聽該配置的變更,設置應用中的 Logger 的日志級別,從而后續的日志打印可以根據新的日志級別
@Slf4j @Component public class LoggingSystemAdjustListener {/*** 日志配置項的前綴*/private static final String LOGGER_PREFIX = "logging.level.";@Resourceprivate LoggingSystem loggingSystem;// By default only read config in "application"@ApolloConfigChangeListenerpublic void onChange(ConfigChangeEvent changeEvent) throws Exception {// <Y> 遍歷配置集的每個配置項,判斷是否是 logging.level 配置項for (String key : changeEvent.changedKeys()) {// 如果是 logging.level 配置項,則設置其對應的日志級別if (key.startsWith(LOGGER_PREFIX)) {String loggerName = key.replace(LOGGER_PREFIX, "");//LoggerConfiguration cfg = loggingSystem.getLoggerConfiguration(loggerName);if (cfg == null) {if (log.isErrorEnabled()) {log.error("no loggerConfiguration with loggerName:{}", loggerName);}continue;}// 獲得日志級別ConfigChange change = changeEvent.getChange(key);// the newLevel could be null if the config is deleted from apollo// in this case we update it same as "root" levelString newLevel = change.getNewValue();LogLevel level = null;// config is deleted or kept as empty stringif (newLevel == null || newLevel.isEmpty()) {level = getFallbackLogLevel(ROOT_LOGGER_NAME);} else {try {level = LogLevel.valueOf(newLevel.toUpperCase());} catch (IllegalArgumentException e) {// do nothing}}if (level == null) {if (log.isErrorEnabled()) {log.error("logger:[{}]current LogLevel is invalid:{}", loggerName, newLevel);}continue;}if (!isSupportLevel(level)) {if (log.isErrorEnabled()) {log.error("LoggingSystem:[] not support current LogLevel:{}",loggingSystem.getClass().getName(), newLevel);}continue;}if (log.isInfoEnabled()) {log.info("logger:[{}] current effective level:{}, to be changed to level:{}", loggerName,cfg.getEffectiveLevel(), newLevel);}// 基于springboot的日志抽象類,設置日志級別到 LoggingSystem 中loggingSystem.setLogLevel(loggerName, level);}}}private boolean isSupportLevel(LogLevel level) {for (LogLevel ll : loggingSystem.getSupportedLogLevels()) {if (ll == level) {return true;}}return false;}public LogLevel getFallbackLogLevel(String loggerName) {LoggerConfiguration cfg = loggingSystem.getLoggerConfiguration(loggerName);if (cfg == null) {if (log.isErrorEnabled()) {log.error("no loggerConfiguration with loggerName:{}", loggerName);}// use WARN as unexpected casereturn LogLevel.WARN;}return cfg.getEffectiveLevel();}}基于spring的日志支持,我們還可以在logback中通過logger標簽對某一個包甚至類單獨配置日志級別
<logger name="com.ethan.demo.log.controller" level="INFO" additivity="true"><appender-ref ref="${CONSOLE_APPENDER}"/></logger>總結
- 上一篇: MySQL数据导入导出的两种方式
- 下一篇: 责任分配矩阵