From ca0dffb45c51a0565227b45053162b1c02bae41b Mon Sep 17 00:00:00 2001 From: anushkagupta200615-jpg Date: Mon, 6 Jul 2026 04:10:39 +0530 Subject: [PATCH 1/2] Fix #16165: Propagate traceId and spanId to MDC in tracing handlers --- .../DubboClientTracingObservationHandler.java | 23 ++++++++++++++++++- .../DubboServerTracingObservationHandler.java | 15 ++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/dubbo-metrics/dubbo-tracing/src/main/java/org/apache/dubbo/tracing/handler/DubboClientTracingObservationHandler.java b/dubbo-metrics/dubbo-tracing/src/main/java/org/apache/dubbo/tracing/handler/DubboClientTracingObservationHandler.java index f638dc6c5895..9bb25bebe7f0 100644 --- a/dubbo-metrics/dubbo-tracing/src/main/java/org/apache/dubbo/tracing/handler/DubboClientTracingObservationHandler.java +++ b/dubbo-metrics/dubbo-tracing/src/main/java/org/apache/dubbo/tracing/handler/DubboClientTracingObservationHandler.java @@ -21,6 +21,7 @@ import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationHandler; import io.micrometer.tracing.Tracer; +import org.slf4j.MDC; public class DubboClientTracingObservationHandler implements ObservationHandler { private final Tracer tracer; @@ -30,7 +31,27 @@ public DubboClientTracingObservationHandler(Tracer tracer) { } @Override - public void onScopeOpened(T context) {} + public void onScopeOpened(T context) { + io.micrometer.tracing.TraceContext traceContext = + tracer.currentTraceContext().context(); + if (traceContext == null) { + return; + } + try { + MDC.put("traceId", traceContext.traceId()); + MDC.put("spanId", traceContext.spanId()); + } catch (Throwable ignored) { + } + } + + @Override + public void onScopeClosed(T context) { + try { + MDC.remove("traceId"); + MDC.remove("spanId"); + } catch (Throwable ignored) { + } + } @Override public boolean supportsContext(Observation.Context context) { diff --git a/dubbo-metrics/dubbo-tracing/src/main/java/org/apache/dubbo/tracing/handler/DubboServerTracingObservationHandler.java b/dubbo-metrics/dubbo-tracing/src/main/java/org/apache/dubbo/tracing/handler/DubboServerTracingObservationHandler.java index c355f2fe85c0..6c2a1f5c3a7e 100644 --- a/dubbo-metrics/dubbo-tracing/src/main/java/org/apache/dubbo/tracing/handler/DubboServerTracingObservationHandler.java +++ b/dubbo-metrics/dubbo-tracing/src/main/java/org/apache/dubbo/tracing/handler/DubboServerTracingObservationHandler.java @@ -23,6 +23,7 @@ import io.micrometer.observation.ObservationHandler; import io.micrometer.tracing.TraceContext; import io.micrometer.tracing.Tracer; +import org.slf4j.MDC; public class DubboServerTracingObservationHandler implements ObservationHandler { @@ -41,6 +42,20 @@ public void onScopeOpened(T context) { return; } RpcContext.getServerContext().setAttachment(DEFAULT_TRACE_ID_KEY, traceContext.traceId()); + try { + MDC.put(DEFAULT_TRACE_ID_KEY, traceContext.traceId()); + MDC.put("spanId", traceContext.spanId()); + } catch (Throwable ignored) { + } + } + + @Override + public void onScopeClosed(T context) { + try { + MDC.remove(DEFAULT_TRACE_ID_KEY); + MDC.remove("spanId"); + } catch (Throwable ignored) { + } } @Override From 798beaf19997ded28f835318c1e998c6ceb43963 Mon Sep 17 00:00:00 2001 From: anushkagupta200615-jpg Date: Tue, 7 Jul 2026 01:04:10 +0530 Subject: [PATCH 2/2] test: Add unit tests for MDC propagation in DubboServer/ClientTracingObservationHandler --- ...boClientTracingObservationHandlerTest.java | 88 ++++++++++++++++++ ...boServerTracingObservationHandlerTest.java | 90 +++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/handler/DubboClientTracingObservationHandlerTest.java create mode 100644 dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/handler/DubboServerTracingObservationHandlerTest.java diff --git a/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/handler/DubboClientTracingObservationHandlerTest.java b/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/handler/DubboClientTracingObservationHandlerTest.java new file mode 100644 index 000000000000..cfecf342d64e --- /dev/null +++ b/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/handler/DubboClientTracingObservationHandlerTest.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.tracing.handler; + +import org.apache.dubbo.tracing.context.DubboClientContext; + +import io.micrometer.tracing.CurrentTraceContext; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.Tracer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.slf4j.MDC; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.when; + +class DubboClientTracingObservationHandlerTest { + + private Tracer mockTracer; + private CurrentTraceContext mockCurrentTraceContext; + private TraceContext mockTraceContext; + private DubboClientContext mockContext; + private DubboClientTracingObservationHandler 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(DubboClientContext.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 DubboClientTracingObservationHandler<>(mockTracer); + } + + @AfterEach + void tearDown() { + MDC.clear(); + } + + @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. + 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."); + } +} diff --git a/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/handler/DubboServerTracingObservationHandlerTest.java b/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/handler/DubboServerTracingObservationHandlerTest.java new file mode 100644 index 000000000000..d45ca80071e3 --- /dev/null +++ b/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/handler/DubboServerTracingObservationHandlerTest.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.tracing.handler; + +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.tracing.context.DubboServerContext; + +import io.micrometer.tracing.CurrentTraceContext; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.Tracer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.slf4j.MDC; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.when; + +class DubboServerTracingObservationHandlerTest { + + private Tracer mockTracer; + private CurrentTraceContext mockCurrentTraceContext; + private TraceContext mockTraceContext; + private DubboServerContext mockContext; + private DubboServerTracingObservationHandler 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); + } + + @AfterEach + void tearDown() { + MDC.clear(); + RpcContext.removeServerContext(); + } + + @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. + 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."); + } +}