From ef6f620169580cdc9f798a7126f0a1b0a032dc73 Mon Sep 17 00:00:00 2001 From: Jiri Ondrusek Date: Fri, 10 Apr 2026 15:59:46 +0200 Subject: [PATCH] Add SpanKind parameter to telemetry API to support proper span type classification --- .../MicrometerObservabilityTracer.java | 12 +-- .../opentelemetry2/OpenTelemetryTracer.java | 20 +++-- .../telemetrydev/TelemetryDevTracer.java | 7 +- .../telemetrydev/DisableEndpointTest.java | 5 +- .../telemetrydev/EnableProcessorsTest.java | 5 +- .../telemetrydev/MDCHeadersTraceTest.java | 5 +- .../SpanPropagationUpstreamTest.java | 5 +- .../telemetrydev/TelemetryDevTracerTest.java | 9 +- .../TelemetryDevTracerTestSupport.java | 41 ++++++++++ .../apache/camel/telemetry/SpanDecorator.java | 8 ++ .../org/apache/camel/telemetry/SpanKind.java | 39 +++++++++ .../camel/telemetry/SpanLifecycleManager.java | 2 +- .../org/apache/camel/telemetry/Tracer.java | 6 +- .../decorators/AbstractHttpSpanDecorator.java | 11 +++ .../AbstractMessagingSpanDecorator.java | 11 +++ .../decorators/AbstractSpanDecorator.java | 8 ++ .../camel/telemetry/mock/MockTracer.java | 11 ++- .../pages/camel-4x-upgrade-guide-4_20.adoc | 82 +++++++++++++++++++ 18 files changed, 250 insertions(+), 37 deletions(-) create mode 100644 components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanKind.java 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 76b18dd6fb4cc..408411485fea6 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 @@ -32,10 +32,7 @@ import org.apache.camel.spi.Configurer; import org.apache.camel.spi.annotations.JdkService; import org.apache.camel.support.CamelContextHelper; -import org.apache.camel.telemetry.Span; -import org.apache.camel.telemetry.SpanContextPropagationExtractor; -import org.apache.camel.telemetry.SpanContextPropagationInjector; -import org.apache.camel.telemetry.SpanLifecycleManager; +import org.apache.camel.telemetry.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -126,7 +123,9 @@ private MicrometerObservabilitySpanLifecycleManager() { } @Override - public Span create(String spanName, Span parent, SpanContextPropagationExtractor extractor) { + public Span create( + String spanName, SpanKind kind, Span parent, + SpanContextPropagationExtractor extractor) { io.micrometer.tracing.Span span; if (parent != null) { MicrometerObservabilitySpanAdapter microObsParentSpan = (MicrometerObservabilitySpanAdapter) parent; @@ -139,6 +138,9 @@ public Span create(String spanName, Span parent, SpanContextPropagationExtractor span = builder.start(); } span.name(spanName); + // Note: Micrometer determines span kind through Observation context types, + // not through direct span.kind() calls. The kind parameter is accepted + // for API compatibility but not used in this implementation. return new MicrometerObservabilitySpanAdapter(span); } 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..0ad51429172a8 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 @@ -28,10 +28,7 @@ import org.apache.camel.spi.Configurer; import org.apache.camel.spi.annotations.JdkService; import org.apache.camel.support.CamelContextHelper; -import org.apache.camel.telemetry.Span; -import org.apache.camel.telemetry.SpanContextPropagationExtractor; -import org.apache.camel.telemetry.SpanContextPropagationInjector; -import org.apache.camel.telemetry.SpanLifecycleManager; +import org.apache.camel.telemetry.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -96,8 +93,11 @@ private OpentelemetrySpanLifecycleManager(Tracer tracer, ContextPropagators cont } @Override - public Span create(String spanName, Span parent, SpanContextPropagationExtractor extractor) { + public Span create( + String spanName, SpanKind kind, Span parent, + SpanContextPropagationExtractor extractor) { SpanBuilder builder = tracer.spanBuilder(spanName); + builder = builder.setSpanKind(mapToSpanKind(kind)); Baggage baggage = null; if (parent != null) { @@ -171,6 +171,16 @@ public void inject(Span span, SpanContextPropagationInjector injector, boolean i } } + private io.opentelemetry.api.trace.SpanKind mapToSpanKind(org.apache.camel.telemetry.SpanKind kind) { + return switch (kind) { + case CLIENT -> io.opentelemetry.api.trace.SpanKind.CLIENT; + case SERVER -> io.opentelemetry.api.trace.SpanKind.SERVER; + case PRODUCER -> io.opentelemetry.api.trace.SpanKind.PRODUCER; + case CONSUMER -> io.opentelemetry.api.trace.SpanKind.CONSUMER; + case INTERNAL -> io.opentelemetry.api.trace.SpanKind.INTERNAL; + }; + } + } } 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 f4d0cdee05a8f..226007b397c14 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 @@ -25,6 +25,7 @@ import org.apache.camel.telemetry.Span; import org.apache.camel.telemetry.SpanContextPropagationExtractor; import org.apache.camel.telemetry.SpanContextPropagationInjector; +import org.apache.camel.telemetry.SpanKind; import org.apache.camel.telemetry.SpanLifecycleManager; import org.apache.camel.telemetry.Tracer; import org.slf4j.Logger; @@ -87,7 +88,9 @@ private DevSpanLifecycleManager(String traceFormat) { } @Override - public Span create(String spanName, Span parent, SpanContextPropagationExtractor extractor) { + public Span create( + String spanName, SpanKind kind, Span parent, + SpanContextPropagationExtractor extractor) { Span span = DevSpanAdapter.buildSpan(spanName); String traceId = UUID.randomUUID().toString().replaceAll("-", ""); if (parent != null) { @@ -101,6 +104,7 @@ public Span create(String spanName, Span parent, SpanContextPropagationExtractor LOG.error("TRACE ERROR: wrong format, could not split traceparent {}", upstreamTraceParent); span.setTag("traceid", traceId); span.setTag("spanid", UUID.randomUUID().toString().replaceAll("-", "")); + span.setTag("kind", kind.toString()); return span; } traceId = split[0]; @@ -110,6 +114,7 @@ public Span create(String spanName, Span parent, SpanContextPropagationExtractor } span.setTag("traceid", traceId); span.setTag("spanid", UUID.randomUUID().toString().replaceAll("-", "")); + span.setTag("kind", kind.toString()); return span; } diff --git a/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/DisableEndpointTest.java b/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/DisableEndpointTest.java index 869e49f3f666a..1a4e682916a68 100644 --- a/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/DisableEndpointTest.java +++ b/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/DisableEndpointTest.java @@ -16,7 +16,6 @@ */ package org.apache.camel.telemetrydev; -import java.io.IOException; import java.util.List; import java.util.Map; @@ -47,9 +46,9 @@ protected CamelContext createCamelContext() throws Exception { } @Test - void testProcessorsTraceRequest() throws IOException { + void testProcessorsTraceRequest() { template.sendBody("direct:start", "my-body"); - Map traces = tracesFromLog(); + Map traces = awaitTracesFromLog(1); assertEquals(1, traces.size()); checkTrace(traces.values().iterator().next()); } diff --git a/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/EnableProcessorsTest.java b/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/EnableProcessorsTest.java index 9c0a4b9226318..2a54665da5718 100644 --- a/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/EnableProcessorsTest.java +++ b/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/EnableProcessorsTest.java @@ -16,7 +16,6 @@ */ package org.apache.camel.telemetrydev; -import java.io.IOException; import java.util.List; import java.util.Map; @@ -46,9 +45,9 @@ protected CamelContext createCamelContext() throws Exception { } @Test - void testProcessorsTraceRequest() throws IOException { + void testProcessorsTraceRequest() { template.sendBody("direct:start", "my-body"); - Map traces = tracesFromLog(); + Map traces = awaitTracesFromLog(1); assertEquals(1, traces.size()); checkTrace(traces.values().iterator().next()); } diff --git a/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/MDCHeadersTraceTest.java b/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/MDCHeadersTraceTest.java index 2e9472aece6bc..64f9e39a3e4d9 100644 --- a/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/MDCHeadersTraceTest.java +++ b/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/MDCHeadersTraceTest.java @@ -16,7 +16,6 @@ */ package org.apache.camel.telemetrydev; -import java.io.IOException; import java.util.Map; import org.apache.camel.CamelContext; @@ -44,11 +43,11 @@ protected CamelContext createCamelContext() throws Exception { } @Test - void testProcessorsTraceRequest() throws InterruptedException, IOException { + void testProcessorsTraceRequest() throws InterruptedException { MockEndpoint mock = getMockEndpoint("mock:result"); mock.expectedMessageCount(1); template.sendBody("direct:start", "my-body"); - Map traces = tracesFromLog(); + Map traces = awaitTracesFromLog(1); assertEquals(1, traces.size()); mock.assertIsSatisfied(); Map headers = mock.getExchanges().get(0).getIn().getHeaders(); diff --git a/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/SpanPropagationUpstreamTest.java b/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/SpanPropagationUpstreamTest.java index 19af81965cc1e..00a7a8f6b6633 100644 --- a/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/SpanPropagationUpstreamTest.java +++ b/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/SpanPropagationUpstreamTest.java @@ -16,7 +16,6 @@ */ package org.apache.camel.telemetrydev; -import java.io.IOException; import java.util.List; import java.util.Map; @@ -41,10 +40,10 @@ protected CamelContext createCamelContext() throws Exception { } @Test - void testPropagateUpstreamTraceRequest() throws IOException { + void testPropagateUpstreamTraceRequest() { template.requestBodyAndHeader("direct:start", "sample body", "traceparent", "123456789-123456"); - Map traces = tracesFromLog(); + Map traces = awaitTracesFromLog(1); assertEquals(1, traces.size()); checkTrace(traces.values().iterator().next()); } diff --git a/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/TelemetryDevTracerTest.java b/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/TelemetryDevTracerTest.java index 6fea7695553d3..ab2c4c1f451c4 100644 --- a/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/TelemetryDevTracerTest.java +++ b/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/TelemetryDevTracerTest.java @@ -16,7 +16,6 @@ */ package org.apache.camel.telemetrydev; -import java.io.IOException; import java.util.List; import java.util.Map; @@ -45,21 +44,21 @@ protected CamelContext createCamelContext() throws Exception { } @Test - void testRouteSingleRequest() throws IOException { + void testRouteSingleRequest() { Exchange result = template.request("direct:start", null); // Make sure the trace is propagated downstream assertNotNull(result.getIn().getHeader("traceparent")); - Map traces = tracesFromLog(); + Map traces = awaitTracesFromLog(1); assertEquals(1, traces.size()); checkTrace(traces.values().iterator().next(), null); } @Test - void testRouteMultipleRequests() throws IOException { + void testRouteMultipleRequests() { for (int i = 1; i <= 10; i++) { context.createProducerTemplate().sendBody("direct:start", "Hello!"); } - Map traces = tracesFromLog(); + Map traces = awaitTracesFromLog(10); // Each trace should have a unique trace id. It is enough to assert that // the number of elements in the map is the same of the requests to prove // all traces have been generated uniquely. diff --git a/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/TelemetryDevTracerTestSupport.java b/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/TelemetryDevTracerTestSupport.java index 56f63d67d5c32..0f56277b4466b 100644 --- a/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/TelemetryDevTracerTestSupport.java +++ b/components/camel-telemetry-dev/src/test/java/org/apache/camel/telemetrydev/TelemetryDevTracerTestSupport.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.camel.telemetry.Op; @@ -32,14 +33,32 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.RollingFileAppender; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import static org.awaitility.Awaitility.await; public class TelemetryDevTracerTestSupport extends ExchangeTestSupport { private final ObjectMapper mapper = new ObjectMapper(); + /* + * Clear the log traces before each test to prevent flaky tests from leftover data + */ + @BeforeEach + public synchronized void clearLogTracesBeforeTest() throws IOException { + final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + RollingFileAppender appender = (RollingFileAppender) ctx.getConfiguration().getAppenders().get("file2"); + if (appender != null) { + appender.getManager().rollover(); + } + } + protected Map tracesFromLog() throws IOException { Map answer = new HashMap<>(); Path path = Paths.get("target/telemetry-traces.log"); + if (!Files.exists(path)) { + return answer; + } List allTraces = Files.readAllLines(path); for (String trace : allTraces) { DevTrace st = mapper.readValue(trace, DevTrace.class); @@ -57,6 +76,28 @@ protected Map tracesFromLog() throws IOException { return answer; } + /** + * Wait for the expected number of traces to be written to the log file. Uses Awaitility to avoid flaky tests from + * timing issues. + */ + protected Map awaitTracesFromLog(int expectedCount) { + await().atMost(10, TimeUnit.SECONDS) + .pollInterval(100, TimeUnit.MILLISECONDS) + .until(() -> { + try { + Map traces = tracesFromLog(); + return traces.size() >= expectedCount; + } catch (IOException e) { + return false; + } + }); + try { + return tracesFromLog(); + } catch (IOException e) { + throw new RuntimeException("Failed to read traces from log", e); + } + } + /* * This one is required to rollover the log traces database file and make sure each test has its own * set of fresh data. 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 7b019e38fd168..aa191ce02bcd9 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,12 @@ public interface SpanDecorator { SpanContextPropagationInjector getInjector(Exchange exchange); + /** + * This method returns the SpanKind for a given operation. + * + * @param op The operation type + * @return The span kind to use for this operation + */ + SpanKind getSpanKind(Op op); + } diff --git a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanKind.java b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanKind.java new file mode 100644 index 0000000000000..94a85fba13414 --- /dev/null +++ b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/SpanKind.java @@ -0,0 +1,39 @@ +/* + * 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.telemetry; + +/** + * Span kind constants for telemetry tracing. + *

+ * These values describe the relationship between the span and its parent: + *

    + *
  • CLIENT - The span covers a client-side call to a remote service
  • + *
  • SERVER - The span covers server-side handling of a remote request
  • + *
  • PRODUCER - The span covers the production of a message to a remote system (e.g., message broker, queue, HTTP + * endpoint)
  • + *
  • CONSUMER - The span covers the consumption of a message from a remote system (e.g., message broker, queue, HTTP + * endpoint)
  • + *
  • INTERNAL - The span represents internal operations with no remote interaction
  • + *
+ */ +public enum SpanKind { + CLIENT, + SERVER, + PRODUCER, + CONSUMER, + INTERNAL +} 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 501dd2430f20f..45fd2874ebbff 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 @@ */ public interface SpanLifecycleManager { - Span create(String spanName, Span parent, SpanContextPropagationExtractor extractor); + Span create(String spanName, SpanKind kind, 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 514fd20445655..7caef9ecb4fca 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,8 @@ protected void beginEventSpan(Exchange exchange, Endpoint endpoint, Op op) throw 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)); + SpanKind spanKind = spanDecorator.getSpanKind(op); + 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 +287,8 @@ protected void beginProcessorSpan(Exchange exchange, String processorName) throw // there is some inconsistency LOG.warn("Processor tracing parent should not be null!"); } - Span span = spanLifecycleManager.create(processorName, parentSpan, spanDecorator.getExtractor(exchange)); + SpanKind spanKind = spanDecorator.getSpanKind(Op.EVENT_PROCESS); + 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 e0ae5b91a3764..544184551b30c 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,7 +19,9 @@ 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.SpanKind; import org.apache.camel.telemetry.TagConstants; public abstract class AbstractHttpSpanDecorator extends AbstractSpanDecorator { @@ -105,4 +107,13 @@ public void afterTracingEvent(Span span, Exchange exchange) { } } } + + @Override + public SpanKind getSpanKind(Op op) { + return switch (op) { + case EVENT_SENT -> SpanKind.CLIENT; + case EVENT_RECEIVED -> SpanKind.SERVER; + case EVENT_PROCESS -> SpanKind.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 bd4852ffaea2e..c1f588ab17d97 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,7 +18,9 @@ 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.SpanKind; import org.apache.camel.telemetry.TagConstants; public abstract class AbstractMessagingSpanDecorator extends AbstractSpanDecorator { @@ -60,4 +62,13 @@ protected String getMessageId(Exchange exchange) { return null; } + @Override + public SpanKind getSpanKind(Op op) { + return switch (op) { + case EVENT_SENT -> SpanKind.PRODUCER; + case EVENT_RECEIVED -> SpanKind.CONSUMER; + case EVENT_PROCESS -> SpanKind.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 ee7e8002e2e2f..e6468f8fe0eb9 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 @@ -21,10 +21,12 @@ import org.apache.camel.Endpoint; import org.apache.camel.Exchange; import org.apache.camel.spi.EndpointServiceLocation; +import org.apache.camel.telemetry.Op; import org.apache.camel.telemetry.Span; import org.apache.camel.telemetry.SpanContextPropagationExtractor; import org.apache.camel.telemetry.SpanContextPropagationInjector; import org.apache.camel.telemetry.SpanDecorator; +import org.apache.camel.telemetry.SpanKind; import org.apache.camel.telemetry.TagConstants; import org.apache.camel.telemetry.propagation.CamelHeadersSpanContextPropagationExtractor; import org.apache.camel.telemetry.propagation.CamelHeadersSpanContextPropagationInjector; @@ -175,4 +177,10 @@ public SpanContextPropagationExtractor getExtractor(Exchange exchange) { public SpanContextPropagationInjector getInjector(Exchange exchange) { return new CamelHeadersSpanContextPropagationInjector(exchange.getIn().getHeaders()); } + + @Override + public SpanKind getSpanKind(Op op) { + // Default to INTERNAL for all operations + return SpanKind.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 48e0467b96127..fe7b39059ec3a 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 @@ -24,11 +24,7 @@ import org.apache.camel.api.management.ManagedResource; import org.apache.camel.spi.Configurer; import org.apache.camel.spi.annotations.JdkService; -import org.apache.camel.telemetry.Span; -import org.apache.camel.telemetry.SpanContextPropagationExtractor; -import org.apache.camel.telemetry.SpanContextPropagationInjector; -import org.apache.camel.telemetry.SpanLifecycleManager; -import org.apache.camel.telemetry.Tracer; +import org.apache.camel.telemetry.*; @JdkService("mock-tracer") @Configurer @@ -53,7 +49,9 @@ private class MockSpanLifecycleManager implements SpanLifecycleManager { Map inMemoryStorageMap = new HashMap<>(); @Override - public Span create(String spanName, Span parentSpan, SpanContextPropagationExtractor extractor) { + public Span create( + String spanName, SpanKind kind, Span parentSpan, + SpanContextPropagationExtractor extractor) { Span span = MockSpanAdapter.buildSpan(spanName); String traceId = UUID.randomUUID().toString().replaceAll("-", ""); if (parentSpan != null) { @@ -69,6 +67,7 @@ public Span create(String spanName, Span parentSpan, SpanContextPropagationExtra } span.setTag("traceid", traceId); span.setTag("spanid", UUID.randomUUID().toString().replaceAll("-", "")); + span.setTag("kind", kind.toString()); return span; } diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_20.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_20.adoc index f2c4391e8c888..dd16d6129eb96 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_20.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_20.adoc @@ -25,3 +25,85 @@ segments is no longer accepted and will result in an `InvalidTopicNameException` If your Camel routes use topic URIs with extra `/` characters in the topic name portion (e.g., `pulsar:persistent://public/default/my-topic/sub-path`), you must replace the extra `/` with another separator such as `-` (e.g., `pulsar:persistent://public/default/my-topic-sub-path`). + +=== camel-telemetry + +The telemetry API has been enhanced to support proper span type classification through the introduction +of the `SpanKind` enum. This affects custom implementations of telemetry tracers and span decorators. + +==== SpanKind API Changes + +The `SpanLifecycleManager.create()` method signature has been updated to accept a `SpanKind` parameter: + +Before: +[source,java] +---- +Span create(Exchange exchange, String spanName); +---- + +After: +[source,java] +---- +Span create(Exchange exchange, String spanName, SpanKind kind); +---- + +==== Impact on Custom Implementations + +If you have implemented custom tracer or span decorator classes, you need to update them: + +**Custom Tracer Implementations** + +If you implemented the `org.apache.camel.telemetry.Tracer` interface, update your `createSpan()` method: + +Before: +[source,java] +---- +@Override +public Span createSpan(Exchange exchange, String spanName) { + return spanLifecycleManager.create(exchange, spanName); +} +---- + +After: +[source,java] +---- +@Override +public Span createSpan(Exchange exchange, String spanName, SpanKind kind) { + return spanLifecycleManager.create(exchange, spanName, kind); +} +---- + +**Custom SpanDecorator Implementations** + +If you implemented custom `SpanDecorator` classes, you must now provide span kind information by implementing +the new `getSpanKind()` method: + +[source,java] +---- +@Override +public SpanKind getSpanKind() { + // Return appropriate span kind based on your component's behavior: + // - CLIENT: for outbound calls to remote services + // - SERVER: for handling incoming requests + // - PRODUCER: for sending messages/data to remote systems + // - CONSUMER: for receiving messages/data from remote systems + // - INTERNAL: for internal operations with no remote interaction + return SpanKind.PRODUCER; +} +---- + +==== MockTracer Test Utilities + +If you use `MockTracer` in tests, update calls to `createSpan()`: + +Before: +[source,java] +---- +mockTracer.createSpan(exchange, "test-span"); +---- + +After: +[source,java] +---- +mockTracer.createSpan(exchange, "test-span", SpanKind.INTERNAL); +----