• 测试环境日志爆内存?我用一个工具类搞定了双日志体系的智能打印


    作为一名后端开发,不知道你有没有遇到过这样的头疼事:测试环境日志疯狂输出,磁盘内存分分钟告急,生产环境又必须保留全量日志方便排查问题

    最近我就被这个问题狠狠折磨了一把。我们项目里有大量定时任务,用的是XxlJobHelper打印任务日志,业务代码又依赖@Slf4j输出业务日志,两套日志体系各自为政。

    测试环境一跑起来,不管是调试信息、中间参数,还是无关紧要的流程日志,一股脑全打出来,没几天日志文件就占满了磁盘,排查问题时翻半天也找不到关键信息;可生产环境又不敢精简日志,万一出问题,少一行日志都可能定位不到故障根因。

    改吧?两套日志写法,到处都是log.info()XxlJobHelper.log(),手动改成本太高;不改吧,测试环境内存天天报警,运维天天找。

    思来想去,我决定写一个统一日志工具类UnifiedLogger,一把搞定两套日志的智能过滤:生产环境全量打印,测试环境只打关键日志,配置一行搞定,不用改动原有业务逻辑!

    一、痛点复盘:我们的日志到底乱在哪?

    先跟大家同步下我们项目的日志现状,相信很多团队都有同款问题:

    1. 双日志体系,无法统一管理
      业务层用@Slf4j打印业务日志,定时任务层用XxlJobHelper打印任务执行日志,两套API完全独立,想做日志过滤得分别改,工作量翻倍。

    2. 测试环境日志泛滥,内存占用爆表
      测试环境不需要调试参数、循环日志、中间状态这些冗余信息,但所有日志都原样输出,导致日志文件过大,磁盘IO高,排查问题效率极低。

    3. 生产环境不能精简,必须全量保留
      生产环境是底线,任何日志都不能丢,一旦故障,全量日志是定位问题的唯一依据,所以不能直接关闭低级别的日志。

    4. 改造难度大,不能影响原有业务
      项目已经上线,不能大规模修改原有日志代码,必须做到无侵入、兼容旧代码、一键切换

    面对这些问题,我定下了改造目标:
    ✅ 一套工具兼容@Slf4j+XxlJobHelper双日志
    ✅ 配置化控制环境,无需修改代码
    ✅ 测试环境只打印关键日志,生产环境全量打印
    ✅ 无侵入改造,原有代码可平滑迁移

    二、核心解决方案:UnifiedLogger 统一日志工具类

    我直接把完整的工具类代码贴出来,复制到项目里就能用,无需额外依赖,完美适配SpringBoot项目:

    package com.at.mrp.bll.log;
    
    import com.xxl.job.core.context.XxlJobHelper;
    import lombok.extern.slf4j.Slf4j;
    import org.slf4j.Logger;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    /**
     * 统一日志工具类
     * 支持 slf4j 和 XxlJob 日志的智能过滤
     *
     * 使用方式:
     * 1. UAT/测试环境:在消息中包含【重要】、【错误】、【失败】等关键词才会输出
     * 2. 生产环境:所有日志都输出
     *
     * 示例:
     * UnifiedLogger.info(log, "【重要】开始执行批量任务");
     * UnifiedLogger.info(log, "普通调试信息"); // UAT环境不输出,生产环境输出
     * UnifiedLogger.error(log, "发生异常: {}", errorMsg); // 所有环境都输出
     */
    @Slf4j
    @Component
    public class UnifiedLogger {
    
        private static String logEnv;
    
        @Value("${log.env:prod}")
        public void setLogEnv(String env) {
            UnifiedLogger.logEnv = env;
        }
    
        /**
         * 判断是否应该记录日志
         * UAT/测试环境:只记录包含特定关键词的日志
         * 生产环境:全部记录
         */
        private static boolean shouldLog(String message) {
            if ("prod".equalsIgnoreCase(logEnv)) {
                return true;
            }
    
            if (message == null || message.isEmpty()) {
                return false;
            }
    
            String upperMsg = message.toUpperCase();
    
            // UAT/测试环境:只记录包含以下关键词的日志
            return upperMsg.contains("【重要】")
                    || upperMsg.contains("【错误】")
                    || upperMsg.contains("【失败】")
                    || upperMsg.contains("【警告】")
                    || upperMsg.contains("【关键】")
                    || upperMsg.contains("[ERROR]")
                    || upperMsg.contains("[FAIL]")
                    || upperMsg.contains("[WARN]")
                    || upperMsg.contains("[IMPORTANT]");
        }
    
        // ==================== slf4j 日志方法 ====================
    
        /**
         * INFO 级别日志 - 智能过滤
         *
         * @param logger slf4j Logger 实例
         * @param message 日志消息
         * @param args 参数
         */
        public static void info(Logger logger, String message, Object... args) {
            if (shouldLog(message)) {
                logger.info(message, args);
            }
        }
    
        /**
         * WARN 级别日志 - 所有环境都记录(自动添加【警告】标记)
         *
         * @param logger slf4j Logger 实例
         * @param message 日志消息
         * @param args 参数
         */
        public static void warn(Logger logger, String message, Object... args) {
            String formattedMessage = "【警告】" + message;
            logger.warn(formattedMessage, args);
        }
    
        /**
         * ERROR 级别日志 - 所有环境都记录(自动添加【错误】标记)
         *
         * @param logger slf4j Logger 实例
         * @param message 日志消息
         * @param args 参数
         */
        public static void error(Logger logger, String message, Object... args) {
            String formattedMessage = "【错误】" + message;
            logger.error(formattedMessage, args);
        }
    
        /**
         * DEBUG 级别日志 - 仅生产环境记录
         *
         * @param logger slf4j Logger 实例
         * @param message 日志消息
         * @param args 参数
         */
        public static void debug(Logger logger, String message, Object... args) {
            if ("prod".equalsIgnoreCase(logEnv)) {
                logger.debug(message, args);
            }
        }
    
        // ==================== XxlJob 日志方法 ====================
    
        /**
         * XxlJob 智能日志记录
         * 根据环境和消息内容决定是否输出
         *
         * @param message 日志消息,可包含【重要】、【错误】等关键词
         * @param args 参数
         */
        public static void xxlLog(String message, Object... args) {
            if (shouldLog(message)) {
                XxlJobHelper.log(message, args);
            }
        }
    
        /**
         * XxlJob 错误日志(自动添加【错误】标记)
         */
        public static void xxlLogError(String message, Object... args) {
            String formattedMessage = "【错误】" + message;
            XxlJobHelper.log(formattedMessage, args);
        }
    
        /**
         * XxlJob 警告日志(自动添加【警告】标记)
         */
        public static void xxlLogWarn(String message, Object... args) {
            String formattedMessage = "【警告】" + message;
            XxlJobHelper.log(formattedMessage, args);
        }
    
        /**
         * XxlJob 重要日志(自动添加【重要】标记)
         */
        public static void xxlLogImportant(String message, Object... args) {
            String formattedMessage = "【重要】" + message;
            XxlJobHelper.log(formattedMessage, args);
        }
    
        /**
         * XxlJob 详细日志 - 仅生产环境记录
         */
        public static void xxlLogDetail(String message, Object... args) {
            if ("prod".equalsIgnoreCase(logEnv)) {
                XxlJobHelper.log(message, args);
            }
        }
    }
    

    核心规则

    1. 生产环境(prod):所有日志无条件打印,保证问题可追溯
    2. 测试/UAT环境(uat/test):只打印包含关键标记的日志,自动过滤冗余信息

    关键标记(测试环境生效)

    只要日志里包含这些关键词,测试环境就会打印,完美区分重要日志和调试日志:

    • 业务关键:【重要】、【关键】
    • 异常错误:【错误】、[ERROR]、【失败】、[FAIL]
    • 警告提示:【警告】、[WARN]

    一行配置搞定环境切换

    不用改代码,只需要在对应环境的配置文件加一行配置:
    测试/UAT环境

    log:
      env: uat
    

    生产环境

    log:
      env: prod
    

    工具类会自动读取配置,切换日志打印策略。

    三、实战使用:兼容双日志体系,用法超简单

    这个工具类最香的地方就是,同时支持@Slf4j和XxlJobHelper,原有代码改个调用方法就行,零学习成本。

    1. 业务日志(@Slf4j)用法

    原来的log.info()直接替换成工具类方法,自动适配环境:

    @Slf4j
    @Service
    public class OrderService {
    
        public void syncOrder() {
            // 【重要日志】测试/生产环境都会打印
            UnifiedLogger.info(log, "【重要】开始同步订单,总数量:{}", orderCount);
            
            // 普通调试日志:测试环境不打印,生产环境打印
            UnifiedLogger.info(log, "订单详情参数:{}", JSON.toJSONString(order));
            
            // 错误日志:自动加【错误】标记,所有环境必打印
            UnifiedLogger.error(log, "订单同步失败:{}", e.getMessage());
            
            // 警告日志:自动加【警告】标记,所有环境必打印
            UnifiedLogger.warn(log, "订单库存不足,请及时处理");
        }
    }
    

    2. 定时任务日志(XxlJobHelper)用法

    定时任务里的XxlJobHelper.log()也能一键替换:

    @XxlJob("batchTaskJob")
    public void batchTask() {
        // 重要任务日志:全环境打印
        UnifiedLogger.xxlLog("【重要】定时任务开始执行");
        
        // 详细任务日志:仅生产环境打印
        UnifiedLogger.xxlLog("任务入参:{}", param);
        
        // 任务错误日志:全环境打印
        UnifiedLogger.xxlLogError("定时任务执行失败:{}", errorMsg);
    }
    

    工具类核心亮点

    1. 自动加标记:error/warn方法自动拼接【错误】【警告】,不用手动写
    2. 智能过滤:测试环境自动过滤无关键词的普通日志
    3. 无性能损耗:生产环境直接打印,无额外逻辑开销
    4. 静态方法:直接调用,无需注入,使用超方便

    四、改造效果:测试环境日志直接瘦身90%

    上线这个工具类后,效果立竿见影:
    测试环境日志量直接减少90%,磁盘内存不再报警
    排查问题效率翻倍:打开日志全是关键信息,没有冗余干扰
    生产环境完全无影响:全量日志正常保留
    改造零风险:原有代码逐步迁移,不影响业务运行

    最关键的是,后续新增日志只需要按照规则加关键词,不用再担心日志泛滥问题,一次封装,长期受益。

    五、总结与最佳实践

    最后给大家总结下使用心得:

    1. 关键业务流程:务必加【重要】标记,保证测试环境可观测
    2. 异常/错误:统一用error方法,自动标记,全环境必打
    3. 调试/参数日志:不加标记,测试环境自动过滤,不占用内存
    4. 环境切换:纯配置化,一行代码搞定,不用重新发布

    如果你也在被测试环境日志泛滥、双日志体系难管理的问题困扰,不妨试试这个统一日志工具类,低成本解决大麻烦!

    我一直觉得,好的工具不是花里胡哨的功能,而是能实实在在解决日常开发的痛点,让我们少加班、多高效。这个小小的UnifiedLogger,就是我为团队日志痛点交出的最优解~

  • 相关阅读:
    Day741.Redis消息队列 -Redis 核心技术与实战
    【Webpack】Webpack 优化
    随机抽奖转盘微信小程序项目源码
    ardupilot开发 --- EKF 篇
    将矩阵按对角线排序(c++题解)
    从零搭建云原生技术kubernetes(K8S)环境-通过kubesPhere的AllInOne方式
    C# OpenCvSharp Mat操作-操作符重载
    Cesium关于Entity中的parent、isShowing、entityCollection和监听事件的探讨
    Kubernetes(k8s)的Pod资源清单spec.containers属性详细讲解
    两种解法解决LCR 008. 长度最小的子数组【C++】
  • 原文地址:https://www.cnblogs.com/sun-10387834/p/20082318