实现Log4j和Logback的统一日志封装与动态日志级别调整

一、背景

项目中使用日志的地方非常广泛, 如果要做统一的处理,比如调整格式, 增加信息打印, 要修改很多地方,难以维护,所以需要能够将日志做一个统一的封装, 便于扩展使用;同时想做到动态调整日志级别,在生产环境修改日志级别, 将开销降到最低, 在出现故障时, 又可以重新调整日志级别而不需要重新启动服务, 便于问题的排查定位。

二、统一日志的封装

如果实现统一日志, 需要自己封装一层实现,但在实际日志打印中,往往会打印出所封装的类信息, 而并非实际调用的类信息, 该如何解决这个问题? 网上有很资料, 一般都是采用AOP+反射方式来修改类信息,这样虽能实现,但性能并不理想,这里我们采用最简单的方式直接封装slf4j中的Logger,这样可以支持Log4j和Logback。

  1. 先定义日志代理类

    LogProxy:

    import org.slf4j.Logger;
    import org.slf4j.Marker;
    ...
    public class LogProxy implements Logger {
    
        private Logger log;
    
        /**
         * 构造方法,注入类信息
         */
        public LogProxy(Logger logger) {
            log = logger;
        }
    
        /***
         * debug打印信息
         * @param msg
         */
        public void debug(String msg) {
            log.debug(msg);
        }
        ...
    }
    

    这里通过构造方法 ,将原始Logger注入进来。

  2. 定义日志工厂

    LogFactory

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    ...
    public class LogFactory {
    
    	/**
         * 创建封装的日志代理
         * @param clazz
         * @return
         */
        public static LogProxy getLogProxy(Class clazz) {
            Logger logger = LoggerFactory.getLogger(clazz);
            return new LogProxy(logger);
        }
    }
    
  3. 基准测试验证

    通过JMH做并发性能基准测试, 测试代码:

    @BenchmarkMode(Mode.AverageTime) // 指定mode为Mode.AverageTime
    @Benchmark
    @Fork(1)
    @Threads(5)
    @OutputTimeUnit(TimeUnit.MILLISECONDS) // 指定输出的耗时时长的单位
    @Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
    public void testLog() {
        ClassLog1Test c1 = new ClassLog1Test();
        c1.test();
        ClassLog2Test c2 = new ClassLog2Test();
        c2.test();
    }
    

    开启5个线程进行压测, 内部创建2个实现类,打印不同的日志信息,测试结果:
    在这里插入图片描述

    可以看到,日志信息是能够正确有效的输出,能打印出具体每个调用类的信息。

    查看性能基准测试结果: 在这里插入图片描述

    最终测试结果平均响应耗时在0.315ms,偏差0.174毫秒,整体性能也是比较高的。

三、基于Apollo的动态日志级别调整

基于Apollo配置中心, 实现对日志级别的动态调整,创建一个监听器, 接收Apollo变更的配置信息。

  1. 监听器

    LoggingAdjustListener:

    @Component
    public class LoggingAdjustListener {
        private final static LogProxy log = LogFactory.getLogProxy(LoggingAdjustListener.class);
    
        /**
         * 日志配置项的前缀
         */
        private static final String LOGGER_PREFIX = "logging.level.";
    
        @Resource
        private LoggingSystem loggingSystem;
    
        /**
         * Apollo事件监听处理
         * @param changeEvent
         * @throws Exception
         */
        @ApolloConfigChangeListener
        public 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) {
                        log.error("no loggerConfiguration with loggerName:{}", loggerName);
                        continue;
                    }
    
                    // 获得日志级别
                    ConfigChange change = changeEvent.getChange(key);
                    String newLevel = change.getNewValue();
                    if(null == newLevel) {
                         log.error("loggerConfiguration have no value:{}", loggerName);
                         continue;
                     }
                    LogLevel level = LogLevel.valueOf(newLevel.toUpperCase());
                    if (level == null) {
                        log.error("logger:[{}] current LogLevel is invalid:{}", loggerName, newLevel);
                        continue;
                    }
    
                    if (!isSupportLevel(level)) {
                        log.error("not support LogLevel:{}",  newLevel);
                        continue;
                    }
    
                    log.info("logger:[{}] current level:{}, changed to level:{}", loggerName, cfg.getEffectiveLevel(), newLevel);
    
                    // 设置日志级别到 LoggingSystem 中
                    loggingSystem.setLogLevel(loggerName, level);
    
                }
            }
        }
    
        /**
         * 判断是否支持的日志级别
         * @param level
         * @return
         */
        private boolean isSupportLevel(LogLevel level) {
            for (LogLevel logLevel : loggingSystem.getSupportedLogLevels()) {
                if (logLevel == level) {
                    return true;
                }
            }
            return false;
        }
    
    }
    

    这个监听器要注入Spring容器中,并且加上@ApolloConfigChangeListener注解才能生效,接收到时间消息后,会做校验判断, 然后通过loggingSystem重新设置日志级别,该接口是由Spring Boot提供,支持动态修改日志级别。

  2. 在Apollo中调整日志级别

    将com.mirson.log包的日志级别设为info, 查看控制台的日志打印: 在这里插入图片描述
    可以看到监听器能够正常接收Apollo推送的事件信息, 日志级别由warn调整到了info。

  3. 通过接口进行测试验证

    TestController:

    @RestController
    public class TestController {
    
    ...
    	@RequestMapping(value = "/test")
        @ResponseBody
        public String test() {
            log.debug("this is a debug");
            log.info("this is a info");
            log.warn("this is a warn");
            log.error("this is a error");
            return "execute ";
        }
    }
    

    查看控制台的日志输出,只输出了info以上级别的日志:
    在这里插入图片描述

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

麦神-mirson

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值