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.

Top Architect
Top Architect
Top Architect
Auto‑Inject UserId and OrderId into Logs with Spring AOP and MDC

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.

Log output
Log 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.

JavaAOPSpringloggingAnnotationmdc
Top Architect
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.