Auto‑Inject UserId and OrderId into Logs with Spring AOP and MDC
This article explains how to automatically include userId and orderId in log messages of an e‑commerce system by defining log placeholders, storing IDs in ThreadLocal, and using a custom @UserLog annotation with Spring AOP to push the values into MDC, complete with configuration, code examples, and verification steps.
1. Goal
In an e‑commerce transaction system, each log entry should contain the user ID and order ID to simplify troubleshooting, but manually adding these fields to every log call is tedious.
2. Implementation Idea
Declare placeholders userId and orderId in the log template.
Store the user ID in a ThreadLocal variable at the business entry point.
Use Spring AOP together with a custom annotation to automatically transfer the ThreadLocal values into the logging context.
3. Configure Log Variables
Log4j2’s %X{} syntax can reference MDC variables. The pattern below adds the placeholders to each log line.
<?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. Put Data into MDC
MDC (Mapped Diagnostic Context) is a thread‑local map provided by SLF4J. Values put into MDC are automatically substituted into the %X{} placeholders of the log pattern. Each thread has its own MDC, so the data does not leak across requests.
5. Annotation + Spring AOP
5.1 Define Annotation
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLog {
String userId() default "";
String orderId() default "";
}5.2 Define Aspect
@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 ann = method.getAnnotation(UserLog.class);
if (ann != null && args != null && args.length > 0) {
String userId = String.valueOf(PropertyUtils.getProperty(args[0], ann.userId()));
String orderId = String.valueOf(PropertyUtils.getProperty(args[0], ann.orderId()));
MDC.put("userId", userId);
MDC.put("orderId", orderId);
}
try {
return joinPoint.proceed();
} finally {
MDC.clear();
}
}
}5.3 Key Points
The aspect retrieves the @UserLog annotation, extracts the configured property paths with PropertyUtils.getProperty, puts the values into MDC, proceeds with the original method, and finally clears MDC to avoid contamination.
6. Validation
6.1 Service Implementation
@Service
public class OrderService {
private 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 Unit Test
@Test
public void testUserLog() {
OrderService.UserOrder order = new OrderService.UserOrder();
order.setUserId("32894934895");
order.setOrderId("8497587947594859232");
orderService.orderPerform(order);
}Running the test produces a log line similar to:
2024-08-17 21:35:38,284 [main] WARN - userId:32894934895 orderId:8497587947594859232 订单履约完成The image below shows the actual console output.
7. Conclusion
By combining a custom @UserLog annotation, Spring AOP, and MDC, developers can eliminate repetitive log parameter handling, ensure each request is uniquely traceable, and easily extend the mechanism for additional monitoring or automatic request/response logging.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
