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.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Auto‑Inject UserId and OrderId into Logs with Spring AOP and MDC

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 订单履约完成
Log output example
Log output example

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

Annotationspring-aopThreadLocalLog4j2Java Loggingmdc
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

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.