How to Link Distributed Logs with TraceId Across Rest, MQ, and RPC in Java

This article explains how to generate a unique traceId at request entry, propagate it through Rest, MQ, and RPC modules using Log4j2, MDC, and custom converters, and format logs so that distributed traces become easily readable and correlated across services.

Java Backend Technology
Java Backend Technology
Java Backend Technology
How to Link Distributed Logs with TraceId Across Rest, MQ, and RPC in Java

Background

When taking over a project with no maintenance, the author found that log output during debugging lacked any context, making it impossible to follow the request chain.

The project only depends on Rest and MQ modules, so the solution focuses on those (and a reference for RPC).

Solution

Introduce a unique identifier (traceId) generated at the entry point of a request or transaction and propagate it downstream. By printing traceId in every log line, logs from different services can be correlated.

1. Rest Module

Use Log4j2's LogEventPatternConverter to include traceId in the log pattern. Define a custom TraceMessage class extending ParameterizedMessage, a TraceMessageFactory that creates TraceMessage, and a TraceIdPatternConverter that appends the traceId to the log output.

[%d{yyyy-MM-dd HH:mm:ss.SSS}][%t][%level][%C:%L][%traceId] %m%n
@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;
    }
}
public class TraceMessageFactory extends AbstractMessageFactory {
    @Override
    public Message newMessage(String message, Object... params) {
        String traceId = "..."; // obtain traceId from upstream
        return new TraceMessage(traceId, message, params);
    }
    // other overloads omitted for brevity
}
@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

For RocketMQ, use the built‑in msgId as a surrogate traceId. An AOP aspect extracts the msgId before the consumer processes the message and stores it in MDC, which Log4j2 then reads.

@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<MessageExt> list = (List<MessageExt>) pjp.getArgs()[0];
                String messageId = list.stream()
                    .map(MessageExt::getMsgId)
                    .collect(Collectors.joining("-"));
                MDC.put("msgId", messageId);
            }
            return pjp.proceed(pjp.getArgs());
        } finally {
            MDC.clear();
        }
    }
}
@Plugin(name = "TraceIdPatternConverter", category = PatternConverter.CATEGORY)
@ConverterKeys({"traceId"})
public class TraceIdPatternConverter extends LogEventPatternConverter {
    // same implementation as in the Rest module, but uses MDC.get("msgId") when available
}

3. RPC Module (Dubbo example)

Even though the current project does not use RPC, a Dubbo filter is shown to illustrate how to put traceId into Dubbo's RPC context attachments and retrieve it on the provider side.

@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 context = RpcContext.getContext();
        if (context.isConsumerSide()) {
            String traceId = "..."; // obtain from upstream or generate
            if (StringUtils.isBlank(traceId)) {
                traceId = UuidUtils.getUuid();
            }
            context.setAttachment("traceId", traceId);
        } else if (context.isProviderSide()) {
            LogContext.setTraceId(context.getAttachment("traceId"));
        }
        return invoker.invoke(invocation);
    }
}

Conclusion

After applying the above changes, the distributed logs are linked by a common traceId, making the request flow easy to follow.

TraceId logging illustration
TraceId logging illustration
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaLog4j2distributed loggingTraceId
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

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.