1,為什麼日誌列印級別要動態調整?
隨著專案越來越大,訪問量也越來越高,遇到問題時想要排查,可是日誌一開啟卻刷的太快太快,不好排查問題,有的時候甚至因為短時間列印日誌太多,嚴重影響了效能,這個時候日誌的列印級別的動態調整就相當有必要了,在不重啟專案的情況,不改動程式碼的情況下,通過Apollo動態配置就可以通過配置動態的調整日誌的級別,可以精確到配置具體的類的日誌列印級別。
2,動態調整的方案
大致思路為在springboot專案啟動之後,讀取Apollo配置檔案裡的配置檔案,總共有兩個,一個是總的日誌級別,一個是單獨的類的配置,然後設定總的之後再設定具體到類的自定義的,同時註冊一個監聽器監聽兩個檔案的變化,一旦有變化就重新設定一遍,是不是很簡單呢?
在專案中使用日誌的方式請統一使用Slf4j門面模式。具體程式碼如下,將該類在啟動時註冊入spring容器就行。值得注意的是該類中的initCustomClass()方法,該方法是因為有很多類在springboot啟動時沒有初始化,那麼也就沒有註冊入LoggerContext的屬性中,所以是無法設定的,通過手動初始化該類的形式來初始化之後重新設定一遍。在詳細的配置檔案中是支援正規表示式來匹配的。
@Service @Slf4j public class LoggerConfiguration implements ConfigChangeListener, ApplicationListener<ContextRefreshedEvent> { private static final String LOGGER_LEVEL = "logger_level"; private static final String LOGGER_LEVEL_DETAIL = "logger_level_detail"; private static final String DEFAULT_LEVEL = "error"; private static final String INFO_LEVEL = "info"; private Config applicationConfig; public LoggerConfiguration(Config applicationConfig) { this.applicationConfig = applicationConfig; } @Override public void onChange(ConfigChangeEvent changeEvent) { if (changeEvent.changedKeys().contains(LOGGER_LEVEL)) { String newValue = changeEvent.getChange(LOGGER_LEVEL).getNewValue(); try { log.info("update rootLoggerLevel {}", newValue); setRootLoggerLevel(newValue); } catch (Exception e) { log.error("loggerLevel onChange failed {}", ExceptionUtil.stacktraceToString(e)); } } if (changeEvent.changedKeys().contains(LOGGER_LEVEL_DETAIL)) { String newValue = changeEvent.getChange(LOGGER_LEVEL_DETAIL).getNewValue(); try { log.info("update loggerLevel detail {}", newValue); parseLoggerConfig(newValue); } catch (Exception e) { log.error("loggerLevel detail onChange failed {}", ExceptionUtil.stacktraceToString(e)); } } } @Override public void onApplicationEvent(ContextRefreshedEvent event) { try { // 初始化風控監聽action配置 String level = applicationConfig.getProperty(LOGGER_LEVEL, DEFAULT_LEVEL); log.info("init root loggerLevel {}", level); setRootLoggerLevel(level); // 註冊配置監聽 applicationConfig.addChangeListener(this); } catch (Exception e) { log.error("loggerLevel init failed {}", ExceptionUtil.stacktraceToString(e)); } } /** * 將未註冊進日誌容器的類處初始化 * * @param className */ private boolean initCustomClass(String className) { try { Class.forName(className); return true; } catch (Exception e) { log.error("init {} failed", className); return false; } } private void setRootLoggerLevel(String level) { try { Level newLevel = Level.valueOf(level); LoggerContext logContext = LoggerContext.getContext(false); Configuration configuration = logContext.getConfiguration(); LoggerConfig loggerConfig = configuration.getRootLogger(); loggerConfig.setLevel(newLevel); logContext.updateLoggers(); //update後會覆蓋定製化的 setLoggerLevel(this.getClass().getName(), INFO_LEVEL); reConfig(); log.info("update rootLoggerLevel {}", level); } catch (Exception e) { log.error("setRootLoggerLevel failed {}", ExceptionUtil.stacktraceToString(e)); } } private void setLoggerLevel(String name, String level) { try { Level newLevel = Level.valueOf(level); LoggerContext logContext = LoggerContext.getContext(false); //是否沒有匹配到 boolean flag = false; if (logContext.hasLogger(name)) { //精確匹配 Logger logger = logContext.getLogger(name); logger.setLevel(newLevel); log.info("update {} logger level {}", name, level); flag = true; } else { //正則匹配 Collection<Logger> loggers = logContext.getLoggers(); for (Logger logger : loggers) { if (Pattern.matches(name, logger.getName())) { logger.setLevel(newLevel); log.info("update {} logger level {}", name, level); flag = true; } } } //該類未註冊就註冊,註冊失敗那麼也就不再繼續設定 if (!flag && initCustomClass(name)) { //初始化未註冊的類 setLoggerLevel(name, level); } } catch (Exception e) { log.error("setLoggerLevel failed {}", ExceptionUtil.stacktraceToString(e)); } } private void reConfig() { String detail = applicationConfig.getProperty(LOGGER_LEVEL_DETAIL, ""); if (StringUtils.isNotEmpty(detail)) { parseLoggerConfig(detail); } } private void parseLoggerConfig(String value) { Map<String, String> config = JSON.parseObject(value, Map.class); if (config == null) { return; } config.forEach((k, v) -> setLoggerLevel(k, v)); } public void setApplicationConfig(Config applicationConfig) { this.applicationConfig = applicationConfig; } }