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

为了更方便地排查问题,电商交易系统的日志中需要记录用户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);

1. 目标

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

2. 实现思路

  • 日志模板中声明占位符 userIdorderId

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

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

3. 配置日志变量,读取上下文变量

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

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

4. 基于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 订单履约完成

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

5. 注解 + SpringAop,自动将UserId放到MDC

通过注解的方式,在方法执行之前自动将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中包含userIdorderId属性,则像如下方式声明。

@UserLog(userId = "userId", orderId = "orderId")
public void orderPerform(UserOrder order) {log.warn("订单履约完成");
}@Data
public static class UserOrder {String userId;String orderId;
}
5.2 定义切面

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

@Aspect
@Component
public class UserLogAspect {@Pointcut("@annotation(UserLog) && execution(public * *(..))")public void pointcut() {}@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 {//清理MDCMDC.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();

6. 验证使用效果

6.1 声明业务Service
@Service
public class OrderService {public static final Logger log = LoggerFactory.getLogger(OrderService.class);@UserLog(userId = "userId", orderId = "orderId")public void orderPerform(UserOrder order) {log.warn("订单履约完成");}@Datapublic static class UserOrder {String userId;String orderId;}
}
6.2 测试日志打印
@Test
public void testUserLog() {OrderService.UserOrder order = new OrderService.UserOrder();order.setUserId("32894934895");order.setOrderId("8497587947594859232");orderService.orderPerform(order);
}
6.3 日志效果

图片

7. 总结

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

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

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

最后说一句(求关注!别白嫖!)

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/418617.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux服务器之top命令详解

top&#xff1a;系统资源管理器 top命令类似于windows的任务管理器&#xff0c;可以查看内存、cpu、进程等信息(动态查看系统资源信息)在linux系统中常用top命令查看资源性能分析工具 一、参数释义&#xff1a; 第一行 系统时间和平均负载 top&#xff1a;名称22:12:46&#…

Spring Boot 部署方案!打包 + Shell 脚本详解

本篇和大家分享的是springboot打包并结合shell脚本命令部署&#xff0c;重点在分享一个shell程序启动工具&#xff0c;希望能便利工作&#xff1b; profiles指定不同环境的配置 maven-assembly-plugin打发布压缩包 分享shenniu_publish.sh程序启动工具 linux上使用shenniu_p…

一文梳理RAG(检索增强生成)的现状与挑战

一 RAG简介 大模型相较于过去的语言模型具备更加强大的能力&#xff0c;但在实际应用中&#xff0c;例如在准确性、知识更新速度和答案透明度方面&#xff0c;仍存在不少问题&#xff0c;比如典型的幻觉现象。因此&#xff0c;检索增强生成 (Retrieval-Augmented Generation, …

哪种超声波清洗机效果好?较好的超声波眼镜清洗机品牌推荐

作为一名拥有20年戴镜经验的眼镜爱好者&#xff0c;我深深体会到眼镜清洁的挑战&#xff1a;微小缝隙里的污垢难以触及&#xff0c;频繁的脏污让我苦于找不到清洁时机&#xff0c;而用力不当的擦拭方法更是可能对眼镜特别是镜片造成伤害&#xff0c;这确实让人感到苦恼&#xf…

Java专栏介绍

专栏导读 在当今这个技术飞速发展的时代&#xff0c;Java作为一门成熟且广泛应用的编程语言&#xff0c;一直是软件开发领域的中坚力量。本“Java技术”专栏旨在帮助读者深入理解Java编程语言的精髓&#xff0c;掌握其核心概念与高级特性&#xff0c;并通过实战案例提升编程技…

字符编码转换

文章目录 1. 背景2. 解决方案3. 编码转换实现3.1 shell实现3.2 python实现3.3 开源工具实现 4. 常见中文字符编码介绍4.1 字符编码解决什么问题4.2 常见的中文字符编码4.3 常见中文字符编码关系4.4 unicide字符集与utf-8 1. 背景 在团队合作开发中&#xff0c;经常发现组员的代…

Redis安装步骤——离线安装与在线安装详解

Linux环境下Redis的离线安装与在线安装详细步骤 环境信息一、离线安装1、安装环境2、下载redis安装包3、上传到服务器并解压4、编译redis5、安装redis6、配置redis&#xff08;基础配置&#xff09;7、启动redis8、本机访问redis9、远程访问redis 二、在线安装1、更新yum源2、安…

k8s 高级调度

搞懂Kubernetes调度 K8S调度器Kube-schduler的主要作用是将新创建的Pod调度到集群中的合适节点上运行。kube-scheduler的调度算法非常灵活&#xff0c;可以根据不同的需求进行自定义配置&#xff0c;比如资源限制、亲和性和反亲和性等。 kube-scheduler的工作原理如下&#x…

基于SpringBoot+Vue+MySQL的宿舍维修管理系统

系统展示 前台界面 管理员界面 维修员界面 学生界面 系统背景 在当今高校后勤管理的日益精细化与智能化背景下&#xff0c;宿舍维修管理系统作为提升校园生活品质、优化资源配置的关键环节&#xff0c;其重要性日益凸显。随着学生规模的扩大及住宿条件的不断提升&#xff0c;宿…

Qt/C++ 个人开源项目#串口助手(源码与发布链接)

一、项目概述 该串口助手工具基于Qt/C开发&#xff0c;专为简化串口通信调试与开发而设计&#xff0c;适合新手快速上手。工具具有直观的用户界面和丰富的功能&#xff0c;旨在帮助用户与串口设备建立可靠通信&#xff0c;便于调试、数据传输和分析。 二、主要功能 波特率&a…

【Hadoop|MapReduce篇】MapReduce概述

1. MapReduce定义 MapReduce是一个分布式运算程序的编程框架&#xff0c;是用户开发“基于Hadoop的数据分析应用”的核心框架。 MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序&#xff0c;并发运行在一个Hadoop集群上。 2. Map…

视频汇聚平台LntonAIServer视频质量诊断功能--偏色检测与噪声检测

随着视频监控技术的不断进步&#xff0c;视频质量成为了决定监控系统性能的关键因素之一。LntonAIServer新增的视频质量诊断功能&#xff0c;特别是偏色检测和噪声检测&#xff0c;进一步强化了视频监控系统的可靠性和实用性。下面我们将详细介绍这两项功能的技术细节、应用场景…

2158. 直播获奖(live)

代码 #include<bits/stdc.h> using namespace std; int main() {int n,w,a[100000],cnt[601]{0},i,j,s;cin>>n>>w;for(i0;i<n;i){scanf("%d",&a[i]);cnt[a[i]];int x(i1)*w/100;if(!x) x1;for(j600,s0;j>0;j--){scnt[j];if(s>x){cou…

linux编译器——gcc/g++

1.gcc linux上先要安装&#xff0c; sudo yum install gcc gcc --version 可以查看当前的版本 &#xff0c;我们默认安装的是4.8.5的版本&#xff0c;比较低&#xff0c; gcc test.c -stdc99 可以使他支持更高版本的c标准 -o 可以殖指明生成文件的名字&#xff0c;可以自己…

重启顺风车的背后,是高德难掩的“野心”

以史鉴今&#xff0c;我们往往可以从今天的事情中&#xff0c;看到古人的智慧&#xff0c;也看到时代的进步。就如西汉后期文学家恒宽曾说的&#xff0c;“明者因时而变&#xff0c;知者随事而制”。 图源来自高德官方 近日&#xff0c;高德就展现了这样的智慧。在网约车市场陷…

电子电气架构---私有总线通信和诊断规则

电子电气架构—私有总线通信和诊断规则 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自…

ROS 工具箱系统要求

ROS 工具箱系统要求 要为 ROS 或 ROS 2 生成自定义消息&#xff0c;或从 MATLAB 或 Simulink 软件中部署 ROS 或 ROS 2 节点&#xff0c;您必须构建必要的 ROS 或 ROS 2 软件包。要构建这些软件包&#xff0c;您必须具备 Python 软件、CMake 软件以及适用于您的平台的 C 编译器…

文件操作与隐写

一、文件类型的识别 1、文件头完好情况&#xff1a; &#xff08;1&#xff09;file命令 使用file命令识别&#xff1a;识别出file.doc为jpg类型 &#xff08;2&#xff09;winhex 通过winhex工具查看文件头类型&#xff0c;根据文件头部内容去判断文件的类型 eg:JPG类型 &a…

3DMax基础- 创建基础模型

目录 零.软件简介 一. 标准基本型 长方体 圆锥体 球体 圆柱体 管状体 圆环 四棱锥 茶壶 平面​编辑 加强型文本 二. 扩展基本体 三.复合对象 变形 散布 一致 连接 图形合并 布尔 并集 合并 交集 差集 四.门和窗 门 窗 植物,栏杆,墙 零.软件简介 3…

JdK版本介绍

JDK&#xff08;Java Development Kit&#xff09;是Java开发的核心工具包&#xff0c;它包含了Java运行时环境&#xff08;JRE&#xff09;以及Java开发工具&#xff08;如编译器、调试器等&#xff09;。JDK的版本更新不断&#xff0c;每个版本都带来了新特性、性能改进和安全…