首页 > 其他分享 >我司使用了两年的高效日志打印工具,非常牛逼!

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

时间:2024-09-07 13:24:48浏览次数:15  
标签:orderId String 我司 打印 userId MDC 日志 public

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

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("订单履约完成");
   }

   @Data
   public 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实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!





标签:orderId,String,我司,打印,userId,MDC,日志,public
From: https://blog.51cto.com/u_16502039/11944665

相关文章

  • Linux 性能优化(网络、磁盘、内存、日志监控)
    1、CPU性能监控1.2、平均负载基础平均负载是指单位时间内,系统处于可运行状态和不可中断状态的平均进程数,也就是平均活跃进程数,它和CPU使用率并没有直接关系。平均负载其实就是平均活跃进程数。平均活跃进程数,直观上的理解就是单位时间内的活跃进程数。查看cpu个数:grep'modelnam......
  • Linux日志-sar日志
    作者介绍:简历上没有一个精通的运维工程师。希望大家多多关注作者,下面的思维导图也是预计更新的内容和当前进度(不定时更新)。Linux系统中的日志是记录系统活动和事件的重要工具,它们可以帮助管理员监视系统状态、调查问题以及了解系统运行状况。主要涉及到系统日志,登录日志,......
  • mybatis-plus打印完整sql
     importlombok.extern.slf4j.Slf4j;importorg.apache.ibatis.cache.CacheKey;importorg.apache.ibatis.executor.Executor;importorg.apache.ibatis.mapping.BoundSql;importorg.apache.ibatis.mapping.MappedStatement;importorg.apache.ibatis.mapping.Paramet......
  • Grafana进阶教程:使用Loki、Tempo进行日志与追踪可视化
    Grafana进阶教程:使用Loki、Tempo进行日志与追踪可视化在现代运维和开发环境中,日志和追踪是观测系统健康状态、分析问题和优化性能的重要手段。Grafana是一款广泛使用的开源数据可视化和监控平台,它支持与多种数据源的集成,能够提供灵活和强大的仪表板功能。Loki和Tempo......
  • 深入解析:Spring Boot中使用Log4j2进行日志管理
    在现代Java应用开发中,日志管理是不可或缺的一部分。SpringBoot框架提供了一种简便的方式来集成日志系统,但默认使用的是Logback。本文将详细介绍如何在SpringBoot应用中使用Log4j2作为日志实现,并展示如何通过SLF4JAPI进行日志记录。引入依赖首先,我们需要在pom.xml文件中......
  • 青岛奥特富隆新材料有限公司选购我司热重分析仪
    近日,青岛奥特富隆新材料有限公司宣布正式选购我司HS-TGA-101热重分析仪(TGA),奥特富隆始终站在科技创新的前沿,不断探索材料科学的无限可能。为了进一步提升研发效率与产品质量,选购我司热重分析仪(TGA),这一举措无疑为其科研实力增添了强劲动力。青岛奥特富隆新材料有限公司我司热重分析仪......
  • C# 调用 exe 输出日志
    C#调用exe输出日志ProcessStartInfousingSystem;usingSystem.Diagnostics;usingNLog;classProgram{staticvoidMain(){ProcessStartInfostartInfo=newProcessStartInfo("your_exe_path.exe");startInfo.UseShellExecute=......
  • Gin使用zap替换默认的两个日志中间件
    packagemainimport( "encoding/json" "fmt" "github.com/gin-gonic/gin" "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" "net" "net/http"......
  • linux系统下各种日志文件的介绍,查看,及日志服务配置
    转载于https://zhuanlan.zhihu.com/p/298335887,侵权删!linux系统日志文件的详细介绍日志文件的作用日志文件用于记录linux系统的各种运行信息的文件,相当于linux主机的日记,不同的日志文件记载了不同类型的信息,如Linux内核消息、用户登录事件、程序错误等。.日志文件对于诊断和......
  • 打印设计器-MyPrint
    MyPrint操作简单,组件丰富的一站式打印解决方案打印设计器项目说明地址(github)地址(gitee)MyPrint主项目前往前往MyPrint-server服务端(去下载)——MyPrint-desktop客户端(去下载)——MyPrint-examples客户端前往前往MyPrint-dockerdocker构建镜像前往前往文档前往——......