news 2026/4/16 14:13:25

我司使用了两年的高效日志打印工具,非常好用!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
我司使用了两年的高效日志打印工具,非常好用!

为了更方便地排查问题,电商交易系统的日志中需要记录用户id和订单id等字段。然而,每次打印日志都需要手动设置用户id,这一过程非常繁琐,需要想个办法优化下。

log.warn("user:{}, orderId:{} 订单提单成功",userId, orderId); log.warn("user:{}, orderId:{} 订单支付成功",userId, orderId); log.warn("user:{}, orderId:{} 订单收到履约请求",userId, orderId); log.warn("user:{}, orderId:{} 订单履约成功",userId, orderId);

01

目标

打印日志时,自动填充用户id和订单Id等通参,无需手动指定

02

实现思路

  1. 日志模板中声明占位符 userId,orderId

  2. 在业务入口将userId放入到线程ThreadLocal本地变量中。

  3. 使用SpringAop+ 注解 自动将第二步的用户信息放到线程上下文

03

配置日志变量

%X{}可以自定义占位符,例如本例中 使用userId:%X{userId} orderId:%X{orderId},定义了userId和orderId两个占位符。

<?xml version="1.0" encoding="UTF-8"?> <Configurationstatus="info"> <Appenders> <Consolename="consoleAppender"target="SYSTEM_OUT"> <PatternLayoutpattern="%d{DEFAULT} [%t] %-5p - userId:%X{userId} orderId:%X{orderId} %m%n%ex"charset="UTF-8"/> </Console> </Appenders> <Loggers> <!-- Root Logger --> <AsyncRootlevel="info"includeLocation="true"> <appender-refref="consoleAppender"/> </AsyncRoot> </Loggers> </Configuration>

04

基于MDC 的上下文Map

为了给每个请求添加唯一标识,用户可将上下文信息放入MDC(Mapped Diagnostic Context)。

slfj 提供了MDC 类,可以将变量设置在线程上下文中,日志框架会自动将线程上下文中的变量放置到日志占位符中。Slf4j 作为java日志标准,log4j和logback都实现了slfj 日志标准。

MDC是基于每个线程进行管理的,允许每个服务器线程具有不同的MDC标记。MDC类中的put()和get()等操作仅影响当前线程的MDC。其他线程中的MDC不会受到影响,所以可以理解MDC是基于ThreadLocal的Map。

例如下面这种方式,打印日志的效果是这样的。

MDC.put("userId", userId); MDC.put("orderId", orderId); log.warn("订单履约完成");

当使用log.warn("订单履约完成")方式打印日志时,代码中会自动包含userId和 订单Id。

2024-08-17 21:35:38,284 [main] WARN - userId:32894934895 orderId:8497587947594859232 订单履约完成

接下来,声明一个注解加切面,自动将用户和订单信息放到日志占位符中。

05

注解 + SpringAop

通过注解的方式,在方法执行之前自动将UserId注入到MDC中。其中的难点在于如何获取到UserId。我的思路是,方法的入参中肯定包含了UserId。可以在注解中声明UserId的获取路径,在切面中获取到UserId,并将其注入到MDC中。

5.1 定义注解

@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public@interface UserLog { String userId()default ""; String orderId()default ""; }

使用时,要求输入userId属性的路径。例如UserOrder中包含userId和orderId属性,则像如下方式声明。

@UserLog(userId = "userId", orderId = "orderId") publicvoidorderPerform(UserOrder order){ log.warn("订单履约完成"); } @Data publicstaticclassUserOrder{ String userId; String orderId; }

5.2 定义切面

声明注解的Aop切面,在方法执行前,将UserId从入参中取出来,放到MDC中。全部代码如下

@Aspect @Component publicclassUserLogAspect{ @Pointcut("@annotation(UserLog) && execution(public * *(..))") publicvoidpointcut(){ } @Around(value = "pointcut()") public Object around(ProceedingJoinPoint joinPoint)throws Throwable { //无参方法不处理 Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); Object[] args = joinPoint.getArgs(); //获取注解 UserLog userLogAnnotation = method.getAnnotation(UserLog.class); if (userLogAnnotation != null && args != null && args.length > 0) { //使用工具类获取userId。 String userId = String.valueOf(PropertyUtils.getProperty(args[0], userLogAnnotation.userId())); String orderId = String.valueOf(PropertyUtils.getProperty(args[0], userLogAnnotation.orderId())); // 放到MDC中 MDC.put("userId", userId); MDC.put("orderId", orderId); } try { Object response = joinPoint.proceed(); return response; } catch (Exception e) { throw e; } finally { //清理MDC MDC.clear(); } } }

5.3 关键代码解读

5.3.1 获取UserLog注解

UserLog userLogAnnotation = method.getAnnotation(UserLog.class);

5.3.2 使用PropertyUtils.getProperty 获取userId

PropertyUtils.getProperty(args[0], userLogAnnotation.userId())

要注意 PropertyUtils 是commons-beanutils提供的工具类,可以指定属性的路径,自动提取属性值。如果存在多层关系,可以使用,级联取属性值。例如 info.userId,则从对象的info属性中取userId属性。

<dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.4</version> </dependency>

5.3.3 使用MDC设置变量和清除变量。

MDC.put("userId", userId); MDC.clear();

06

验证使用效果

6.1 声明业务Service

@Service publicclassOrderService{ publicstaticfinal Logger log = LoggerFactory.getLogger(OrderService.class); @UserLog(userId = "userId", orderId = "orderId") publicvoidorderPerform(UserOrder order){ log.warn("订单履约完成"); } @Data publicstaticclassUserOrder{ String userId; String orderId; } }

6.2 测试日志打印

@Test publicvoidtestUserLog(){ OrderService.UserOrder order = new OrderService.UserOrder(); order.setUserId("32894934895"); order.setOrderId("8497587947594859232"); orderService.orderPerform(order); }

6.3 日志效果

07

总结

不同的业务场景有不同的日志需求,一般情况下为了排查问题方便,需要唯一标识把一系列请求串联起来,使用 UserLog 注解+Aop ,自动将这部分默认参数放到日志中,可以简化业务日志打印,极大地提高了生产力。

另外大家可以自行扩展能力,例如自动打印出入参日志,自动上报监控打点等等。

各位朋友,以上工具的关键代码不超过30行,快点试试吧。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 6:26:57

AI 软件开发的管理

管理 AI 软件开发不仅是技术的堆叠&#xff0c;更是对不确定性、数据资产以及人机协作模式的深度治理。与传统软件工程相比&#xff0c;AI 项目管理的核心挑战在于&#xff1a;代码逻辑是确定的&#xff0c;但模型的输出是概率性的。以下是管理 AI 软件开发的关键维度&#xff…

作者头像 李华
网站建设 2026/4/16 13:05:50

Adobe Premiere Pro 26.0 - 原生支持Windows on ARM

一句话亮点&#xff1a;Adobe首次为ARM平台提供原生支持&#xff0c;适配高通Snapdragon X系列处理器设备。详细描述&#xff1a;Adobe发布26.0版本&#xff0c;标志着其创意软件生态系统正式进入ARM时代。Premiere Pro、After Effects、Audition及Media Encoder首次实现对Wind…

作者头像 李华
网站建设 2026/4/16 12:46:11

LLMs Are Dumber Than a House Cat

原文&#xff1a;towardsdatascience.com/llms-are-dumber-than-a-house-cat-81e7b3d63190 挫折&#xff1f;困惑&#xff1f;或许“缺乏优雅”是一个更好的表达。这是当你看到顶级科学家对已经理解的技术感到惊奇时所感受到的痛苦。 AI 影响者用惊奇牌来吸引点击&#xff0c;…

作者头像 李华
网站建设 2026/3/23 14:22:22

2026年牛客网最新版Java面试题及答案整理

Java学到什么程度可以面试工作&#xff1f; 要达到能够面试Java开发工作的水平&#xff0c;需要掌握以下几个方面的知识和技能&#xff1a; 1. 基础扎实&#xff1a;熟悉Java语法、面向对象编程概念、异常处理、I/O流等基础知识。这是所有Java开发者必备的基础&#xff0c;也…

作者头像 李华
网站建设 2026/4/12 3:40:03

深度测评9个AI论文平台,MBA高效写作必备!

深度测评9个AI论文平台&#xff0c;MBA高效写作必备&#xff01; AI 工具如何重塑论文写作的效率与质量 在当今快速发展的学术环境中&#xff0c;MBA 学生和研究人员正面临着越来越高的写作压力。从选题到开题&#xff0c;从初稿撰写到最终定稿&#xff0c;每一个环节都需要精准…

作者头像 李华
网站建设 2026/4/14 10:02:28

EpiQuik Plant ChIP Kit:高灵敏度与特异性,专为植物样本优化

在生命科学领域&#xff0c;表观遗传学的研究正以前所未有的速度推进。其中&#xff0c;染色质免疫共沉淀&#xff08;ChIP&#xff09;技术作为解析基因组与蛋白质相互作用的核心手段&#xff0c;广泛应用于动植物研究中。尤其是在植物研究中&#xff0c;由于其复杂的基因组结…

作者头像 李华