Fix #16165: Propagate traceId and spanId to MDC in tracing handlers#16371
Fix #16165: Propagate traceId and spanId to MDC in tracing handlers#16371anushkagupta200615-jpg wants to merge 2 commits into
Conversation
Codecov Report❌ Patch coverage is 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
LI123456mo
left a comment
There was a problem hiding this comment.
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.");
}
}
16a300a to
798beaf
Compare
There was a problem hiding this comment.
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.MDCwithtraceIdandspanIdinDubboServerTracingObservationHandler/DubboClientTracingObservationHandlerduringonScopeOpened(). - 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.
| public void onScopeClosed(T context) { | ||
| try { | ||
| MDC.remove("traceId"); | ||
| MDC.remove("spanId"); | ||
| } catch (Throwable ignored) { | ||
| } |
| } catch (Throwable ignored) { | ||
| } |
| public void onScopeClosed(T context) { | ||
| try { | ||
| MDC.remove(DEFAULT_TRACE_ID_KEY); | ||
| MDC.remove("spanId"); | ||
| } catch (Throwable ignored) { | ||
| } |
| } catch (Throwable ignored) { | ||
| } |
| @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."); | ||
| } |
| @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."); | ||
| } |
What is the purpose of the change?
This PR addresses issue #16165 where
MDC.get("traceId")returnsnullin 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
TraceContextand injects it into Dubbo's internalRpcContext, it did not explicitly populate the underlying SLF4JMDC. Unless a user was running a strictly configured Spring Boot Actuator environment (which natively registers Micrometer'sMDCScopeDecorator), the trace context was lost for loggers on Dubbo's worker threads.Fix:
DubboServerTracingObservationHandlerandDubboClientTracingObservationHandlerto explicitly pushtraceIdandspanIdintoorg.slf4j.MDCduringonScopeOpened().onScopeClosed()to clean up the MDC state viaMDC.remove(), preventing context leaks across reused Dubbo worker threads.try-catch(Throwable)block as a safety mechanism to preventNoClassDefFoundErrorcrashes in edge cases where a user excludesslf4j-apifrom their classpath.Checklist