log4j日誌列印級別動態調整

幻真0526發表於2020-07-26

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;
    }
}

  

相關文章