diff --git a/components/camel-opentelemetry2/src/generated/resources/META-INF/services/org/apache/camel/thread-factory-listener b/components/camel-opentelemetry2/src/generated/resources/META-INF/services/org/apache/camel/thread-factory-listener deleted file mode 100644 index 8cb98164902d5..0000000000000 --- a/components/camel-opentelemetry2/src/generated/resources/META-INF/services/org/apache/camel/thread-factory-listener +++ /dev/null @@ -1,2 +0,0 @@ -# Generated by camel build tools - do NOT edit this file! -class=org.apache.camel.opentelemetry2.OpenTelemetryInstrumentedThreadFactoryListener diff --git a/components/camel-opentelemetry2/src/generated/resources/META-INF/services/org/apache/camel/thread-pool-factory b/components/camel-opentelemetry2/src/generated/resources/META-INF/services/org/apache/camel/thread-pool-factory deleted file mode 100644 index 31eb3a73f0bc1..0000000000000 --- a/components/camel-opentelemetry2/src/generated/resources/META-INF/services/org/apache/camel/thread-pool-factory +++ /dev/null @@ -1,2 +0,0 @@ -# Generated by camel build tools - do NOT edit this file! -class=org.apache.camel.opentelemetry2.OpenTelemetryInstrumentedThreadPoolFactory diff --git a/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryInstrumentedThreadFactoryListener.java b/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryInstrumentedThreadFactoryListener.java deleted file mode 100644 index f2eaae4bcd06b..0000000000000 --- a/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryInstrumentedThreadFactoryListener.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.camel.opentelemetry2; - -import java.util.concurrent.ThreadFactory; - -import io.opentelemetry.context.Context; -import org.apache.camel.spi.ExecutorServiceManager; -import org.apache.camel.spi.annotations.JdkService; - -@JdkService(ExecutorServiceManager.ThreadFactoryListener.FACTORY) -public class OpenTelemetryInstrumentedThreadFactoryListener implements ExecutorServiceManager.ThreadFactoryListener { - - @Override - public ThreadFactory onNewThreadFactory(Object source, ThreadFactory factory) { - return runnable -> factory.newThread(Context.current().wrap(runnable)); - } -} diff --git a/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryInstrumentedThreadPoolFactory.java b/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryInstrumentedThreadPoolFactory.java deleted file mode 100644 index 9c70dbaf1b199..0000000000000 --- a/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryInstrumentedThreadPoolFactory.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.camel.opentelemetry2; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; - -import io.opentelemetry.context.Context; -import org.apache.camel.spi.ThreadPoolFactory; -import org.apache.camel.spi.ThreadPoolProfile; -import org.apache.camel.spi.annotations.JdkService; -import org.apache.camel.support.DefaultThreadPoolFactory; - -@JdkService(ThreadPoolFactory.FACTORY) -public class OpenTelemetryInstrumentedThreadPoolFactory extends DefaultThreadPoolFactory { - - @Override - public ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { - return Context.taskWrapping(super.newCachedThreadPool(threadFactory)); - } - - @Override - public ExecutorService newThreadPool( - int corePoolSize, - int maxPoolSize, - long keepAliveTime, - TimeUnit timeUnit, - int maxQueueSize, - boolean allowCoreThreadTimeOut, - RejectedExecutionHandler rejectedExecutionHandler, - ThreadFactory threadFactory) - throws IllegalArgumentException { - - ExecutorService executorService = super.newThreadPool( - corePoolSize, - maxPoolSize, - keepAliveTime, - timeUnit, - maxQueueSize, - allowCoreThreadTimeOut, - rejectedExecutionHandler, - threadFactory); - - return Context.taskWrapping(executorService); - } - - @Override - public ScheduledExecutorService newScheduledThreadPool(ThreadPoolProfile profile, ThreadFactory threadFactory) { - return Context.taskWrapping(super.newScheduledThreadPool(profile, threadFactory)); - } - -} diff --git a/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetrySpanAdapter.java b/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetrySpanAdapter.java index b6c7a07202153..bff54e03c0989 100644 --- a/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetrySpanAdapter.java +++ b/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetrySpanAdapter.java @@ -18,7 +18,6 @@ import java.util.Map; -import io.opentelemetry.api.baggage.Baggage; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.trace.Span; @@ -29,18 +28,12 @@ public class OpenTelemetrySpanAdapter implements org.apache.camel.telemetry.Span { private static final String DEFAULT_EVENT_NAME = "log"; - static final String BAGGAGE_CAMEL_FLAG = "camelScope"; private final Span otelSpan; - private final Baggage baggage; private Scope scope; - private Scope baggageScope; - protected OpenTelemetrySpanAdapter(Span otelSpan, Baggage baggage) { + protected OpenTelemetrySpanAdapter(Span otelSpan) { this.otelSpan = otelSpan; - // We store an important flag in the baggage in order to verify if the - // root span was generated internally or from a third party dependency. - this.baggage = baggage.toBuilder().put(BAGGAGE_CAMEL_FLAG, "true").build(); } protected Span getSpan() { @@ -49,7 +42,6 @@ protected Span getSpan() { protected void makeCurrent() { this.scope = this.otelSpan.makeCurrent(); - this.baggageScope = this.baggage.makeCurrent(); } protected void end() { @@ -57,18 +49,11 @@ protected void end() { } protected void close() { - if (baggageScope != null) { - this.baggageScope.close(); - } if (scope != null) { this.scope.close(); } } - protected Baggage getBaggage() { - return this.baggage; - } - @Override public void log(Map fields) { this.otelSpan.addEvent(getEventNameFromFields(fields), convertToAttributes(fields)); @@ -126,7 +111,7 @@ private Attributes convertToAttributes(Map fields) { @Override public String toString() { - return "OpenTelemetrySpanAdapter [span=" + otelSpan + ", baggage=" + baggage + "]"; + return "OpenTelemetrySpanAdapter [span=" + otelSpan + "]"; } } diff --git a/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryTracer.java b/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryTracer.java index 381488b6e3002..deb85b11b77bf 100644 --- a/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryTracer.java +++ b/components/camel-opentelemetry2/src/main/java/org/apache/camel/opentelemetry2/OpenTelemetryTracer.java @@ -17,7 +17,6 @@ package org.apache.camel.opentelemetry2; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.baggage.Baggage; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; @@ -98,24 +97,14 @@ private OpentelemetrySpanLifecycleManager(Tracer tracer, ContextPropagators cont @Override public Span create(String spanName, Span parent, SpanContextPropagationExtractor extractor) { SpanBuilder builder = tracer.spanBuilder(spanName); - Baggage baggage = null; if (parent != null) { OpenTelemetrySpanAdapter otelParentSpan = (OpenTelemetrySpanAdapter) parent; builder = builder.setParent(Context.current().with(otelParentSpan.getSpan())); - baggage = otelParentSpan.getBaggage(); } else { - Context current = Context.root(); - // If the current span was generated by Camel, then, this is a "dirty" context. - // A "dirty" context happens when the Camel thread local is reused and - // due to the way Camel async works, can't reliably clean its context before reusing it. - if (Baggage.current().getEntryValue(OpenTelemetrySpanAdapter.BAGGAGE_CAMEL_FLAG) == null) { - // Not "dirty" context. In this case a Span exists and the current span was generated by some third party dependency (ie, vertx) - // therefore we need to consider this span as the root on such a trace. - current = Context.current(); - } - // Try to get parent from context propagation (upstream traces) - Context ctx = contextPropagators.getTextMapPropagator().extract(current, extractor, + // Always use Context.root() as the base — context propagation is handled + // exclusively via Exchange headers (W3C traceparent), not ThreadLocal. + Context ctx = contextPropagators.getTextMapPropagator().extract(Context.root(), extractor, new TextMapGetter() { @Override public Iterable keys(SpanContextPropagationExtractor carrier) { @@ -132,10 +121,9 @@ public String get(SpanContextPropagationExtractor carrier, String key) { }); builder = builder.setParent(ctx); - baggage = Baggage.fromContext(ctx); } - return new OpenTelemetrySpanAdapter(builder.startSpan(), baggage); + return new OpenTelemetrySpanAdapter(builder.startSpan()); } @Override @@ -160,9 +148,6 @@ public void close(Span span) { public void inject(Span span, SpanContextPropagationInjector injector, boolean includeTracing) { OpenTelemetrySpanAdapter otelSpan = (OpenTelemetrySpanAdapter) span; Context ctx = Context.current().with(otelSpan.getSpan()); - if (otelSpan.getBaggage() != null) { - ctx = ctx.with(otelSpan.getBaggage()); - } contextPropagators.getTextMapPropagator().inject(ctx, injector, (carrier, key, value) -> carrier.put(key, value)); if (includeTracing) { diff --git a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanInjection.java b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanInjectionTest.java similarity index 84% rename from components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanInjection.java rename to components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanInjectionTest.java index 917aaba3b8b95..78b842023a01b 100644 --- a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanInjection.java +++ b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanInjectionTest.java @@ -23,7 +23,6 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.Span; -import io.opentelemetry.context.Scope; import io.opentelemetry.sdk.trace.data.SpanData; import org.apache.camel.CamelContext; import org.apache.camel.CamelContextAware; @@ -38,7 +37,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -public class SpanInjection extends OpenTelemetryTracerTestSupport { +public class SpanInjectionTest extends OpenTelemetryTracerTestSupport { @Override protected CamelContext createCamelContext() throws Exception { @@ -54,30 +53,30 @@ protected CamelContext createCamelContext() throws Exception { @Test void testRouteSingleRequest() throws IOException { - // NOTE: we simulate that any external third party is the root parent, as we want Camel traces to depend on it. - Span span = otelExtension.getOpenTelemetry().getTracer("traceTest").spanBuilder("mySpan").startSpan(); + // Simulate an external third party parent span by injecting traceparent header + // into the Exchange (the way HTTP components do it), not via ThreadLocal. + Span span = otelExtension.getOpenTelemetry().getTracer("traceTest").spanBuilder("mySpan").setNoParent().startSpan(); String expectedTrace = span.getSpanContext().getTraceId(); String expectedSpan = span.getSpanContext().getSpanId(); - try (Scope scope = span.makeCurrent()) { - template.sendBody("direct:start", "my-body"); - Map traces = otelExtension.getTraces(); - assertEquals(1, traces.size()); - checkTrace(traces.values().iterator().next(), expectedTrace, expectedSpan); - } + String traceparent = "00-" + expectedTrace + "-" + expectedSpan + "-01"; + template.sendBodyAndHeader("direct:start", "my-body", "traceparent", traceparent); + Map traces = otelExtension.getTraces(); + assertEquals(1, traces.size()); + checkTrace(traces.values().iterator().next(), expectedTrace, expectedSpan); } @Test void testRouteMultipleRequests() throws IOException { + otelExtension.clearSpans(); int i = 10; Map tracesRef = new HashMap<>(); for (int j = 0; j < i; j++) { - // NOTE: we simulate that any external third party is the root parent, as we want Camel traces to depend on it. - Span span = otelExtension.getOpenTelemetry().getTracer("traceTest").spanBuilder("mySpan").startSpan(); - // We hold the reference of each parent span for each trace + // Simulate external parent via traceparent header + Span span = otelExtension.getOpenTelemetry().getTracer("traceTest").spanBuilder("mySpan").setNoParent().startSpan(); tracesRef.put(span.getSpanContext().getTraceId(), span.getSpanContext().getSpanId()); - try (Scope scope = span.makeCurrent()) { - context.createProducerTemplate().sendBody("direct:start", "Hello!"); - } + String traceparent + = "00-" + span.getSpanContext().getTraceId() + "-" + span.getSpanContext().getSpanId() + "-01"; + context.createProducerTemplate().sendBodyAndHeader("direct:start", "Hello!", "traceparent", traceparent); } Map traces = otelExtension.getTraces(); // Each trace should have a unique trace id. It is enough to assert that @@ -123,7 +122,7 @@ private void checkTrace(OtelTrace trace, String parentTrace, String parentSpan) assertEquals(Op.EVENT_PROCESS.toString(), innerProcessor.getAttributes().get(AttributeKey.stringKey("op"))); // Validate hierarchy - // The parent now must be a valid trace as it was generated by a third party (our test in this case). + // The parent now must be a valid trace as it was provided via traceparent header. assertTrue(testProducer.getParentSpanContext().isValid()); assertEquals(parentSpan, testProducer.getParentSpanContext().getSpanId());