This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 10ae2cedb594 CAMEL-23312: Add SpanKind support to OpenTelemetry2 
component (#22653)
10ae2cedb594 is described below

commit 10ae2cedb59480c109a42956ec3c8f497a829455
Author: JiriOndrusek <[email protected]>
AuthorDate: Thu Apr 30 16:00:20 2026 +0200

    CAMEL-23312: Add SpanKind support to OpenTelemetry2 component (#22653)
---
 .../MicrometerObservabilityTracer.java             |   2 +-
 .../camel/opentelemetry2/OpenTelemetryTracer.java  |   7 +-
 .../apache/camel/opentelemetry2/SpanKindTest.java  | 155 +++++++++++++++++++++
 .../opentelemetry2/mock/MockHttpComponent.java}    |  25 ++--
 .../opentelemetry2/mock/MockHttpEndpoint.java}     |  25 ++--
 .../opentelemetry2/mock/MockHttpProducer.java}     |  23 ++-
 .../mock/MockHttpSpanDecorator.java}               |  23 ++-
 .../opentelemetry2/mock/MockKafkaComponent.java}   |  25 ++--
 .../opentelemetry2/mock/MockKafkaEndpoint.java}    |  32 ++---
 .../opentelemetry2/mock/MockKafkaProducer.java}    |  38 +++--
 .../mock/MockKafkaSpanDecorator.java}              |  24 ++--
 .../org.apache.camel.telemetry.SpanDecorator       |  21 +++
 .../camel/telemetrydev/TelemetryDevTracer.java     |   2 +-
 .../camel-telemetry/src/main/docs/telemetry.adoc   |   2 +-
 .../org/apache/camel/telemetry/SpanDecorator.java  |   1 +
 .../camel/telemetry/SpanLifecycleManager.java      |   2 +-
 .../java/org/apache/camel/telemetry/Tracer.java    |   8 +-
 .../decorators/AbstractHttpSpanDecorator.java      |  12 ++
 .../decorators/AbstractMessagingSpanDecorator.java |  11 ++
 .../decorators/AbstractSpanDecorator.java          |   5 +
 .../apache/camel/telemetry/mock/MockTracer.java    |   2 +-
 .../ROOT/pages/camel-4x-upgrade-guide-4_21.adoc    |  34 +++++
 22 files changed, 359 insertions(+), 120 deletions(-)

diff --git 
a/components/camel-micrometer-observability/src/main/java/org/apache/camel/micrometer/observability/MicrometerObservabilityTracer.java
 
b/components/camel-micrometer-observability/src/main/java/org/apache/camel/micrometer/observability/MicrometerObservabilityTracer.java
index 4de010e86553..1244b976b279 100644
--- 
a/components/camel-micrometer-observability/src/main/java/org/apache/camel/micrometer/observability/MicrometerObservabilityTracer.java
+++ 
b/components/camel-micrometer-observability/src/main/java/org/apache/camel/micrometer/observability/MicrometerObservabilityTracer.java
@@ -126,7 +126,7 @@ public class MicrometerObservabilityTracer extends 
org.apache.camel.telemetry.Tr
         }
 
         @Override
-        public Span create(String spanName, Span parent, 
SpanContextPropagationExtractor extractor) {
+        public Span create(String spanName, String spanKind, Span parent, 
SpanContextPropagationExtractor extractor) {
             io.micrometer.tracing.Span span;
             if (parent != null) {
                 MicrometerObservabilitySpanAdapter microObsParentSpan = 
(MicrometerObservabilitySpanAdapter) parent;
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 b1a3ed47daa7..257e5afd250b 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
@@ -22,6 +22,7 @@ import java.util.Map;
 import io.opentelemetry.api.GlobalOpenTelemetry;
 import io.opentelemetry.api.baggage.Baggage;
 import io.opentelemetry.api.trace.SpanBuilder;
+import io.opentelemetry.api.trace.SpanKind;
 import io.opentelemetry.api.trace.Tracer;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.context.propagation.ContextPropagators;
@@ -101,7 +102,7 @@ public class OpenTelemetryTracer extends 
org.apache.camel.telemetry.Tracer {
         }
 
         @Override
-        public Span create(String spanName, Span parent, 
SpanContextPropagationExtractor extractor) {
+        public Span create(String spanName, String spanKind, Span parent, 
SpanContextPropagationExtractor extractor) {
             SpanBuilder builder = tracer.spanBuilder(spanName);
             Baggage baggage = Baggage.current();
 
@@ -143,6 +144,10 @@ public class OpenTelemetryTracer extends 
org.apache.camel.telemetry.Tracer {
             }
             baggage = getBaggageFromHeaders(baggage, extractor);
 
+            if (spanKind != null) {
+                builder.setSpanKind(SpanKind.valueOf(spanKind));
+            }
+
             return new OpenTelemetrySpanAdapter(builder.startSpan(), baggage);
         }
 
diff --git 
a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanKindTest.java
 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanKindTest.java
new file mode 100644
index 000000000000..41e0d021d6d5
--- /dev/null
+++ 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanKindTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.List;
+import java.util.Map;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.sdk.trace.data.SpanData;
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.opentelemetry2.CamelOpenTelemetryExtension.OtelTrace;
+import org.apache.camel.opentelemetry2.mock.MockHttpComponent;
+import org.apache.camel.opentelemetry2.mock.MockKafkaComponent;
+import org.apache.camel.telemetry.Op;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Test that verifies SpanKind is set correctly for different component types.
+ */
+public class SpanKindTest extends OpenTelemetryTracerTestSupport {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        OpenTelemetryTracer tst = new OpenTelemetryTracer();
+        
tst.setTracer(otelExtension.getOpenTelemetry().getTracer("spanKindTest"));
+        
tst.setContextPropagators(otelExtension.getOpenTelemetry().getPropagators());
+        CamelContext context = super.createCamelContext();
+
+        // Register mock HTTP component for testing
+        context.addComponent("mock-http", new MockHttpComponent());
+        // Register mock Kafka component for testing
+        context.addComponent("mock-kafka", new MockKafkaComponent());
+
+        CamelContextAware.trySetCamelContext(tst, context);
+        tst.init(context);
+        return context;
+    }
+
+    @Test
+    void testDirectComponentHasInternalSpanKind() {
+        template.sendBody("direct:start", "test");
+
+        List<OtelTrace> traces = 
List.copyOf(otelExtension.getTraces().values());
+        assertEquals(1, traces.size());
+
+        List<SpanData> spans = traces.get(0).getSpans();
+
+        // Find the direct:start EVENT_SENT span
+        SpanData directSentSpan = getSpan(spans, "direct://start", 
Op.EVENT_SENT);
+        assertEquals(SpanKind.INTERNAL, directSentSpan.getKind(),
+                "direct:start EVENT_SENT should have INTERNAL SpanKind");
+
+        // Find the direct:start EVENT_RECEIVED span
+        SpanData directReceivedSpan = getSpan(spans, "direct://start", 
Op.EVENT_RECEIVED);
+        assertEquals(SpanKind.INTERNAL, directReceivedSpan.getKind(),
+                "direct:start EVENT_RECEIVED should have INTERNAL SpanKind");
+    }
+
+    @Test
+    void testHttpComponentHasClientServerSpanKind() throws Exception {
+        MockEndpoint mockEndpoint = getMockEndpoint("mock:result");
+        mockEndpoint.expectedMessageCount(1);
+
+        template.sendBody("direct:httpClient", "test message");
+
+        mockEndpoint.assertIsSatisfied();
+
+        List<OtelTrace> traces = 
List.copyOf(otelExtension.getTraces().values());
+        assertEquals(1, traces.size());
+
+        List<SpanData> spans = traces.get(0).getSpans();
+
+        // Find the mock-http EVENT_SENT span (client side)
+        SpanData httpClientSpan = getSpan(spans, "mock-http://testEndpoint";, 
Op.EVENT_SENT);
+        assertEquals(SpanKind.CLIENT, httpClientSpan.getKind(),
+                "HTTP EVENT_SENT should have CLIENT SpanKind");
+    }
+
+    @Test
+    void testKafkaComponentHasProducerSpanKindAndInheritedProperties() throws 
Exception {
+        MockEndpoint mockEndpoint = getMockEndpoint("mock:result");
+        mockEndpoint.expectedMessageCount(1);
+
+        // Send with Kafka headers that would normally be set before/during 
sending
+        template.sendBodyAndHeaders("direct:kafkaProducer", "test message",
+                Map.of("kafka.KEY", "test-key",
+                        "kafka.PARTITION", 0,
+                        "kafka.OFFSET", "12345"));
+
+        mockEndpoint.assertIsSatisfied();
+
+        List<OtelTrace> traces = 
List.copyOf(otelExtension.getTraces().values());
+        assertEquals(1, traces.size());
+
+        List<SpanData> spans = traces.get(0).getSpans();
+
+        // Find the mock-kafka EVENT_SENT span
+        SpanData kafkaSpan = getSpan(spans, "mock-kafka://testTopic", 
Op.EVENT_SENT);
+
+        // Verify OpenTelemetry-specific SpanKind
+        assertEquals(SpanKind.PRODUCER, kafkaSpan.getKind(),
+                "Kafka EVENT_SENT should have PRODUCER SpanKind");
+
+        // Verify inherited properties from camel-telemetry KafkaSpanDecorator
+        assertEquals("0", 
kafkaSpan.getAttributes().get(AttributeKey.stringKey("kafka.partition")),
+                "Should have kafka.partition tag from inherited decorator");
+        assertEquals("12345", 
kafkaSpan.getAttributes().get(AttributeKey.stringKey("kafka.offset")),
+                "Should have kafka.offset tag from inherited decorator");
+        assertEquals("test-key", 
kafkaSpan.getAttributes().get(AttributeKey.stringKey("kafka.key")),
+                "Should have kafka.key tag from inherited decorator");
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start")
+                        .log("Processing message");
+
+                // Mock HTTP client route - tests CLIENT SpanKind
+                from("direct:httpClient")
+                        .to("mock-http://testEndpoint";)
+                        .to("mock:result");
+
+                // Mock Kafka producer route - tests PRODUCER SpanKind and 
inherited properties
+                from("direct:kafkaProducer")
+                        .to("mock-kafka://testTopic")
+                        .to("mock:result");
+            }
+        };
+    }
+
+}
diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockHttpComponent.java
similarity index 58%
copy from 
components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
copy to 
components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockHttpComponent.java
index 501dd2430f20..9d74ab627166 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
+++ 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockHttpComponent.java
@@ -14,21 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.telemetry;
+package org.apache.camel.opentelemetry2.mock;
+
+import java.util.Map;
 
 /**
- * This interface is used to manage the lifecycle of a Span.
+ * Mock HTTP component for testing SpanKind. This component is recognized by 
HttpSpanDecorator based on its class name.
  */
-public interface SpanLifecycleManager {
-
-    Span create(String spanName, Span parent, SpanContextPropagationExtractor 
extractor);
-
-    void activate(Span span);
-
-    void deactivate(Span span);
-
-    void close(Span span);
-
-    void inject(Span span, SpanContextPropagationInjector injector, boolean 
includeTracing);
+public class MockHttpComponent extends 
org.apache.camel.support.DefaultComponent {
 
+    @Override
+    protected org.apache.camel.Endpoint createEndpoint(String uri, String 
remaining, Map<String, Object> parameters)
+            throws Exception {
+        MockHttpEndpoint endpoint = new MockHttpEndpoint(uri, this);
+        setProperties(endpoint, parameters);
+        return endpoint;
+    }
 }
diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockHttpEndpoint.java
similarity index 56%
copy from 
components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
copy to 
components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockHttpEndpoint.java
index 501dd2430f20..ee1d5e061862 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
+++ 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockHttpEndpoint.java
@@ -14,21 +14,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.telemetry;
+package org.apache.camel.opentelemetry2.mock;
 
 /**
- * This interface is used to manage the lifecycle of a Span.
+ * Mock HTTP endpoint for testing SpanKind.
  */
-public interface SpanLifecycleManager {
+class MockHttpEndpoint extends org.apache.camel.support.DefaultEndpoint {
 
-    Span create(String spanName, Span parent, SpanContextPropagationExtractor 
extractor);
+    public MockHttpEndpoint(String endpointUri, org.apache.camel.Component 
component) {
+        super(endpointUri, component);
+    }
 
-    void activate(Span span);
-
-    void deactivate(Span span);
-
-    void close(Span span);
-
-    void inject(Span span, SpanContextPropagationInjector injector, boolean 
includeTracing);
+    @Override
+    public org.apache.camel.Producer createProducer() throws Exception {
+        return new MockHttpProducer(this);
+    }
 
+    @Override
+    public org.apache.camel.Consumer createConsumer(org.apache.camel.Processor 
processor) throws Exception {
+        throw new IllegalArgumentException("Not used in MockHttpEndpoint");
+    }
 }
diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockHttpProducer.java
similarity index 61%
copy from 
components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
copy to 
components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockHttpProducer.java
index 501dd2430f20..11530a443255 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
+++ 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockHttpProducer.java
@@ -14,21 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.telemetry;
+package org.apache.camel.opentelemetry2.mock;
 
 /**
- * This interface is used to manage the lifecycle of a Span.
+ * Mock HTTP producer that just echoes the input.
  */
-public interface SpanLifecycleManager {
+class MockHttpProducer extends org.apache.camel.support.DefaultProducer {
 
-    Span create(String spanName, Span parent, SpanContextPropagationExtractor 
extractor);
-
-    void activate(Span span);
-
-    void deactivate(Span span);
-
-    void close(Span span);
-
-    void inject(Span span, SpanContextPropagationInjector injector, boolean 
includeTracing);
+    public MockHttpProducer(org.apache.camel.Endpoint endpoint) {
+        super(endpoint);
+    }
 
+    @Override
+    public void process(org.apache.camel.Exchange exchange) throws Exception {
+        // Simple echo - set response body to request body
+        exchange.getMessage().setBody("HTTP Response: " + 
exchange.getIn().getBody());
+    }
 }
diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockHttpSpanDecorator.java
similarity index 66%
copy from 
components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
copy to 
components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockHttpSpanDecorator.java
index 501dd2430f20..e8597fbfca97 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
+++ 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockHttpSpanDecorator.java
@@ -14,21 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.telemetry;
+package org.apache.camel.opentelemetry2.mock;
 
 /**
- * This interface is used to manage the lifecycle of a Span.
+ * Span decorator for mock HTTP component used in tests.
  */
-public interface SpanLifecycleManager {
+public class MockHttpSpanDecorator extends 
org.apache.camel.telemetry.decorators.AbstractHttpSpanDecorator {
 
-    Span create(String spanName, Span parent, SpanContextPropagationExtractor 
extractor);
-
-    void activate(Span span);
-
-    void deactivate(Span span);
-
-    void close(Span span);
-
-    void inject(Span span, SpanContextPropagationInjector injector, boolean 
includeTracing);
+    @Override
+    public String getComponent() {
+        return "mock-http";
+    }
 
+    @Override
+    public String getComponentClassName() {
+        return MockHttpComponent.class.getName();
+    }
 }
diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockKafkaComponent.java
similarity index 60%
copy from 
components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
copy to 
components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockKafkaComponent.java
index 501dd2430f20..8ee1a0725ecd 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
+++ 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockKafkaComponent.java
@@ -14,21 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.telemetry;
+package org.apache.camel.opentelemetry2.mock;
+
+import java.util.Map;
 
 /**
- * This interface is used to manage the lifecycle of a Span.
+ * Mock Kafka component for testing SpanKind and inherited properties.
  */
-public interface SpanLifecycleManager {
-
-    Span create(String spanName, Span parent, SpanContextPropagationExtractor 
extractor);
-
-    void activate(Span span);
-
-    void deactivate(Span span);
-
-    void close(Span span);
-
-    void inject(Span span, SpanContextPropagationInjector injector, boolean 
includeTracing);
+public class MockKafkaComponent extends 
org.apache.camel.support.DefaultComponent {
 
+    @Override
+    protected org.apache.camel.Endpoint createEndpoint(String uri, String 
remaining, Map<String, Object> parameters)
+            throws Exception {
+        MockKafkaEndpoint endpoint = new MockKafkaEndpoint(uri, this);
+        setProperties(endpoint, parameters);
+        return endpoint;
+    }
 }
diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanDecorator.java
 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockKafkaEndpoint.java
similarity index 54%
copy from 
components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanDecorator.java
copy to 
components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockKafkaEndpoint.java
index 7b019e38fd16..c9fcd1d58a7e 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanDecorator.java
+++ 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockKafkaEndpoint.java
@@ -14,28 +14,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.telemetry;
-
-import org.apache.camel.Endpoint;
-import org.apache.camel.Exchange;
+package org.apache.camel.opentelemetry2.mock;
 
 /**
- * This interface represents a decorator specific to the component/endpoint 
being instrumented.
+ * Mock Kafka endpoint for testing SpanKind and inherited properties.
  */
-public interface SpanDecorator {
-
-    void beforeTracingEvent(Span span, Exchange exchange, Endpoint endpoint);
-
-    void afterTracingEvent(Span span, Exchange exchange);
-
-    String getComponent();
-
-    String getComponentClassName();
-
-    String getOperationName(Exchange exchange, Endpoint endpoint);
+class MockKafkaEndpoint extends org.apache.camel.support.DefaultEndpoint {
 
-    SpanContextPropagationExtractor getExtractor(Exchange exchange);
+    public MockKafkaEndpoint(String endpointUri, org.apache.camel.Component 
component) {
+        super(endpointUri, component);
+    }
 
-    SpanContextPropagationInjector getInjector(Exchange exchange);
+    @Override
+    public org.apache.camel.Producer createProducer() throws Exception {
+        return new MockKafkaProducer(this);
+    }
 
+    @Override
+    public org.apache.camel.Consumer createConsumer(org.apache.camel.Processor 
processor) throws Exception {
+        throw new UnsupportedOperationException("Consumer not implemented for 
mock Kafka");
+    }
 }
diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanDecorator.java
 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockKafkaProducer.java
similarity index 50%
copy from 
components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanDecorator.java
copy to 
components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockKafkaProducer.java
index 7b019e38fd16..033993a5d226 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanDecorator.java
+++ 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockKafkaProducer.java
@@ -14,28 +14,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.telemetry;
-
-import org.apache.camel.Endpoint;
-import org.apache.camel.Exchange;
+package org.apache.camel.opentelemetry2.mock;
 
 /**
- * This interface represents a decorator specific to the component/endpoint 
being instrumented.
+ * Mock Kafka producer that simulates Kafka headers.
  */
-public interface SpanDecorator {
-
-    void beforeTracingEvent(Span span, Exchange exchange, Endpoint endpoint);
-
-    void afterTracingEvent(Span span, Exchange exchange);
-
-    String getComponent();
-
-    String getComponentClassName();
-
-    String getOperationName(Exchange exchange, Endpoint endpoint);
-
-    SpanContextPropagationExtractor getExtractor(Exchange exchange);
-
-    SpanContextPropagationInjector getInjector(Exchange exchange);
-
+class MockKafkaProducer extends org.apache.camel.support.DefaultProducer {
+
+    public MockKafkaProducer(org.apache.camel.Endpoint endpoint) {
+        super(endpoint);
+    }
+
+    @Override
+    public void process(org.apache.camel.Exchange exchange) throws Exception {
+        // Simulate Kafka response with partition, offset, and key
+        // These headers would normally be set by the real Kafka producer
+        exchange.getMessage().setHeader("kafka.PARTITION", 0);
+        exchange.getMessage().setHeader("kafka.OFFSET", "12345");
+        exchange.getMessage().setHeader("kafka.KEY", "test-key");
+        exchange.getMessage().setBody("Kafka Response: " + 
exchange.getIn().getBody());
+    }
 }
diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockKafkaSpanDecorator.java
similarity index 60%
copy from 
components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
copy to 
components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockKafkaSpanDecorator.java
index 501dd2430f20..6d0cc534c437 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
+++ 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/mock/MockKafkaSpanDecorator.java
@@ -14,21 +14,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.telemetry;
+package org.apache.camel.opentelemetry2.mock;
 
 /**
- * This interface is used to manage the lifecycle of a Span.
+ * Span decorator for mock Kafka component used in tests. Extends the real 
KafkaSpanDecorator to inherit all
+ * Kafka-specific behavior (partition, offset, key tags) and adds SpanKind.
  */
-public interface SpanLifecycleManager {
+public class MockKafkaSpanDecorator extends 
org.apache.camel.telemetry.decorators.KafkaSpanDecorator {
 
-    Span create(String spanName, Span parent, SpanContextPropagationExtractor 
extractor);
-
-    void activate(Span span);
-
-    void deactivate(Span span);
-
-    void close(Span span);
-
-    void inject(Span span, SpanContextPropagationInjector injector, boolean 
includeTracing);
+    @Override
+    public String getComponent() {
+        return "mock-kafka";
+    }
 
+    @Override
+    public String getComponentClassName() {
+        return MockKafkaComponent.class.getName();
+    }
 }
diff --git 
a/components/camel-opentelemetry2/src/test/resources/META-INF/services/org.apache.camel.telemetry.SpanDecorator
 
b/components/camel-opentelemetry2/src/test/resources/META-INF/services/org.apache.camel.telemetry.SpanDecorator
new file mode 100644
index 000000000000..3a7f879efce0
--- /dev/null
+++ 
b/components/camel-opentelemetry2/src/test/resources/META-INF/services/org.apache.camel.telemetry.SpanDecorator
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+
+# Mock HTTP decorator for testing
+org.apache.camel.opentelemetry2.mock.MockHttpSpanDecorator
+# Mock Kafka decorator for testing
+org.apache.camel.opentelemetry2.mock.MockKafkaSpanDecorator
diff --git 
a/components/camel-telemetry-dev/src/main/java/org/apache/camel/telemetrydev/TelemetryDevTracer.java
 
b/components/camel-telemetry-dev/src/main/java/org/apache/camel/telemetrydev/TelemetryDevTracer.java
index f4d0cdee05a8..c57b5e133ec7 100644
--- 
a/components/camel-telemetry-dev/src/main/java/org/apache/camel/telemetrydev/TelemetryDevTracer.java
+++ 
b/components/camel-telemetry-dev/src/main/java/org/apache/camel/telemetrydev/TelemetryDevTracer.java
@@ -87,7 +87,7 @@ public class TelemetryDevTracer extends Tracer {
         }
 
         @Override
-        public Span create(String spanName, Span parent, 
SpanContextPropagationExtractor extractor) {
+        public Span create(String spanName, String spanKind, Span parent, 
SpanContextPropagationExtractor extractor) {
             Span span = DevSpanAdapter.buildSpan(spanName);
             String traceId = UUID.randomUUID().toString().replaceAll("-", "");
             if (parent != null) {
diff --git a/components/camel-telemetry/src/main/docs/telemetry.adoc 
b/components/camel-telemetry/src/main/docs/telemetry.adoc
index 6daad9776f25..148d7a9cfe67 100644
--- a/components/camel-telemetry/src/main/docs/telemetry.adoc
+++ b/components/camel-telemetry/src/main/docs/telemetry.adoc
@@ -147,7 +147,7 @@ The `initTracer()` is in charge to inject a concrete 
implementation of `SpanLife
 ```java
 public interface SpanLifecycleManager {
 
-    Span create(String spanName, Span parent, SpanContextPropagationExtractor 
extractor);
+    Span create(String spanName, String spanKind, Span parent, 
SpanContextPropagationExtractor extractor);
 
     void activate(Span span);
 
diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanDecorator.java
 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanDecorator.java
index 7b019e38fd16..95492f127ee6 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanDecorator.java
+++ 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanDecorator.java
@@ -38,4 +38,5 @@ public interface SpanDecorator {
 
     SpanContextPropagationInjector getInjector(Exchange exchange);
 
+    String getSpanKind(String operation);
 }
diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
index 501dd2430f20..837cc269657d 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
+++ 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanLifecycleManager.java
@@ -21,7 +21,7 @@ package org.apache.camel.telemetry;
  */
 public interface SpanLifecycleManager {
 
-    Span create(String spanName, Span parent, SpanContextPropagationExtractor 
extractor);
+    Span create(String spanName, String spanKind, Span parent, 
SpanContextPropagationExtractor extractor);
 
     void activate(Span span);
 
diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/Tracer.java
 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/Tracer.java
index 514fd2044565..0c5ada29eb23 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/Tracer.java
+++ 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/Tracer.java
@@ -270,7 +270,9 @@ public abstract class Tracer extends ServiceSupport 
implements CamelTracingServi
         SpanDecorator spanDecorator = spanDecoratorManager.get(endpoint);
         Span parentSpan = spanStorageManager.peek(exchange);
         String spanName = spanDecorator.getOperationName(exchange, endpoint);
-        Span span = spanLifecycleManager.create(spanName, parentSpan, 
spanDecorator.getExtractor(exchange));
+        String spanKind = spanDecorator.getSpanKind(op.toString());
+        Span span = spanLifecycleManager.create(spanName, spanKind, parentSpan,
+                spanDecorator.getExtractor(exchange));
         span.setTag(TagConstants.OP, op.toString());
         spanDecorator.beforeTracingEvent(span, exchange, endpoint);
         spanLifecycleManager.activate(span);
@@ -286,7 +288,9 @@ public abstract class Tracer extends ServiceSupport 
implements CamelTracingServi
             // there is some inconsistency
             LOG.warn("Processor tracing parent should not be null!");
         }
-        Span span = spanLifecycleManager.create(processorName, parentSpan, 
spanDecorator.getExtractor(exchange));
+        String spanKind = 
spanDecorator.getSpanKind(Op.EVENT_PROCESS.toString());
+        Span span = spanLifecycleManager.create(processorName, spanKind,
+                parentSpan, spanDecorator.getExtractor(exchange));
         span.setTag(TagConstants.OP, Op.EVENT_PROCESS.toString());
         spanDecorator.beforeTracingEvent(span, exchange, null);
         spanLifecycleManager.activate(span);
diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/AbstractHttpSpanDecorator.java
 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/AbstractHttpSpanDecorator.java
index e0ae5b91a376..24a1216e796c 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/AbstractHttpSpanDecorator.java
+++ 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/AbstractHttpSpanDecorator.java
@@ -19,6 +19,7 @@ package org.apache.camel.telemetry.decorators;
 import org.apache.camel.Endpoint;
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
+import org.apache.camel.telemetry.Op;
 import org.apache.camel.telemetry.Span;
 import org.apache.camel.telemetry.TagConstants;
 
@@ -105,4 +106,15 @@ public abstract class AbstractHttpSpanDecorator extends 
AbstractSpanDecorator {
             }
         }
     }
+
+    @Override
+    public String getSpanKind(String operationName) {
+        if (Op.EVENT_RECEIVED.name().equals(operationName)) {
+            return "SERVER";
+        } else if (Op.EVENT_SENT.name().equals(operationName)) {
+            return "CLIENT";
+        } else {
+            return "INTERNAL";
+        }
+    }
 }
diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/AbstractMessagingSpanDecorator.java
 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/AbstractMessagingSpanDecorator.java
index bd4852ffaea2..e592fe2c3b18 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/AbstractMessagingSpanDecorator.java
+++ 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/AbstractMessagingSpanDecorator.java
@@ -18,6 +18,7 @@ package org.apache.camel.telemetry.decorators;
 
 import org.apache.camel.Endpoint;
 import org.apache.camel.Exchange;
+import org.apache.camel.telemetry.Op;
 import org.apache.camel.telemetry.Span;
 import org.apache.camel.telemetry.TagConstants;
 
@@ -60,4 +61,14 @@ public abstract class AbstractMessagingSpanDecorator extends 
AbstractSpanDecorat
         return null;
     }
 
+    @Override
+    public String getSpanKind(String operationName) {
+        if (Op.EVENT_RECEIVED.name().equals(operationName)) {
+            return "CONSUMER";
+        } else if (Op.EVENT_SENT.name().equals(operationName)) {
+            return "PRODUCER";
+        } else {
+            return "INTERNAL";
+        }
+    }
 }
diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/AbstractSpanDecorator.java
 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/AbstractSpanDecorator.java
index ee7e8002e2e2..5995ad3ed32b 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/AbstractSpanDecorator.java
+++ 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/AbstractSpanDecorator.java
@@ -175,4 +175,9 @@ public abstract class AbstractSpanDecorator implements 
SpanDecorator {
     public SpanContextPropagationInjector getInjector(Exchange exchange) {
         return new 
CamelHeadersSpanContextPropagationInjector(exchange.getIn().getHeaders());
     }
+
+    @Override
+    public String getSpanKind(String operationName) {
+        return "INTERNAL";
+    }
 }
diff --git 
a/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/mock/MockTracer.java
 
b/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/mock/MockTracer.java
index 48e0467b9612..6479e8ebf213 100644
--- 
a/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/mock/MockTracer.java
+++ 
b/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/mock/MockTracer.java
@@ -53,7 +53,7 @@ public class MockTracer extends Tracer {
         Map<String, Span> inMemoryStorageMap = new HashMap<>();
 
         @Override
-        public Span create(String spanName, Span parentSpan, 
SpanContextPropagationExtractor extractor) {
+        public Span create(String spanName, String spanKind, Span parentSpan, 
SpanContextPropagationExtractor extractor) {
             Span span = MockSpanAdapter.buildSpan(spanName);
             String traceId = UUID.randomUUID().toString().replaceAll("-", "");
             if (parentSpan != null) {
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
index c9536d9da7be..517cc570bfc8 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
@@ -125,3 +125,37 @@ Camel stomp was deprecated with Camel 4.17. The stomp 
library didn't have any ac
 === camel-aws-xray removal
 
 Camel AWS X-Ray was deprecated with Camel 4.17. Amazon Web Services X-Ray 
service is in maintenance mode since February 2026. The component is now 
removed.
+
+=== camel-telemetry
+
+If you have custom telemetry implementations, the following interfaces have 
changed:
+
+==== `org.apache.camel.telemetry.SpanLifecycleManager`
+
+The `create` method signature has changed:
+
+[source,java]
+----
+// Old signature
+Span create(String spanName, Span parent, SpanContextPropagationExtractor 
extractor)
+
+// New signature
+Span create(String spanName, String spanKind, Span parent, 
SpanContextPropagationExtractor
+extractor)
+----
+
+==== `org.apache.camel.telemetry.SpanDecorator`
+
+A new method must be implemented:
+
+[source,java]
+----
+String getSpanKind(String operation)
+----
+
+This method should return the appropriate SpanKind based on the operation. 
Most implementations can
+extend from:
+
+- `AbstractSpanDecorator` (returns `INTERNAL` for all operations)
+- `AbstractHttpSpanDecorator` (returns `CLIENT` for `EVENT_SENT`, `SERVER` for 
`EVENT_RECEIVED`)
+- `AbstractMessagingSpanDecorator` (returns `PRODUCER` for `EVENT_SENT`, 
`CONSUMER` for `EVENT_RECEIVED`)
\ No newline at end of file


Reply via email to