Backend Development 8 min read

Implementing TraceId‑Based Distributed Logging for Rest, MQ, and RPC Modules with Log4j2

This article explains how to generate a unique traceId for each request, propagate it through Rest, RocketMQ, and Dubbo RPC modules, and configure Log4j2 (including custom Message, MessageFactory, and PatternConverter) to output the traceId so that logs across services can be correlated and read sequentially.

Architect's Guide
Architect's Guide
Architect's Guide
Implementing TraceId‑Based Distributed Logging for Rest, MQ, and RPC Modules with Log4j2

Background – The author inherited a project lacking proper maintenance and needed to refactor its functionality. During debugging, the console logs were disordered and could not show the request chain, making troubleshooting difficult. The project only uses Rest and MQ modules, so a traceable logging solution was required.

Solution Overview – Introduce a globally unique traceId generated at the entry point of each request or transaction, pass it downstream, and include it in every log entry. This enables clear correlation of logs across different modules.

1. Rest Module

Use Log4j2’s LogEventPatternConverter to inject the traceId into log messages. The steps are:

Define a custom TraceMessage class extending ParameterizedMessage that holds the traceId .

Create a TraceMessageFactory that extends AbstractMessageFactory and overrides newMessage to produce TraceMessage instances.

Implement a TraceIdPatternConverter plugin to format the traceId in the log pattern.

Log pattern configuration (already includes %traceId ):

[%d{yyyy-MM-dd HH:mm:ss.SSS}][%t][%level][%C:%L][%traceId] %m%n

TraceMessage.java

@Getter
@Setter
public class TraceMessage extends ParameterizedMessage {
    private String traceId;
    public TraceMessage(String traceId, String spanId, String messagePattern, Object... arguments) {
        super(messagePattern, arguments);
        this.traceId = traceId;
    }
}

TraceMessageFactory.java

public class TraceMessageFactory extends AbstractMessageFactory {
    public TraceMessageFactory() {}
    @Override
    public Message newMessage(String message, Object... params) {
        // Retrieve traceId from upstream request here
        String traceId = "...";
        return new TraceMessage(traceId, message, params);
    }
    @Override
    public Message newMessage(CharSequence message) { return newMessage(message.toString()); }
    @Override
    public Message newMessage(Object message) { return super.newMessage(message); }
    @Override
    public Message newMessage(String message) { return newMessage(message, null); }
}

TraceIdPatternConverter.java

@Plugin(name = "TraceIdPatternConverter", category = PatternConverter.CATEGORY)
@ConverterKeys({"traceId"})
public class TraceIdPatternConverter extends LogEventPatternConverter {
    private TraceIdPatternConverter(String name, String style) { super(name, style); }
    public static TraceIdPatternConverter newInstance() {
        return new TraceIdPatternConverter("TraceIdPatternConverter", "TraceIdPatternConverter");
    }
    @Override
    public void format(LogEvent event, StringBuilder toAppendTo) {
        Message message = event.getMessage();
        if (message instanceof TraceMessage) {
            TraceMessage tm = (TraceMessage) message;
            toAppendTo.append("[" + ObjectUtil.defaultIfBlank(tm.getTraceId(), "") + "]");
            return;
        }
        toAppendTo.append("~");
    }
}

2. MQ Module (RocketMQ Example)

For MQ, use the message’s built‑in msgId as a surrogate trace identifier. An AOP aspect captures the msgId before the consumer processes the message and stores it in MDC (or LogContext) so Log4j2 can output it.

LogRocketMQAspect.java

@Slf4j
@Aspect
@Component
public class LogRocketMQAspect {
    @Pointcut("execution(* org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently.consumeMessage(..))")
    public void pointCut() {}
    @Around("pointCut()")
    public Object injectTraceId(ProceedingJoinPoint pjp) throws Throwable {
        try {
            if (pjp.getSignature().getName().equals("consumeMessage")) {
                List
msgs = (List
) pjp.getArgs()[0];
                String messageId = msgs.stream().map(MessageExt::getMsgId).collect(Collectors.joining("-"));
                MDC.put("msgId", messageId);
            }
            return pjp.proceed(pjp.getArgs());
        } finally {
            MDC.clear();
        }
    }
}

The same TraceIdPatternConverter can be reused; it checks MDC for msgId and prints it when present.

3. RPC Module (Dubbo Example)

Although the current project lacks RPC, the article provides a Dubbo filter that propagates traceId via RpcContext attachments.

LogAttachmentFilter.java

@Activate(order = 99, group = {Constants.PROVIDER_PROTOCOL, Constants.CONSUMER_PROTOCOL})
public class LogAttachmentFilter implements Filter {
    @Override
    public Result invoke(Invoker
invoker, Invocation invocation) throws RpcException {
        RpcContext ctx = RpcContext.getContext();
        if (ctx.isConsumerSide()) {
            String traceId = "..."; // obtain from upstream
            if (StringUtils.isBlank(traceId)) {
                traceId = UuidUtils.getUuid();
            }
            ctx.setAttachment("traceId", traceId);
        } else if (ctx.isProviderSide()) {
            LogContext.setTraceId(ctx.getAttachment("traceId"));
        }
        return invoker.invoke(invocation);
    }
}

Conclusion – After applying the traceId propagation and Log4j2 custom converter across Rest, MQ, and RPC modules, the logs become a coherent, ordered chain, greatly simplifying debugging and system observability.

JavaAOPDubboRocketMQlog4j2distributed loggingTraceId
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

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