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.

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

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.

JavaAOPSpringloggingAnnotationThreadLocalmdc
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.