Auto‑Inject UserId and OrderId into Logs with Spring AOP and MDC
This article explains how to eliminate manual logging of user and order identifiers in Java e‑commerce services by using Log4j2 placeholders, ThreadLocal, MDC, a custom @UserLog annotation, and Spring AOP to automatically enrich log messages with these contextual fields.
In e‑commerce transaction systems, developers often need to log userId and orderId for troubleshooting, but manually adding these fields to every log statement is tedious. This guide demonstrates a complete solution that automatically injects these identifiers into log output using Log4j2 pattern layout, ThreadLocal, MDC, a custom annotation, and Spring AOP.
1. Goal
Automatically fill userId and orderId (and other common parameters) in log statements without explicit code.
2. Implementation Idea
1) Declare placeholders userId:%X{userId} orderId:%X{orderId} in the Log4j2 pattern. 2) Store userId and orderId in a ThreadLocal variable at the entry of each request. 3) Use a Spring AOP + custom annotation to copy the ThreadLocal values into the MDC map before the business method executes.
3. Configure Log4j2 Pattern
<?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. Use MDC to Store Context Variables
MDC (Mapped Diagnostic Context) is a thread‑local map provided by SLF4J. When a variable is put into MDC, Log4j2 automatically replaces the corresponding %X{key} placeholder in the pattern.
5. Annotation + Spring AOP
5.1 Define the Annotation
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLog {
String userId() default "";
String orderId() default "";
}5.2 Define the 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 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 {
Object response = joinPoint.proceed();
return response;
} catch (Exception e) {
throw e;
} finally {
MDC.clear();
}
}
}5.3 Key Points
Retrieve the @UserLog annotation at runtime.
Use PropertyUtils.getProperty (from commons‑beanutils) to extract the fields specified by the annotation, supporting nested property paths such as info.userId.
Put the extracted values into MDC and clear them after method execution.
6. Verify the Effect
6.1 Business Service
@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 Test
@Test
public void testUserLog() {
OrderService.UserOrder order = new OrderService.UserOrder();
order.setUserId("32894934895");
order.setOrderId("8497587947594859232");
orderService.orderPerform(order);
}6.3 Sample Log Output
2024-08-17 21:35:38,284 [main] WARN - userId:32894934895 orderId:8497587947594859232 订单履约完成7. Summary
Different business scenarios require different log details, but a unique identifier is essential for tracing. By combining a custom @UserLog annotation with Spring AOP and MDC, developers can automatically enrich log statements with userId and orderId, dramatically improving debugging efficiency and productivity. The core implementation is under 30 lines of code, making it easy to adopt and extend (e.g., auto‑logging method parameters or reporting metrics).
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
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.
