log4j日志打印级别动态调整

1,为什么日志打印级别要动态调整?

  随着项目越来越大,访问量也越来越高,遇到问题时想要排查,可是日志一打开却刷的太快太快,不好排查问题,有的时候甚至因为短时间打印日志太多,严重影响了性能,这个时候日志的打印级别的动态调整就相当有必要了,在不重启项目的情况,不改动代码的情况下,通过Apollo动态配置就可以通过配置动态的调整日志的级别,可以精确到配置具体的类的日志打印级别。

 

2,动态调整的方案

  大致思路为在springboot项目启动之后,读取Apollo配置文件里的配置文件,总共有两个,一个是总的日志级别,一个是单独的类的配置,然后设置总的之后再设置具体到类的自定义的,同时注册一个监听器监听两个文件的变化,一旦有变化就重新设置一遍,是不是很简单呢?

  具体代码如下,将该类在启动时注册入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;
    }
}