Skip to content

Fix #16165: Propagate traceId and spanId to MDC in tracing handlers#16371

Open
anushkagupta200615-jpg wants to merge 2 commits into
apache:3.3from
anushkagupta200615-jpg:fix-mdc-traceid-16165
Open

Fix #16165: Propagate traceId and spanId to MDC in tracing handlers#16371
anushkagupta200615-jpg wants to merge 2 commits into
apache:3.3from
anushkagupta200615-jpg:fix-mdc-traceid-16165

Conversation

@anushkagupta200615-jpg

Copy link
Copy Markdown

What is the purpose of the change?

This PR addresses issue #16165 where MDC.get("traceId") returns null in Dubbo 3.3.2 logs, preventing trace IDs from propagating to logging patterns like %X{traceId}.

Root Cause:
While Dubbo's Micrometer Observation integration successfully captures the TraceContext and injects it into Dubbo's internal RpcContext, it did not explicitly populate the underlying SLF4J MDC. Unless a user was running a strictly configured Spring Boot Actuator environment (which natively registers Micrometer's MDCScopeDecorator), the trace context was lost for loggers on Dubbo's worker threads.

Fix:

  • Updated DubboServerTracingObservationHandler and DubboClientTracingObservationHandler to explicitly push traceId and spanId into org.slf4j.MDC during onScopeOpened().
  • Implemented onScopeClosed() to clean up the MDC state via MDC.remove(), preventing context leaks across reused Dubbo worker threads.
  • Wrapped the MDC interactions in a try-catch(Throwable) block as a safety mechanism to prevent NoClassDefFoundError crashes in edge cases where a user excludes slf4j-api from their classpath.

Checklist

  • Make sure there is a GitHub_issue field for the change.
  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
  • Write necessary unit-test to verify your logic correction. If the new feature or significant change is committed, please remember to add sample in dubbo samples project.
  • Make sure gitHub actions can pass. Why the workflow is failing and how to fix it?

@codecov-commenter

codecov-commenter commented Jul 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 73.91304% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.86%. Comparing base (316df8e) to head (798beaf).

Files with missing lines Patch % Lines
.../handler/DubboClientTracingObservationHandler.java 71.42% 3 Missing and 1 partial ⚠️
.../handler/DubboServerTracingObservationHandler.java 77.77% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##                3.3   #16371      +/-   ##
============================================
- Coverage     60.87%   60.86%   -0.01%     
+ Complexity    11767    11764       -3     
============================================
  Files          1953     1953              
  Lines         89260    89282      +22     
  Branches      13471    13472       +1     
============================================
+ Hits          54334    54344      +10     
- Misses        29342    29354      +12     
  Partials       5584     5584              
Flag Coverage Δ
integration-tests-java21 32.13% <0.00%> (+0.04%) ⬆️
integration-tests-java8 32.32% <0.00%> (+0.10%) ⬆️
samples-tests-java21 32.14% <0.00%> (-0.05%) ⬇️
samples-tests-java8 29.70% <0.00%> (-0.04%) ⬇️
unit-tests-java11 59.13% <73.91%> (+0.03%) ⬆️
unit-tests-java17 58.58% <73.91%> (-0.01%) ⬇️
unit-tests-java21 58.56% <73.91%> (-0.03%) ⬇️
unit-tests-java25 58.52% <73.91%> (+<0.01%) ⬆️
unit-tests-java8 59.11% <73.91%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@LI123456mo LI123456mo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have gone thrrough your PR , run the local tests my self which worked perfectly, I can say this LGTM.

Suggesting if you could add tests for both . Here is for the one for server and also for client it also similar though you need some little changed

class DubboServerTracingObservationHandlerTest {

private Tracer mockTracer;
private CurrentTraceContext mockCurrentTraceContext;
private TraceContext mockTraceContext;
private DubboServerContext mockContext;
private DubboServerTracingObservationHandler<DubboServerContext> handler;

@BeforeEach
void setUp() {
    // Clear MDC before each test to ensure a clean state
    MDC.clear();

    mockTracer = Mockito.mock(Tracer.class);
    mockCurrentTraceContext = Mockito.mock(CurrentTraceContext.class);
    mockTraceContext = Mockito.mock(TraceContext.class);
    mockContext = Mockito.mock(DubboServerContext.class);

    when(mockTracer.currentTraceContext()).thenReturn(mockCurrentTraceContext);
    when(mockCurrentTraceContext.context()).thenReturn(mockTraceContext);

    when(mockTraceContext.traceId()).thenReturn("mock-trace-id-123");
    when(mockTraceContext.spanId()).thenReturn("mock-span-id-456");

    handler = new DubboServerTracingObservationHandler<>(mockTracer);
}

@Test
void proveWorking_WhenScopeOpens_MdcShouldHaveIds() {
    // 1. Verify MDC is currently empty
    assertNull(MDC.get("traceId"));
    assertNull(MDC.get("spanId"));

    handler.onScopeOpened(mockContext);

    assertEquals("mock-trace-id-123", MDC.get("traceId"), "Trace ID should be mapped to MDC!");
    assertEquals("mock-span-id-456", MDC.get("spanId"), "Span ID should be mapped to MDC!");
}

@Test
void proveFailurePrevention_WhenScopeCloses_MdcShouldBeCleaned() {
    handler.onScopeOpened(mockContext);
    assertEquals("mock-trace-id-123", MDC.get("traceId"));

    handler.onScopeClosed(mockContext);

    // PROOF THE LEAK IS PREVENTED: MDC must be completely empty now.
    // If the code failed or missed this method, these assertions would fail!
    assertNull(MDC.get("traceId"), "Trace ID leaked! It was not removed when scope closed.");
    assertNull(MDC.get("spanId"), "Span ID leaked! It was not removed when scope closed.");
}

}

@LI123456mo LI123456mo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@zrlw

@zrlw zrlw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to ensure Micrometer tracing context is visible to logging patterns like %X{traceId} by explicitly propagating traceId / spanId into SLF4J MDC when Dubbo observation scopes open, and cleaning MDC when scopes close.

Changes:

  • Populate org.slf4j.MDC with traceId and spanId in DubboServerTracingObservationHandler / DubboClientTracingObservationHandler during onScopeOpened().
  • Add onScopeClosed() implementations intended to remove MDC entries to avoid worker-thread context leakage.
  • Add unit tests covering MDC population and cleanup behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
dubbo-metrics/dubbo-tracing/src/main/java/org/apache/dubbo/tracing/handler/DubboServerTracingObservationHandler.java Adds MDC put/remove for trace/span IDs during scope open/close.
dubbo-metrics/dubbo-tracing/src/main/java/org/apache/dubbo/tracing/handler/DubboClientTracingObservationHandler.java Implements scope open/close MDC propagation for client-side observations.
dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/handler/DubboServerTracingObservationHandlerTest.java Adds tests asserting MDC is populated and cleared around server handler scope lifecycle.
dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/handler/DubboClientTracingObservationHandlerTest.java Adds tests asserting MDC is populated and cleared around client handler scope lifecycle.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +48 to +53
public void onScopeClosed(T context) {
try {
MDC.remove("traceId");
MDC.remove("spanId");
} catch (Throwable ignored) {
}
Comment on lines +43 to +44
} catch (Throwable ignored) {
}
Comment on lines +53 to +58
public void onScopeClosed(T context) {
try {
MDC.remove(DEFAULT_TRACE_ID_KEY);
MDC.remove("spanId");
} catch (Throwable ignored) {
}
Comment on lines +48 to +49
} catch (Throwable ignored) {
}
Comment on lines +77 to +87
@Test
void proveFailurePrevention_WhenScopeCloses_MdcShouldBeCleaned() {
handler.onScopeOpened(mockContext);
assertEquals("mock-trace-id-123", MDC.get("traceId"));

handler.onScopeClosed(mockContext);

// PROOF THE LEAK IS PREVENTED: MDC must be completely empty now.
assertNull(MDC.get("traceId"), "Trace ID leaked! It was not removed when scope closed.");
assertNull(MDC.get("spanId"), "Span ID leaked! It was not removed when scope closed.");
}
Comment on lines +79 to +89
@Test
void proveFailurePrevention_WhenScopeCloses_MdcShouldBeCleaned() {
handler.onScopeOpened(mockContext);
assertEquals("mock-trace-id-123", MDC.get("traceId"));

handler.onScopeClosed(mockContext);

// PROOF THE LEAK IS PREVENTED: MDC must be completely empty now.
assertNull(MDC.get("traceId"), "Trace ID leaked! It was not removed when scope closed.");
assertNull(MDC.get("spanId"), "Span ID leaked! It was not removed when scope closed.");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants