惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog RSS Feed
博客园_首页
N
News | PayPal Newsroom
有赞技术团队
有赞技术团队
The Hacker News
The Hacker News
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
S
SegmentFault 最新的问题
Jina AI
Jina AI
人人都是产品经理
人人都是产品经理
P
Privacy & Cybersecurity Law Blog
AI
AI
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Schneier on Security
Schneier on Security
博客园 - 三生石上(FineUI控件)
月光博客
月光博客
量子位
Forbes - Security
Forbes - Security
爱范儿
爱范儿
云风的 BLOG
云风的 BLOG
SecWiki News
SecWiki News
Last Week in AI
Last Week in AI
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Tor Project blog
Recorded Future
Recorded Future
A
About on SuperTechFans
J
Java Code Geeks
The Register - Security
The Register - Security
PCI Perspectives
PCI Perspectives
H
Hacker News: Front Page
V2EX - 技术
V2EX - 技术
S
Secure Thoughts
V
Vulnerabilities – Threatpost
Hacker News: Ask HN
Hacker News: Ask HN
MongoDB | Blog
MongoDB | Blog
N
Netflix TechBlog - Medium
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Scott Helme
Scott Helme
T
The Exploit Database - CXSecurity.com
Y
Y Combinator Blog
AWS News Blog
AWS News Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
IT之家
IT之家
T
The Blog of Author Tim Ferriss
G
Google Developers Blog
C
CERT Recently Published Vulnerability Notes
L
LangChain Blog
F
Full Disclosure
Application and Cybersecurity Blog
Application and Cybersecurity Blog
The GitHub Blog
The GitHub Blog

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解 【002】HTTPS 粗解:证书、TLS 握手与对后端配置的影响 Hermes Agent 一周暴涨五万 Star,但我劝你别急着追 明明连接的是Redis的DB0,为什么能查到DB3的数据? 【从0到1构建一个ClaudeAgent】协作-Agent团队 熟悉电子元器件之后,电子小白下一步该怎么走? MAF快速入门(23)通过C#类定义Skills .NET 高级开发 | 手写一个对象映射框架 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 mysqldump 参数拾遗:在遗忘与铭记之间 C# .NET 周刊|2026年3月5期 Claude code入门 - 陈彦斌 一文学习入门 ThingsBoard 开源物联网平台 GitHub 热门项目 | 2026年04月16日 如何为GIT设置全局勾子,为每次提交追加信息 Number.isFinite和isFinite与isNaN()和Number.isNaN的区别 PortSwigger SQL注入LAB2 推荐一个测试人必备的Skills,从功能到性能全搞定(附详细实操和安装下载方式) 筑基期:掌握Odoo基础核心知识点02(Odoo XML 开发方式详解) GLM模型这么火,咱们用vllm也咧一个呗! 深入理解 AbortController:从底层原理到跨语言设计哲学 字符串学习笔记 多租户系统框架的基础模块设计和分析设计 Apache SeaTunnel Zeta 为什么能做到“又快又稳”? AI开发-python-LangGraph框架(3-26-LangGraph基本概念及第一个简单样例) Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 ElasticSearch7.X版本配置密码 用Manim实现动态交点计算--从一个动点问题说起 团结引擎+Addressable+Instant Game打包抖音小游戏 function call 实战:让 LLM 自动判断 pod 异常、调用日志工具并完成故障分析 bubseek —— 让 Agent 的足迹,变成团队的洞察 通过 C# 读取并导出 PDF 书签 如何用 GitHub Actions 实现 Steam 自动化发布 【从0到1构建一个ClaudeAgent】并发-后台任务 .NET 高级开发 | 定制 ASP.NET Core 框架 电子小白:什么是运算放大器(运放) zero2Agent:面向大厂面试的 Agent 工程教程,从概念到生产的完整学习路线 堆上的ORW HC32F460 USB CDC通信异常:非对齐访问异常排查 20260413-Hyperbridge 攻击事件:发生在默克尔山上的验证绕过 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 深度学习进阶(八)Swin Transformer 最小二乘问题详解19:带先验约束的增量式SFM优化与实现 SnapTranslate 3.0 正式发布:全局划词翻译 + 完整英语学习闭环,一站式搞定查词、记词、复习 工作的意义、工作的困难认知再思考 .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 开了 TUN 模式还是直连?90% 的人都踩过这个坑 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术
测试环境日志爆内存?我用一个工具类搞定了双日志体系的智能打印
佛祖让我来巡山 · 2026-05-19 · via 博客园_首页

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

最近我就被这个问题狠狠折磨了一把。我们项目里有大量定时任务,用的是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,就是我为团队日志痛点交出的最优解~