为程序日志添加唯一标识|焦点要闻

2023-05-14 04:00:12 | 来源:程序猿阿嘴

最近看一个工程中将UUID打印在日志中、看到那个时候我想到的就是唯一请求流水编号、什么意思呢、你可以理解为我调用一个接口他就会生成一个编号、这个编号就代表我之前请求的唯一标识、后续出现问题能够快速定位日志信息。


(资料图片仅供参考)

开始-改造

我看别人改程中的打印很繁琐、每个log.xxx()的时候都要传这个编号、所以肯定是要优化一下的!哈哈哈哈!

这边封装了一个工具类、主要还是要懂 ThreadLocal 线程本地变量 !简单理解每个线程都有一份、能做到独立互不干涉。

java复制代码 package com.stall.config;  import java.util.HashMap; import java.util.Map; import java.util.UUID;  /**  * 日志请求流水、用日志追踪  *  * @Author 突突突突突  * @blog https://juejin.cn/user/844892408381735  * @Date 2023/3/24 13:24  */ public class RequestLogManagement {     public static ThreadLocal>threadLocal = new ThreadLocal<>();        /**      *  初始入口、后续打印调用      * @param describe 入口描述      */     public static void init(String describe) {         MapthreadLocalMap = new HashMap<>();         String requestUUID = UUID.randomUUID().toString();         threadLocalMap.put(\"describe\", describe);         threadLocalMap.put(\"uuid\", requestUUID);         threadLocal.set(threadLocalMap);     }      public static String getRequestUUID() {         return threadLocal.get() == null            ? \"\" : String.valueOf(threadLocal.get().get(\"uuid\"));     }      public static String getRequestDescribe() {         return threadLocal.get() == null            ? \"\" : String.valueOf(threadLocal.get().get(\"describe\"));     }     public static void remove() {         threadLocal.remove();     } }

死方式-每个log都手动打印

java复制代码 /**  * 登录认证  *  * @Author 突突突突突  * @blog https://juejin.cn/user/844892408381735  * @Date 2023/3/24 13:49  */ @Slf4j @RestController @RequestMapping(\"/auth\") public class WxLoginController {      @Resource     private AuthService authService;      @PostMapping(\"/wx/login\")     public RwxLogin(String code) {         RequestLogManagement.init(\"微信登录接口\");         try {             log.info(\"{}、开始调用微信登录接口\",RequestLogManagement.getRequestUUID());             authService.wxLogin(code);             return R.success();         } catch (InterfaceException e) {             log.error(\"{}、收到请求异常信息\",RequestLogManagement.getRequestUUID(), e);             return R.custom(e.getCode(), e.getMessage());         } catch (Exception e) {             log.error(\"{}、收到请求异常信息\",RequestLogManagement.getRequestUUID(), e);             return R.failed();         }finally {             RequestLogManagement.remove();         }     } }

从上面的日志打印就能发现问题一些问题吧、如果我很多接口这个 RequestLogManagement.init(\"微信登录接口\"); 、 log.info(\"{}、xxxxxx调用\",RequestLogManagement.getRequestUUID()); 和 RequestLogManagement.remove(); 这些内容中很多重复的操作、首先我们解决 入口开始描述/入口结束清除数据 、用眼睛一看就知道用什么解决这个问题、那就是AOP的方式、在Controller接口请求的方法中的前后进行增强处理。

就是说知道用AOP的方式后、在写牛点自定义一个注解用于AOP能够准确的切入到对应方法。

java复制代码 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequestLog {     /**      * 日志描述      */     String value(); }
java复制代码 @Slf4j @Aspect @Component public class RequestLogOperationAspect {     /**      * 准备环绕的方法      */     @Pointcut(\"@annotation(com.stall.config.aop.RequestLog)\")     public void execRequestLogService() {     }      @Around(\"execRequestLogService()\")     public Object RequestLogAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {         //目标对象         Classclazz = proceedingJoinPoint.getTarget().getClass();         //方法签名         String method = proceedingJoinPoint.getSignature().getName();         //方法参数         Object[] thisArgs = proceedingJoinPoint.getArgs();         //方法参数类型         Class[] parameterTypes = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod().getParameterTypes();         //方法         Method thisMethod = clazz.getMethod(method, parameterTypes);         //自定义日志接口         RequestLog methodAnnotation = Objects.requireNonNull(AnnotationUtils.findAnnotation(thisMethod, RequestLog.class));         //  通用日志打印         RequestLogManagement.init(methodAnnotation.value());         log.info(\"[{}][{}]请求开始、请求参数:{}\",RequestLogManagement.getRequestUUID(), methodAnnotation.value(), Arrays.toString(thisArgs));         Object proceed = null;         try {             proceed = proceedingJoinPoint.proceed();         } finally {             log.info(\"[{}][{}]请求结束、请求参数:{}\",RequestLogManagement.getRequestUUID(), methodAnnotation.value(), proceed);             // 清除数据             RequestLogManagement.remove();         }         return proceed;     } }

然后改造好后的代码、我们在入口上加一个注解就ok了。

java复制代码 @RequestLog(value = \"微信登录接口\") @PostMapping(\"/wx/login\") public RwxLogin(String code) {   try {     log.info(\"{}、开始调用微信登录接口\",RequestLogManagement.getRequestUUID());     authService.wxLogin(code);     return R.success();   } catch (InterfaceException e) {     log.error(\"{}、收到请求异常信息\",RequestLogManagement.getRequestUUID(), e);     return R.custom(e.getCode(), e.getMessage());   } catch (Exception e) {     log.error(\"{}、收到请求异常信息\",RequestLogManagement.getRequestUUID(), e);     return R.failed();   } }

MDC-不需要每个log都手动打印

但是现在解决了那个问题还有这个 log.info(\"{}、xxxxxx调用\",RequestLogManagement.getRequestUUID()); 我总不能说我每次打印日志我都要加一个 RequestLogManagement.getRequestUUID() 。

所以身为大聪明的我又想到AOP的方式、去增强log对象中的所有方法、于是我打开百度找阿找!!!我就发现一个牛很多的写法、就是 MDC 类对象中可能放入参数、而这个参数能够被日志底层使用、相当于在我们打印日志的时候可以向日志中塞入一个值、类似插槽一样的概念、用就加、不用就不加!!!

MDC 底层也是靠 ThreadLocal 来实现的、他泛型是Map类型、就相当于能放键值对的形式的数据、而 MDC 就相当于是我们刚刚写 RequestLogManagement 的一个工具类、提供外部直接调用、要注意的就是一个 MDC 是 org.slf4j.MDC 一个是 org.jboss.logging.MDC 虽然说都能使用、但是里面的方法不一样、最后使用 org.slf4j.MDC 这个就可以。

来先把 RequestLogOperationAspect.RequestLogAround(.) 这个方法改造了、这个是我们写的Controller切入执行的入口。

java复制代码 //自定义日志接口 RequestLog methodAnnotation = Objects.requireNonNull(AnnotationUtils.findAnnotation(thisMethod, RequestLog.class)); //  通用日志打印 RequestLogManagement.init(methodAnnotation.value()); // 将UUID放入到MDC对象中 MDC.put(\"requestId\", RequestLogManagement.getRequestUUID()); log.info(\"[{}]请求开始、请求参数:{}\", methodAnnotation.value(), Arrays.toString(thisArgs)); Object proceed = null; try {   proceed = proceedingJoinPoint.proceed(); } finally {   log.info(\"[{}]请求结束、请求参数:{}\", methodAnnotation.value(), proceed);   RequestLogManagement.remove();   // 执行完成后清除。   MDC.clear(); }
yml复制代码 logging:   pattern:     console: \"${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr([%X{requestId}]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}\"

修改日志的打印格式、主要看 %X{requestId} 、当前的name就是MDC.put中的key的名称。

默认打印日志

修改后的打印日志

不管我们自己写的 RequestLogManagement 还是 MDC 这两种方式都不能在子线程中获取到、解决方法就是在线程外将值赋值出去、然后由子线程重新塞入到自己线程副本的 ThreadLocal 中。

typescript复制代码 MapcopyOfContextMap = MDC.getCopyOfContextMap(); new Thread(new Runnable() {   @Override   public void run() {     MDC.setContextMap(copyOfContextMap);     for (int i = 0; i < 10; i++) {       log.info(\">>>>>>>>>i={}\", i);     }     MDC.clear();   } }).start();

小结

以上方式主要适用单机环境、如分布式服务之间的调用、肯定有其他的更好更牛的链路的方式。

把上面方式集成到你的单机项目中再配合之前写的 linux下查看项目日志的方式就能快速找到请求流水对应的日志信息。

原文链接:https://juejin.cn/post/7215640327633141819

为程序日志添加唯一标识|焦点要闻

全球看热讯:学者回应“给生孩子的人发薪”争议:初衷是缓解生育率过低

14.54万人直接受益!甘肃中部这项供水工程全线贯通

汉阳造艺术区官网_汉阳造艺术区

下周两市解禁市值环比大减近5成 3家公司解禁比例超40%(附表)|每日看点

中科院科普室落户鄂西土家小山村|环球关注

今日最美的时光大结局剧情(最美的时光大结局剧情)_全球微头条

宽带账号查询_电信宽带账号密码查询_每日热点

全球关注:瑞丰村_关于瑞丰村介绍

天天精选!张店区马尚街道开展防范灾害风险知识讲座

chinanet怎么连接不上网_chinanet怎么连接-世界新动态

当前热文:桥墩垫石塑料模板的详细介绍

地球什么时候毁灭它什么时候就恢复了_地球什么时候毁灭 每日消息

每日讯息!各地精心组织防灾减灾宣传活动 增强全民防灾意识

东方证券:五一地产销售表现平淡!央国企优势持续凸显

防灾减灾宣传走到群众身边

长春市星东文化传媒有限公司(吉林省星华文化传播有限公司)

热点!国际原子能机构总干事:扎波罗热核电站可能面临工作人员短缺状况

播报:PLC实时数据采集如何实现?

新能源汽车市场占有率达27% 全球时快讯

多彩贵州非遗周末聚主题沙龙在贵阳举行

白皮书 蓝皮书 红皮书_蓝皮书与白皮书的区别|每日资讯

【独家】津岛修治是谁_津岛修治

5月12日消息,三星公司今天宣布开发出首款支持CX

每日播报!100兆宽带什么意思_100兆宽带用什么路由器合适

天天热门:[路演]中公教育:一季度以来公司业务在逐步恢复,学员退费延期问题也正逐步得到解决

青年怀壮志 立功正当时

党建引领邻里互助,托起社区和谐发展

亚美尼亚称阿塞拜疆部队向亚阿边境方向开火

微资讯!常山北明(000158.SZ):1月至今累计收到政府补助2582.76万元

Copyright   2015-2023 今日运动网 版权所有  备案号:沪ICP备2023005074号-40   联系邮箱:5 85 59 73 @qq.com