成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

高效日志打印技巧,簡潔又清晰

開發 前端
不同的業務場景有不同的日志需求,一般情況下為了排查問題方便,需要唯一標識把一系列請求串聯起來,使用 UserLog 注解+Aop ,自動將這部分默認參數放到日志中,可以簡化業務日志打印,極大地提高了生產力。
為了更方便地排查問題,電商交易系統的日志中需要記錄用戶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、實現思路

  • 日志模板中聲明占位符 userId,orderId
  • 在業務入口將userId放入到線程ThreadLocal本地變量中。
  • 使用SpringAop + 注解 自動將第二步的用戶信息放到線程上下文

3、配置日志變量,讀取上下文變量

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

<?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中包含userId和orderId屬性,則像如下方式聲明。

@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 ,自動將這部分默認參數放到日志中,可以簡化業務日志打印,極大地提高了生產力。

另外大家可以自行擴展能力,例如自動打印出入參日志,自動上報監控打點等等。

責任編輯:武曉燕 來源: 一安未來
相關推薦

2024-01-30 08:54:05

JavaScript技巧代碼

2019-07-31 10:24:16

JavaScript瀏覽器口袋妖怪

2023-07-30 17:10:32

TypeScript開發

2019-07-24 15:29:55

JavaScript開發 技巧

2018-06-08 09:50:07

程序員開發技巧Java

2021-03-16 09:48:51

FaustPython數據流

2021-01-19 13:10:29

ZshLinuxUbuntu

2010-09-06 09:06:22

CSS

2012-12-25 09:45:08

PythonWeb

2025-02-27 10:20:49

2024-11-04 09:47:59

2012-01-09 17:03:39

臺式機評測

2024-11-11 17:00:27

字典壓縮Python代碼

2018-10-19 12:37:47

GitHub代碼開發者

2012-12-26 11:29:05

微軟GPS智能手機

2024-12-19 09:05:13

Python鏈式調用

2023-10-29 12:54:16

Doris數據倉庫

2011-05-06 09:07:19

惠普打印機

2023-08-27 16:19:09

JavaScript編程語言

2024-12-04 15:10:21

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲免费在线观看视频 | 精品一区二区三区四区五区 | 日本欧美在线观看视频 | 国产精品揄拍一区二区 | 啪一啪在线视频 | 午夜影院在线免费观看视频 | 日韩综合在线视频 | 午夜影院在线视频 | 国产精品美女久久久久久久网站 | 欧美日韩精品一区 | 日韩视频一区 | av网站免费在线观看 | 亚洲国产精品99久久久久久久久 | 久久久av | 亚洲一区二区三区在线免费 | 毛片网站免费观看 | 国产性生活一级片 | 日韩福利视频 | 亚洲欧美精品在线观看 | 国产日韩欧美精品 | 久久国产日韩欧美 | 99国产精品久久久 | 国产一级在线观看 | 国产一区二区激情视频 | av午夜电影 | 一区二区三区影院 | 国产精品精品视频一区二区三区 | 亚洲国产精品suv | 一区二区三区韩国 | 在线成人www免费观看视频 | 好好的日在线视频 | 怡红院怡春院一级毛片 | 中文字幕在线免费 | 亚洲第一在线 | 亚洲国产欧美日韩 | 亚洲高清一区二区三区 | 国产区久久| 香蕉久久a毛片 | 午夜免费av | 色资源站 | 久久国产麻豆 |