This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch CAMEL-23225-otel-context-propagation-task-decorator in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git
commit d7ea516a779247a613a9b3c3fee303d7b9d5d273 Author: Guillaume Nodet <[email protected]> AuthorDate: Wed Mar 25 07:57:43 2026 +0100 CAMEL-23225: Propagate OTel context in Spring Boot task executors Register an OTel context-propagating TaskDecorator bean in the camel-opentelemetry2-starter auto-configuration. This ensures Spring's ThreadPoolTaskExecutor (used by camel-platform-http-starter) propagates OpenTelemetry context to worker threads, preventing context leaks in async processing scenarios. Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../camel-opentelemetry2-starter/pom.xml | 12 ++++ .../starter/OpenTelemetry2AutoConfiguration.java | 16 +++++ .../OtelContextPropagatingTaskDecoratorTest.java | 78 ++++++++++++++++++++++ 3 files changed, 106 insertions(+) diff --git a/components-starter/camel-opentelemetry2-starter/pom.xml b/components-starter/camel-opentelemetry2-starter/pom.xml index 451f403831f..ae6e903f8be 100644 --- a/components-starter/camel-opentelemetry2-starter/pom.xml +++ b/components-starter/camel-opentelemetry2-starter/pom.xml @@ -39,6 +39,18 @@ <artifactId>camel-opentelemetry2</artifactId> <version>${camel-version}</version> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <version>${spring-boot-version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test-spring-junit6</artifactId> + <version>${camel-version}</version> + <scope>test</scope> + </dependency> <!--START OF GENERATED CODE--> <dependency> <groupId>org.apache.camel.springboot</groupId> diff --git a/components-starter/camel-opentelemetry2-starter/src/main/java/org/apache/camel/opentelemetry2/starter/OpenTelemetry2AutoConfiguration.java b/components-starter/camel-opentelemetry2-starter/src/main/java/org/apache/camel/opentelemetry2/starter/OpenTelemetry2AutoConfiguration.java index 17f79d9de6a..159e0604f48 100644 --- a/components-starter/camel-opentelemetry2-starter/src/main/java/org/apache/camel/opentelemetry2/starter/OpenTelemetry2AutoConfiguration.java +++ b/components-starter/camel-opentelemetry2-starter/src/main/java/org/apache/camel/opentelemetry2/starter/OpenTelemetry2AutoConfiguration.java @@ -16,6 +16,8 @@ */ package org.apache.camel.opentelemetry2.starter; +import io.opentelemetry.context.Context; + import org.apache.camel.CamelContext; import org.apache.camel.opentelemetry2.OpenTelemetryTracer; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -23,6 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.TaskDecorator; @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(OpenTelemetry2ConfigurationProperties.class) @@ -45,4 +48,17 @@ public class OpenTelemetry2AutoConfiguration { return ottracer; } + + @Bean + @ConditionalOnMissingBean(TaskDecorator.class) + TaskDecorator otelContextPropagatingTaskDecorator() { + return runnable -> { + Context context = Context.current(); + return () -> { + try (var ignored = context.makeCurrent()) { + runnable.run(); + } + }; + }; + } } diff --git a/components-starter/camel-opentelemetry2-starter/src/test/java/org/apache/camel/opentelemetry2/starter/OtelContextPropagatingTaskDecoratorTest.java b/components-starter/camel-opentelemetry2-starter/src/test/java/org/apache/camel/opentelemetry2/starter/OtelContextPropagatingTaskDecoratorTest.java new file mode 100644 index 00000000000..7e128c719a8 --- /dev/null +++ b/components-starter/camel-opentelemetry2-starter/src/test/java/org/apache/camel/opentelemetry2/starter/OtelContextPropagatingTaskDecoratorTest.java @@ -0,0 +1,78 @@ +/* + * 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.starter; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextKey; + +import org.apache.camel.test.spring.junit6.CamelSpringBootTest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.task.TaskDecorator; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@CamelSpringBootTest +@EnableAutoConfiguration +@SpringBootTest(classes = OtelContextPropagatingTaskDecoratorTest.class) +public class OtelContextPropagatingTaskDecoratorTest { + + private static final ContextKey<String> TEST_KEY = ContextKey.named("test-key"); + + @Autowired + private TaskDecorator taskDecorator; + + @Test + void taskDecoratorBeanIsRegistered() { + assertNotNull(taskDecorator); + } + + @Test + void taskDecoratorPropagatesOtelContext() throws Exception { + AtomicReference<String> valueOnWorkerThread = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + + // Set a value in the OTel context on the current thread + Context contextWithValue = Context.current().with(TEST_KEY, "propagated-value"); + try (var ignored = contextWithValue.makeCurrent()) { + // Decorate the runnable while the context is active + Runnable decorated = taskDecorator.decorate(() -> { + valueOnWorkerThread.set(Context.current().get(TEST_KEY)); + latch.countDown(); + }); + + // Run on a different thread — context should still be propagated + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + executor.execute(decorated); + latch.await(); + } finally { + executor.shutdown(); + } + } + + assertEquals("propagated-value", valueOnWorkerThread.get()); + } +}
