一种高效的日志打印工具,通过使用SpringAOP和MDC,将用户ID和订单ID自动填充到日志中,简化了日志记录的过程
1. 目标
在电商交易系统的日志中,为了更方便地记录信息,每次都需要手动设置 userId
和 orderId
,这显得非常繁琐。因此,目标是通过优化,实现日志打印时自动填充这些常用字段,而无需每次手动指定。
2. 实现思路
- 在日志模板中声明占位符
userId
和orderId
。 - 在业务入口处将
userId
放入到线程的ThreadLocal
本地变量中。 - 通过
Spring AOP + 注解
,自动将用户信息放入到线程上下文中,进而填充日志。
3. 配置日志变量,读取上下文变量
使用 %X{}
占位符来自定义日志格式。例如:
<?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>
<AsyncRoot level="info" includeLocation="true">
<appender-ref ref="consoleAppender"/>
</AsyncRoot>
</Loggers>
</Configuration>
4. 基于 MDC 将订单和用户信息放到线程的上下文 Map
MDC
(Mapped Diagnostic Context)允许将信息放入线程上下文中,日志框架会自动将这些信息应用到日志格式中。
使用 MDC
类的 put()
方法可以将 userId
和 orderId
注入到日志中,例如:
MDC.put("userId", userId);
MDC.put("orderId", orderId);
log.warn("订单履约完成");
效果如下:
2024-08-17 21:35:38,284 [main] WARN - userId:32894934895 orderId:8497587947594859232 订单履约完成
5. 注解 + SpringAop,自动将 UserId
放到 MDC
通过注解和切面的方式,自动将 UserId
注入到 MDC
中。在方法执行前,注解会自动获取入参中的 userId
和 orderId
。
5.1 定义注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLog {
String userId() default "";
String orderId() default "";
}
使用方法:
@UserLog(userId = "userId", orderId = "orderId")
public void orderPerform(UserOrder order) {
log.warn("订单履约完成");
}
5.2 定义切面
@Aspect
@Component
public class UserLogAspect {
@Pointcut("@annotation(UserLog) && execution(public * *(..))")
public void pointcut() {}
@Around("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) {
String userId = String.valueOf(PropertyUtils.getProperty(args[0], userLogAnnotation.userId()));
String orderId = String.valueOf(PropertyUtils.getProperty(args[0], userLogAnnotation.orderId()));
MDC.put("userId", userId);
MDC.put("orderId", orderId);
}
try {
return joinPoint.proceed();
} finally {
MDC.clear();
}
}
}
5.3 关键代码解读
获取
UserLog
注解:UserLog userLogAnnotation = method.getAnnotation(UserLog.class);
使用
PropertyUtils.getProperty
获取userId
:PropertyUtils.getProperty(args[0], userLogAnnotation.userId());
使用
MDC.put()
设置和清除变量: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 日志效果
打印的日志会自动包含 userId
和 orderId
:
2024-08-17 21:35:38,284 [main] WARN - userId:32894934895 orderId:8497587947594859232 订单履约完成
7. 总结
使用 UserLog
注解和 AOP,可以自动将 userId
和 orderId
等默认参数放入日志中,简化了业务日志的打印,大幅提升了生产力。通过进一步扩展,可以自动打印出入参日志或自动上报监控打点等功能。