Auto‑Inject UserId and OrderId into Logs with Spring AOP and MDC
This guide shows how to eliminate manual logging of user and order identifiers in a Java e‑commerce system by declaring log placeholders, storing values in ThreadLocal, and using a custom @UserLog annotation with Spring AOP to automatically populate MDC variables for Log4j2.
1. Goal
Automatically fill userId and orderId into log messages without manually passing them each time.
2. Implementation Idea
Declare placeholders userId and orderId in the log pattern.
Store userId and orderId in a ThreadLocal at the entry point of the business logic.
Use Spring AOP + custom annotation to automatically copy the values from ThreadLocal into the logging context.
3. Log4j2 Configuration
<?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 Variables into MDC
Use MDC.put("userId", userId) and MDC.put("orderId", orderId). MDC is a thread‑local map provided by SLF4J.
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 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 Key Points
Retrieve the annotation with method.getAnnotation(UserLog.class).
Extract values using PropertyUtils.getProperty, which supports nested property paths (e.g., info.userId).
Put values into MDC and clear the context after method execution.
6. Service and Test
@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;
}
} @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 such as:
2024-08-17 21:35:38,284 [main] WARN - userId:32894934895 orderId:8497587947594859232 订单履约完成7. Summary
Using a custom @UserLog annotation together with Spring AOP and MDC eliminates repetitive manual logging of common fields, simplifies code, and makes troubleshooting easier. The core implementation is under 30 lines and can be extended to log method parameters, send metrics, or perform other cross‑cutting concerns.
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.
