Linking Distributed Logs with a Unified traceId in Spring Boot

This guide explains how to generate a unique traceId for each request, propagate it through REST, MQ, and RPC modules, and configure Log4j2 to include the traceId in log output, enabling clear end‑to‑end request tracing in a Spring Boot project.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Linking Distributed Logs with a Unified traceId in Spring Boot

Background

When taking over an unmaintained Java project, console logs lacked any request‑level correlation, making it impossible to trace a request across the REST, MQ and (potential) RPC layers.

Approach

Generate a globally unique traceId at the entry point of each request or transaction, propagate it downstream, and configure Log4j2 to output the identifier in every log line. The identifier is stored in MDC (or a custom thread‑local context) so that Log4j2 pattern converters can retrieve it.

Implementation

1. REST layer

Extract the incoming traceId from HTTP headers (or generate one if missing) and use a custom Log4j2 Message and pattern converter.

Create a TraceMessage that extends ParameterizedMessage and holds a traceId.

Implement TraceMessageFactory (extends AbstractMessageFactory) that builds TraceMessage instances, pulling the current traceId from MDC.

Register a Log4j2 plugin TraceIdPatternConverter that formats the traceId for the log pattern.

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

public class TraceMessageFactory extends AbstractMessageFactory {
    @Override
    public Message newMessage(String message, Object... params) {
        String traceId = MDC.get("traceId"); // obtain from MDC or other context
        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 msg = event.getMessage();
        if (msg instanceof TraceMessage) {
            String id = ((TraceMessage) msg).getTraceId();
            toAppendTo.append('[').append(StringUtils.defaultIfBlank(id, "")).append(']');
        } else {
            toAppendTo.append("~");
        }
    }
}

Log4j2 pattern example (add to log4j2.xml or log4j2.properties):

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

2. MQ layer (RocketMQ example)

When a consumer receives a batch of MessageExt objects, concatenate their msgId values, store the result in MDC under the key msgId, and let the same TraceIdPatternConverter render it.

@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 {
            List<MessageExt> msgs = (List<MessageExt>) pjp.getArgs()[0];
            String combinedId = msgs.stream()
                                   .map(MessageExt::getMsgId)
                                   .collect(Collectors.joining("-"));
            MDC.put("msgId", combinedId);
            return pjp.proceed(pjp.getArgs());
        } finally {
            MDC.clear();
        }
    }
}

The converter checks MDC for msgId first; if present it prints that value, otherwise it falls back to the traceId from the REST side.

3. RPC layer (Dubbo example)

Even though the current project does not contain RPC code, the following filter demonstrates how to propagate the identifier via Dubbo’s RpcContext attachments.

@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 = MDC.get("traceId");
            if (StringUtils.isBlank(traceId)) {
                traceId = UuidUtils.getUuid(); // generate if missing
            }
            ctx.setAttachment("traceId", traceId);
        } else if (ctx.isProviderSide()) {
            LogContext.setTraceId(ctx.getAttachment("traceId"));
        }
        return invoker.invoke(invocation);
    }
}

Result

After adding trace‑id generation, propagation, and Log4j2 formatting to the REST, MQ and optional RPC modules, every log entry contains a consistent identifier. Developers can now reconstruct the full request flow across services by filtering on traceId (or msgId for MQ).

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.

loggingDistributed TracingLog4j2TraceIdspring-boot
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.