当前位置: 首页 > news >正文

【log4j2】将运行时变量注入日志、附性能对比与生产案例(动态日志实战)

在这里插入图片描述

文章目录

    • 为什么需要动态日志
    • ThreadContext方式
    • 自定义Converter方式
    • 生产环境实践
    • 性能对比(ThreadContext vs 直接传参)

更多相关内容可查看

业务背景:在已成型很多年的项目的基础上,进行日志改造,例如在程序中已经通过lg4j2打印了很多日志了,通过改每一条输出日志不显示,可以通过插件挂载,动态读取的方式来改造日志

对log4j2日志不了解的可查看主页log4j2相关文章,此篇不做详细log4j2框架讲解

官网地址:https://www.docs4dev.com/docs/zh/log4j2/2.x/all/manual-index.html


为什么需要动态日志

在分布式系统中,我们经常需要在日志中自动记录以下信息:

• 用户ID、请求链路TraceID

• 线程池任务编号

• 动态业务参数(如订单号、商户ID)

传统硬编码方式:

logger.info("用户{}下单,订单号:{}", userId, orderId);  // 每个日志都要手动传参

痛点:

  1. 代码重复率高
  2. 修改成本大(如新增字段需修改所有日志点)
  3. 无法统一处理敏感信息脱敏

ThreadContext方式

ThreadContext(旧称MDC)是Log4j2提供的线程级变量存储,原理如下:

组件作用
ThreadContext基于ThreadLocal的键值对存储,生命周期与线程绑定
%X{key}日志模板中通过占位符引用变量
Put/Remove变量操作API,支持嵌套调用(如子线程继承父线程上下文)

直接put后要clear

// 注入变量
ThreadContext.put("traceId", UUID.randomUUID().toString());
ThreadContext.put("userId", "U10001");// 日志模板(log4j2.xml配置)
<PatternLayout pattern="%d [%X{traceId}] [%X{userId}] %m%n"/>// 输出效果
2023-08-20 14:00 [a1b2c3d4] [U10001] 用户登录成功try {ThreadContext.put("key", "value");logger.info("业务操作...");  // 可读取%X{key}
} finally {ThreadContext.clear();  // 必须清理避免内存泄漏!
}//也可以通过过滤器在请求结束后统一清理
public class CleanContextFilter implements Filter {@Overridepublic void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {try {chain.doFilter(req, res);} finally {ThreadContext.clearAll(); // 确保每个请求结束后清理}}
}

Spring 环境Aop实现

@Aspect
@Component
public class ThreadContextAspect {@Around("execution(* com..service.*.*(..))")public Object cleanContext(ProceedingJoinPoint pjp) throws Throwable {try {// 可以在此统一注入公共变量ThreadContext.put("app", "order-service");return pjp.proceed();} finally {ThreadContext.clearAll();}}
}

自定义Converter方式

通过自定义Converter实现:

<PatternLayout pattern="%d %X{traceId} %mask{%X{phoneNumber}} %m%n"/>
@Plugin(name = "mask", category = "Converter")
public class MaskConverter extends LogEventPatternConverter {@Overridepublic void format(LogEvent event, StringBuilder output) {String phone = event.getContextData().getValue("phoneNumber");output.append(phone.substring(0, 3) + "****" + phone.substring(7));}
}

与APM系统集成

// 将ThreadContext变量注入Span标签(SkyWalking示例)
ActiveSpan.tag("user_id", ThreadContext.get("userId"));

生产环境实践

问题:一个产品多个客户,不同的客户不同的日志需求,所以不能改原来的代码,只能通过别的方式去解决这个问题

//在流量的入口放入,这样在进行请求或者响应的时候就会自己注入日志当中
ThreadContext.put("GlobalSeqNo", businessSerialNumber);
ThreadContext.put("UUID", UUID.randomUUID().toString());
//在流量的出口clear,避免内存泄漏
ThreadContext.clearAll();

具体的logj2配置如下,其中%X{UUID}|%X{GlobalSeqNo}配置就是通过这个功能去实现的

<?xml version="1.0" encoding="UTF-8"?>
<!--status : 这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,会看到log4j2内部各种详细输出monitorInterval : Log4j能够自动检测修改配置文件和重新配置本身, 设置间隔秒数。
-->
<Configuration status="WARN" monitorInterval="10" packages="com.server.util"><Properties><!-- 配置日志文件输出目录 --><Property name="LOG_HOME">/opt/log</Property><Property name="ARCHIVE_HOME">${LOG_HOME}/aaa</Property><Property name="APP_Name" value="sss"/><!-- 版本号属性 --><Property name="LOG_VERSION">v01</Property><!-- 主机号 --><Property name="HOSTNAME">xxx</Property><Property name="Pattern">[%d{yyyy-MM-dd HH:mm:ss.SSS}]|%level|${APP_Name}|%X{UUID}|%X{GlobalSeqNo}|%pid|%thread|%class{1.}:%line|%X{NestedLevel}|%msg%n</Property></Properties><Appenders><!--这个输出控制台的配置--><Console name="STDOUT" target="SYSTEM_OUT"><!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) --><ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/><!-- 输出日志的格式 --><PatternLayout pattern="${Pattern}"/></Console><RollingRandomAccessFile name="FILE"immediateFlush="true" fileName="${LOG_HOME}/proc-${xxx}-${APP_Name}-${HOSTNAME}-1.log"filePattern="${ARCHIVE_HOME}/proc-${xxx}-${APP_Name}-${HOSTNAME}-%d{yyyyMMdd}-%i.log.zip"></RollingRandomAccessFile></Appenders>

性能对比(ThreadContext vs 直接传参)

• 日志量:100万条

• 线程数:50并发

• 硬件:4C8G VM

结果数据

方式耗时(ms)内存占用(MB)适用场景
直接传参1,200150简单业务,少量日志
ThreadContext1,450180需要上下文传递的复杂场景
ThreadContext + 清空1,500160严格防泄漏的生产环境

结论:
ThreadContext有约20%的性能损耗,但在需要跨线程/组件传递上下文时不可替代。

• 核心价值:ThreadContext实现了日志与业务代码解耦,是分布式追踪的基石 • 适用场景:微服务链路追踪、多线程任务标记、审计日志 • 避坑指南:务必及时清理变量,异步场景使用`wrap()`方法

生产建议:在网关层统一注入基础字段(如traceId),业务层按需添加动态参数。

http://www.xdnf.cn/news/1005679.html

相关文章:

  • JFLASH 提示license 配置操作 Sorry,no valid license for I-Flash found.
  • Trae重磅升级
  • Python 字典
  • 第六节 工程化与高级特性-TS配置选项解析
  • AUTOSAR图解==>AUTOSAR_TR_InteroperabilityOfAutosarTools
  • Rust 通用代码生成器:莲花,红莲尝鲜版三十六,哑数据模式图片初始化功能介绍
  • 测试完成的标准是什么?
  • Vue3项目与桌面端(C++)通过Websocket 对接接口方案实现
  • 【源码+文档+调试讲解】自习室系统
  • HALCON第二讲->预处理
  • vue中的doSave()方法
  • Excel大厂自动化报表实战(互联网金融-数据分析周报制作上)
  • 桥接模式(Bridge Pattern)
  • FastDFS
  • Flash数据写入及ECC纠错关键函数:Fapi_issueProgrammingCommand()
  • 【微软RDP协议】微软RDP协议技术架构特点与跨地域应用实践
  • Qt 环境搭建全流程详解:从下载到创建首个项目
  • 服务器部署普罗米修斯监控CentOS8
  • 2025 TechViz 新功能:3D协作,技术进化,体验升级
  • TypeScript 项目配置
  • 时事新闻:2025 伦敦科技周
  • Odoo 基于规则的线索自动分配实践指南
  • C++ STL容器汇总
  • 大中台应用的层次抽象
  • bisheng系列(三)- 本地部署(后端 1.2版本)
  • 亚马逊云服务器配置推荐
  • PCL 导入VS配置的大量依赖项名称快速读取
  • LLaMA Factory 微调教程:如何构建高质量数据集?
  • 重启杀手--误操作梳理
  • 命令模式:解耦请求与执行的优雅之道