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 d02d1120f94b CAMEL-23709: Reduce span verbosity for endpoint-sending 
processors (#23843)
d02d1120f94b is described below

commit d02d1120f94bc27a7682a9850ee45abcec1830c1
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Jun 8 16:07:42 2026 +0200

    CAMEL-23709: Reduce span verbosity for endpoint-sending processors (#23843)
    
    * CAMEL-23709: Reduce span verbosity for endpoint-sending processors
    
    Introduce EndpointSending marker interface in camel-api. Processors
    that send to endpoints (to, toD, wireTap, enrich) implement it to
    signal they produce their own EVENT_SENT span via ExchangeSendingEvent.
    The telemetry intercept strategy skips creating redundant EVENT_PROCESS
    processor spans for these processors, reducing span tree depth.
    
    Co-Authored-By: Claude <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    * CAMEL-23709: Add upgrade guide note for reduced span verbosity
    
    Co-Authored-By: Claude <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    ---------
    
    Signed-off-by: Claus Ibsen <[email protected]>
    Co-authored-by: Claude <[email protected]>
---
 .../camel/opentelemetry2/BaggageInjectionTest.java |  5 +--
 .../camel/opentelemetry2/BaggageSettingTest.java   |  5 +--
 .../camel/opentelemetry2/EnableProcessorsTest.java |  7 +--
 .../apache/camel/opentelemetry2/SpanBeanTest.java  |  9 ++--
 .../opentelemetry2/SpanCustomizationTest.java      |  7 +--
 .../camel/opentelemetry2/SpanInjectionTest.java    |  7 +--
 .../camel/opentelemetry2/SpanToBeanTest.java       | 23 ++++------
 .../TraceProcessorsInterceptStrategy.java          | 52 +++++++++++++++-------
 .../camel/telemetry/EnableCoreProcessorsTest.java  |  9 +---
 .../org/apache/camel/telemetry/SpanBeanTest.java   | 15 +++----
 .../org/apache/camel/telemetry/SpanToBeanTest.java | 27 ++++-------
 .../java/org/apache/camel/EndpointSending.java     | 27 +++++++++++
 .../java/org/apache/camel/processor/Enricher.java  |  4 +-
 .../camel/processor/SendDynamicProcessor.java      |  3 +-
 .../org/apache/camel/processor/SendProcessor.java  |  3 +-
 .../apache/camel/processor/WireTapProcessor.java   |  3 +-
 .../ROOT/pages/camel-4x-upgrade-guide-4_21.adoc    | 19 ++++++++
 17 files changed, 128 insertions(+), 97 deletions(-)

diff --git 
a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/BaggageInjectionTest.java
 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/BaggageInjectionTest.java
index 23a20c2f12a7..d7be5cba8de1 100644
--- 
a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/BaggageInjectionTest.java
+++ 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/BaggageInjectionTest.java
@@ -74,7 +74,8 @@ public class BaggageInjectionTest extends 
OpenTelemetryTracerTestSupport {
 
     private void checkTrace(OtelTrace trace) {
         List<SpanData> spans = trace.getSpans();
-        assertEquals(8, spans.size());
+        // to("log:info") no longer produces a processor span (SendProcessor 
implements EndpointSending)
+        assertEquals(7, spans.size());
         SpanData testProducer = spans.get(0);
         SpanData direct = spans.get(1);
         SpanData innerProcessor1 = spans.get(2);
@@ -82,7 +83,6 @@ public class BaggageInjectionTest extends 
OpenTelemetryTracerTestSupport {
         SpanData innerLog = spans.get(4);
         SpanData innerProcessor2 = spans.get(5);
         SpanData log = spans.get(6);
-        SpanData innerToLog = spans.get(7);
 
         // Validate span completion
         assertTrue(testProducer.hasEnded());
@@ -92,7 +92,6 @@ public class BaggageInjectionTest extends 
OpenTelemetryTracerTestSupport {
         assertTrue(innerLog.hasEnded());
         assertTrue(innerProcessor2.hasEnded());
         assertTrue(log.hasEnded());
-        assertTrue(innerToLog.hasEnded());
     }
 
     @Override
diff --git 
a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/BaggageSettingTest.java
 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/BaggageSettingTest.java
index f953f0098564..e161a6dc3630 100644
--- 
a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/BaggageSettingTest.java
+++ 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/BaggageSettingTest.java
@@ -61,14 +61,14 @@ public class BaggageSettingTest extends 
OpenTelemetryTracerTestSupport {
 
     private void checkTrace(OtelTrace trace) {
         List<SpanData> spans = trace.getSpans();
-        assertEquals(7, spans.size());
+        // to("log:info") no longer produces a processor span (SendProcessor 
implements EndpointSending)
+        assertEquals(6, spans.size());
         SpanData testProducer = spans.get(0);
         SpanData direct = spans.get(1);
         SpanData setHeaders = spans.get(2);
         SpanData innerLog = spans.get(3);
         SpanData innerProcessor = spans.get(4);
         SpanData log = spans.get(5);
-        SpanData innerToLog = spans.get(6);
 
         // Validate span completion
         assertTrue(testProducer.hasEnded());
@@ -77,7 +77,6 @@ public class BaggageSettingTest extends 
OpenTelemetryTracerTestSupport {
         assertTrue(innerLog.hasEnded());
         assertTrue(innerProcessor.hasEnded());
         assertTrue(log.hasEnded());
-        assertTrue(innerToLog.hasEnded());
     }
 
     @Override
diff --git 
a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/EnableProcessorsTest.java
 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/EnableProcessorsTest.java
index 76f4abe6868f..e1470e581b5d 100644
--- 
a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/EnableProcessorsTest.java
+++ 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/EnableProcessorsTest.java
@@ -60,13 +60,13 @@ public class EnableProcessorsTest extends 
OpenTelemetryTracerTestSupport {
 
     private void checkTrace(OtelTrace trace) {
         List<SpanData> spans = trace.getSpans();
-        assertEquals(6, spans.size());
+        // to("log:info") no longer produces a processor span (SendProcessor 
implements EndpointSending)
+        assertEquals(5, spans.size());
         SpanData testProducer = spans.get(0);
         SpanData direct = spans.get(1);
         SpanData innerLog = spans.get(2);
         SpanData innerProcessor = spans.get(3);
         SpanData log = spans.get(4);
-        SpanData innerToLog = spans.get(5);
 
         // Validate span completion
         assertTrue(testProducer.hasEnded());
@@ -74,14 +74,12 @@ public class EnableProcessorsTest extends 
OpenTelemetryTracerTestSupport {
         assertTrue(innerLog.hasEnded());
         assertTrue(innerProcessor.hasEnded());
         assertTrue(log.hasEnded());
-        assertTrue(innerToLog.hasEnded());
 
         // Validate same trace
         assertEquals(testProducer.getSpanContext().getTraceId(), 
direct.getSpanContext().getTraceId());
         assertEquals(testProducer.getSpanContext().getTraceId(), 
innerLog.getSpanContext().getTraceId());
         assertEquals(testProducer.getSpanContext().getTraceId(), 
innerProcessor.getSpanContext().getTraceId());
         assertEquals(testProducer.getSpanContext().getTraceId(), 
log.getSpanContext().getTraceId());
-        assertEquals(testProducer.getSpanContext().getTraceId(), 
innerToLog.getSpanContext().getTraceId());
 
         // Validate operations
         assertEquals(Op.EVENT_RECEIVED.toString(), 
direct.getAttributes().get(AttributeKey.stringKey("op")));
@@ -93,7 +91,6 @@ public class EnableProcessorsTest extends 
OpenTelemetryTracerTestSupport {
         assertEquals(direct.getSpanContext().getSpanId(), 
innerLog.getParentSpanContext().getSpanId());
         assertEquals(direct.getSpanContext().getSpanId(), 
innerProcessor.getParentSpanContext().getSpanId());
         assertEquals(direct.getSpanContext().getSpanId(), 
log.getParentSpanContext().getSpanId());
-        assertEquals(log.getSpanContext().getSpanId(), 
innerToLog.getParentSpanContext().getSpanId());
     }
 
     @Override
diff --git 
a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanBeanTest.java
 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanBeanTest.java
index a785de244f28..5952d2aca42c 100644
--- 
a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanBeanTest.java
+++ 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanBeanTest.java
@@ -63,14 +63,14 @@ public class SpanBeanTest extends 
OpenTelemetryTracerTestSupport {
 
     private void checkTrace(OtelTrace trace) {
         List<SpanData> spans = trace.getSpans();
-        assertEquals(7, spans.size());
+        // to("log:info") no longer produces a processor span (SendProcessor 
implements EndpointSending)
+        assertEquals(6, spans.size());
         SpanData testProducer = spans.get(0);
         SpanData direct = spans.get(1);
         SpanData innerLog = spans.get(2);
         SpanData beanProcessor = spans.get(3);
         SpanData beanMethod = spans.get(4);
         SpanData log = spans.get(5);
-        SpanData innerToLog = spans.get(6);
 
         // Validate span completion
         assertTrue(testProducer.hasEnded());
@@ -79,7 +79,6 @@ public class SpanBeanTest extends 
OpenTelemetryTracerTestSupport {
         assertTrue(beanProcessor.hasEnded());
         assertTrue(beanMethod.hasEnded());
         assertTrue(log.hasEnded());
-        assertTrue(innerToLog.hasEnded());
 
         // Validate same trace
         assertEquals(testProducer.getSpanContext().getTraceId(), 
direct.getSpanContext().getTraceId());
@@ -87,7 +86,6 @@ public class SpanBeanTest extends 
OpenTelemetryTracerTestSupport {
         assertEquals(testProducer.getSpanContext().getTraceId(), 
beanProcessor.getSpanContext().getTraceId());
         assertEquals(testProducer.getSpanContext().getTraceId(), 
beanMethod.getSpanContext().getTraceId());
         assertEquals(testProducer.getSpanContext().getTraceId(), 
log.getSpanContext().getTraceId());
-        assertEquals(testProducer.getSpanContext().getTraceId(), 
innerToLog.getSpanContext().getTraceId());
 
         // Validate operations
         assertEquals(Op.EVENT_RECEIVED.toString(), 
direct.getAttributes().get(AttributeKey.stringKey("op")));
@@ -101,13 +99,12 @@ public class SpanBeanTest extends 
OpenTelemetryTracerTestSupport {
         assertEquals(direct.getSpanContext().getSpanId(), 
beanProcessor.getParentSpanContext().getSpanId());
         assertEquals(beanProcessor.getSpanContext().getSpanId(), 
beanMethod.getParentSpanContext().getSpanId());
         assertEquals(direct.getSpanContext().getSpanId(), 
log.getParentSpanContext().getSpanId());
-        assertEquals(log.getSpanContext().getSpanId(), 
innerToLog.getParentSpanContext().getSpanId());
 
         // Validate message logging
         assertEquals("A message", 
innerLog.getEvents().get(0).getAttributes().get(AttributeKey.stringKey("message")));
         assertEquals(
                 "Exchange[ExchangePattern: InOnly, BodyType: String, Body: 
my-body]",
-                
innerToLog.getEvents().get(0).getAttributes().get(AttributeKey.stringKey("message")));
+                
log.getEvents().get(0).getAttributes().get(AttributeKey.stringKey("message")));
     }
 
     @Override
diff --git 
a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanCustomizationTest.java
 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanCustomizationTest.java
index 2b1d6c4d91d3..cc4d61938125 100644
--- 
a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanCustomizationTest.java
+++ 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanCustomizationTest.java
@@ -64,14 +64,14 @@ public class SpanCustomizationTest extends 
OpenTelemetryTracerTestSupport {
 
     private void checkTrace(OtelTrace trace) {
         List<SpanData> spans = trace.getSpans();
-        assertEquals(7, spans.size());
+        // to("log:info") no longer produces a processor span (SendProcessor 
implements EndpointSending)
+        assertEquals(6, spans.size());
         SpanData testProducer = spans.get(0);
         SpanData direct = spans.get(1);
         SpanData innerLog = spans.get(2);
         SpanData innerProcessor = spans.get(3);
         SpanData customSpan = spans.get(4);
         SpanData log = spans.get(5);
-        SpanData innerToLog = spans.get(6);
 
         // Validate span completion
         assertTrue(testProducer.hasEnded());
@@ -80,16 +80,13 @@ public class SpanCustomizationTest extends 
OpenTelemetryTracerTestSupport {
         assertTrue(innerProcessor.hasEnded());
         assertTrue(customSpan.hasEnded());
         assertTrue(log.hasEnded());
-        assertTrue(innerToLog.hasEnded());
 
         // Validate same trace
         assertEquals(testProducer.getSpanContext().getTraceId(), 
direct.getSpanContext().getTraceId());
-        assertEquals(testProducer.getSpanContext().getTraceId(), 
direct.getSpanContext().getTraceId());
         assertEquals(testProducer.getSpanContext().getTraceId(), 
innerLog.getSpanContext().getTraceId());
         assertEquals(testProducer.getSpanContext().getTraceId(), 
innerProcessor.getSpanContext().getTraceId());
         assertEquals(testProducer.getSpanContext().getTraceId(), 
customSpan.getSpanContext().getTraceId());
         assertEquals(testProducer.getSpanContext().getTraceId(), 
log.getSpanContext().getTraceId());
-        assertEquals(testProducer.getSpanContext().getTraceId(), 
innerToLog.getSpanContext().getTraceId());
 
         // Validate operations
         assertEquals(Op.EVENT_RECEIVED.toString(), 
direct.getAttributes().get(AttributeKey.stringKey("op")));
diff --git 
a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanInjectionTest.java
 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanInjectionTest.java
index fb72cd03684d..80182ad54569 100644
--- 
a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanInjectionTest.java
+++ 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanInjectionTest.java
@@ -102,13 +102,13 @@ public class SpanInjectionTest extends 
OpenTelemetryTracerTestSupport {
 
     private void checkTrace(OtelTrace trace, String parentTrace, String 
parentSpan) {
         List<SpanData> spans = trace.getSpans();
-        assertEquals(6, spans.size());
+        // to("log:info") no longer produces a processor span (SendProcessor 
implements EndpointSending)
+        assertEquals(5, spans.size());
         SpanData testProducer = spans.get(0);
         SpanData direct = spans.get(1);
         SpanData innerLog = spans.get(2);
         SpanData innerProcessor = spans.get(3);
         SpanData log = spans.get(4);
-        SpanData innerToLog = spans.get(5);
 
         // Validate span completion
         assertTrue(testProducer.hasEnded());
@@ -116,7 +116,6 @@ public class SpanInjectionTest extends 
OpenTelemetryTracerTestSupport {
         assertTrue(innerLog.hasEnded());
         assertTrue(innerProcessor.hasEnded());
         assertTrue(log.hasEnded());
-        assertTrue(innerToLog.hasEnded());
 
         // Validate same trace
         assertEquals(parentTrace, testProducer.getSpanContext().getTraceId());
@@ -124,7 +123,6 @@ public class SpanInjectionTest extends 
OpenTelemetryTracerTestSupport {
         assertEquals(testProducer.getSpanContext().getTraceId(), 
innerLog.getSpanContext().getTraceId());
         assertEquals(testProducer.getSpanContext().getTraceId(), 
innerProcessor.getSpanContext().getTraceId());
         assertEquals(testProducer.getSpanContext().getTraceId(), 
log.getSpanContext().getTraceId());
-        assertEquals(testProducer.getSpanContext().getTraceId(), 
innerToLog.getSpanContext().getTraceId());
 
         // Validate operations
         assertEquals(Op.EVENT_RECEIVED.toString(), 
direct.getAttributes().get(AttributeKey.stringKey("op")));
@@ -139,7 +137,6 @@ public class SpanInjectionTest extends 
OpenTelemetryTracerTestSupport {
         assertEquals(direct.getSpanContext().getSpanId(), 
innerLog.getParentSpanContext().getSpanId());
         assertEquals(direct.getSpanContext().getSpanId(), 
innerProcessor.getParentSpanContext().getSpanId());
         assertEquals(direct.getSpanContext().getSpanId(), 
log.getParentSpanContext().getSpanId());
-        assertEquals(log.getSpanContext().getSpanId(), 
innerToLog.getParentSpanContext().getSpanId());
     }
 
     @Override
diff --git 
a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanToBeanTest.java
 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanToBeanTest.java
index 64f0eb2d8c1c..d138c89fd8c9 100644
--- 
a/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanToBeanTest.java
+++ 
b/components/camel-opentelemetry2/src/test/java/org/apache/camel/opentelemetry2/SpanToBeanTest.java
@@ -61,46 +61,39 @@ public class SpanToBeanTest extends 
OpenTelemetryTracerTestSupport {
 
     private void checkTrace(OtelTrace trace) {
         List<SpanData> spans = trace.getSpans();
-        assertEquals(8, spans.size());
+        // to("bean:myBean") and to("log:info") no longer produce processor 
spans (SendProcessor implements EndpointSending)
+        assertEquals(6, spans.size());
         SpanData testProducer = spans.get(0);
         SpanData direct = spans.get(1);
         SpanData innerLog = spans.get(2);
-        SpanData beanTo = spans.get(3);
-        SpanData beanProcessor = spans.get(4);
-        SpanData beanMethod = spans.get(5);
-        SpanData log = spans.get(6);
-        SpanData innerToLog = spans.get(7);
+        SpanData beanProcessor = spans.get(3);
+        SpanData beanMethod = spans.get(4);
+        SpanData log = spans.get(5);
 
         // Validate span completion
         assertTrue(testProducer.hasEnded());
         assertTrue(direct.hasEnded());
         assertTrue(innerLog.hasEnded());
-        assertTrue(beanTo.hasEnded());
         assertTrue(beanProcessor.hasEnded());
         assertTrue(beanMethod.hasEnded());
         assertTrue(log.hasEnded());
-        assertTrue(innerToLog.hasEnded());
 
         // Validate same trace
         assertEquals(testProducer.getSpanContext().getTraceId(), 
direct.getSpanContext().getTraceId());
         assertEquals(testProducer.getSpanContext().getTraceId(), 
innerLog.getSpanContext().getTraceId());
-        assertEquals(testProducer.getSpanContext().getTraceId(), 
beanTo.getSpanContext().getTraceId());
         assertEquals(testProducer.getSpanContext().getTraceId(), 
beanProcessor.getSpanContext().getTraceId());
         assertEquals(testProducer.getSpanContext().getTraceId(), 
beanMethod.getSpanContext().getTraceId());
         assertEquals(testProducer.getSpanContext().getTraceId(), 
log.getSpanContext().getTraceId());
-        assertEquals(testProducer.getSpanContext().getTraceId(), 
innerToLog.getSpanContext().getTraceId());
 
         // Validate hierarchy
         assertFalse(testProducer.getParentSpanContext().isValid());
 
         assertEquals(testProducer.getSpanContext().getSpanId(), 
direct.getParentSpanContext().getSpanId());
         assertEquals(direct.getSpanContext().getSpanId(), 
innerLog.getParentSpanContext().getSpanId());
-        assertEquals(direct.getSpanContext().getSpanId(), 
beanTo.getParentSpanContext().getSpanId());
-        assertEquals(beanTo.getSpanContext().getSpanId(), 
beanProcessor.getParentSpanContext().getSpanId());
-        // NOTE: the bean method belongs to the component ("to") node.
-        assertEquals(beanTo.getSpanContext().getSpanId(), 
beanMethod.getParentSpanContext().getSpanId());
+        assertEquals(direct.getSpanContext().getSpanId(), 
beanProcessor.getParentSpanContext().getSpanId());
+        // beanMethod's parent is direct (OTel context set by scope wrapper, 
not the event span)
+        assertEquals(direct.getSpanContext().getSpanId(), 
beanMethod.getParentSpanContext().getSpanId());
         assertEquals(direct.getSpanContext().getSpanId(), 
log.getParentSpanContext().getSpanId());
-        assertEquals(log.getSpanContext().getSpanId(), 
innerToLog.getParentSpanContext().getSpanId());
     }
 
     @Override
diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/TraceProcessorsInterceptStrategy.java
 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/TraceProcessorsInterceptStrategy.java
index 5369cba95192..2185881e7077 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/TraceProcessorsInterceptStrategy.java
+++ 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/TraceProcessorsInterceptStrategy.java
@@ -20,6 +20,8 @@ import java.util.concurrent.CompletableFuture;
 
 import org.apache.camel.AsyncCallback;
 import org.apache.camel.CamelContext;
+import org.apache.camel.DelegateProcessor;
+import org.apache.camel.EndpointSending;
 import org.apache.camel.Exchange;
 import org.apache.camel.NamedNode;
 import org.apache.camel.Processor;
@@ -57,9 +59,7 @@ public class TraceProcessorsInterceptStrategy implements 
InterceptStrategy {
         @Override
         public void process(Exchange exchange) throws Exception {
             String processorName = processorDefinition.getId() + "-" + 
processorDefinition.getShortName();
-            if ((isCoreProcessEnabled(tracer.isDisableCoreProcessors(), 
processorDefinition.getShortName()) ||
-                    isCustomProcessEnabled(tracer.isTraceProcessors(), 
processorDefinition.getShortName()))
-                    && tracer.match(processorName, exchange.getContext())) {
+            if (shouldTrace(processorName, exchange)) {
                 tracer.beginProcessorSpan(exchange, processorName);
                 try {
                     processor.process(exchange);
@@ -67,7 +67,6 @@ public class TraceProcessorsInterceptStrategy implements 
InterceptStrategy {
                     tracer.endProcessorSpan(exchange, processorName);
                 }
             } else {
-                // We must always execute this
                 processor.process(exchange);
             }
         }
@@ -75,11 +74,8 @@ public class TraceProcessorsInterceptStrategy implements 
InterceptStrategy {
         @Override
         public boolean process(Exchange exchange, AsyncCallback callback) {
             String processorName = processorDefinition.getId() + "-" + 
processorDefinition.getShortName();
-            boolean isTraceProcessor
-                    = (isCoreProcessEnabled(tracer.isDisableCoreProcessors(), 
processorDefinition.getShortName()) ||
-                            isCustomProcessEnabled(tracer.isTraceProcessors(), 
processorDefinition.getShortName()))
-                            && tracer.match(processorName, 
exchange.getContext());
-            if (isTraceProcessor) {
+            boolean trace = shouldTrace(processorName, exchange);
+            if (trace) {
                 try {
                     tracer.beginProcessorSpan(exchange, processorName);
                 } catch (Exception e) {
@@ -90,7 +86,7 @@ public class TraceProcessorsInterceptStrategy implements 
InterceptStrategy {
                 try {
                     callback.done(doneSync);
                 } finally {
-                    if (isTraceProcessor) {
+                    if (trace) {
                         try {
                             tracer.endProcessorSpan(exchange, processorName);
                         } catch (Exception e) {
@@ -108,15 +104,39 @@ public class TraceProcessorsInterceptStrategy implements 
InterceptStrategy {
             process(exchange, callback);
             return callback.getFuture();
         }
+
+        private boolean shouldTrace(String processorName, Exchange exchange) {
+            // skip processors that send to an endpoint (to, toD, wireTap, 
enrich)
+            // unwrap through any delegate chain (e.g. otel scope wrapper) to 
find the real processor
+            if (isEndpointSending(processor)) {
+                return false;
+            }
+            String shortName = processorDefinition.getShortName();
+            boolean enabled = isCoreProcessEnabled(shortName) || 
isCustomProcessEnabled(shortName);
+            return enabled && tracer.match(processorName, 
exchange.getContext());
+        }
+
+        private boolean isEndpointSending(Processor target) {
+            Processor p = target;
+            while (p != null) {
+                if (p instanceof EndpointSending) {
+                    return true;
+                }
+                if (p instanceof DelegateProcessor dp) {
+                    p = dp.getProcessor();
+                } else {
+                    break;
+                }
+            }
+            return false;
+        }
     }
 
-    private boolean isCoreProcessEnabled(boolean isDisableCoreProcessors, 
String processDefinitionShortName) {
-        // Any enabled core process which is not a "custom" processor.
-        return !isDisableCoreProcessors && 
!processDefinitionShortName.equals("process");
+    private boolean isCoreProcessEnabled(String shortName) {
+        return !tracer.isDisableCoreProcessors() && 
!"process".equals(shortName);
     }
 
-    private boolean isCustomProcessEnabled(boolean isTraceProcessors, String 
processDefinitionShortName) {
-        // Any custom core process.
-        return isTraceProcessors && 
processDefinitionShortName.equals("process");
+    private boolean isCustomProcessEnabled(String shortName) {
+        return tracer.isTraceProcessors() && "process".equals(shortName);
     }
 }
diff --git 
a/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/EnableCoreProcessorsTest.java
 
b/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/EnableCoreProcessorsTest.java
index 62c50a27899d..04b873507a30 100644
--- 
a/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/EnableCoreProcessorsTest.java
+++ 
b/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/EnableCoreProcessorsTest.java
@@ -57,27 +57,23 @@ public class EnableCoreProcessorsTest extends 
ExchangeTestSupport {
 
     private void checkTrace(MockTrace trace) {
         List<Span> spans = trace.spans();
-        assertEquals(5, spans.size());
-        // Cast to implementation object to be able to
-        // inspect the status of the Span.
+        // to("log:info") no longer produces a processor span (SendProcessor 
implements EndpointSending)
+        assertEquals(4, spans.size());
         MockSpanAdapter testProducer = (MockSpanAdapter) spans.get(0);
         MockSpanAdapter direct = (MockSpanAdapter) spans.get(1);
         MockSpanAdapter innerLog = (MockSpanAdapter) spans.get(2);
         MockSpanAdapter log = (MockSpanAdapter) spans.get(3);
-        MockSpanAdapter innerToLog = (MockSpanAdapter) spans.get(4);
 
         // Validate span completion
         assertEquals("true", testProducer.getTag("isDone"));
         assertEquals("true", direct.getTag("isDone"));
         assertEquals("true", innerLog.getTag("isDone"));
         assertEquals("true", log.getTag("isDone"));
-        assertEquals("true", innerToLog.getTag("isDone"));
 
         // Validate same trace
         assertEquals(testProducer.getTag("traceid"), direct.getTag("traceid"));
         assertEquals(testProducer.getTag("traceid"), 
innerLog.getTag("traceid"));
         assertEquals(testProducer.getTag("traceid"), log.getTag("traceid"));
-        assertEquals(testProducer.getTag("traceid"), 
innerToLog.getTag("traceid"));
 
         // Validate op
         assertEquals(Op.EVENT_RECEIVED.toString(), direct.getTag("op"));
@@ -87,7 +83,6 @@ public class EnableCoreProcessorsTest extends 
ExchangeTestSupport {
         assertEquals(testProducer.getTag("spanid"), 
direct.getTag("parentSpan"));
         assertEquals(direct.getTag("spanid"), innerLog.getTag("parentSpan"));
         assertEquals(direct.getTag("spanid"), log.getTag("parentSpan"));
-        assertEquals(log.getTag("spanid"), innerToLog.getTag("parentSpan"));
     }
 
     @Override
diff --git 
a/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/SpanBeanTest.java
 
b/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/SpanBeanTest.java
index ed2cba75226d..0adafe69ce01 100644
--- 
a/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/SpanBeanTest.java
+++ 
b/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/SpanBeanTest.java
@@ -57,16 +57,14 @@ public class SpanBeanTest extends ExchangeTestSupport {
 
     private void checkTrace(MockTrace trace) {
         List<Span> spans = trace.spans();
-        assertEquals(7, spans.size());
-        // Cast to implementation object to be able to
-        // inspect the status of the Span.
+        // to("log:info") no longer produces a processor span (SendProcessor 
implements EndpointSending)
+        assertEquals(6, spans.size());
         MockSpanAdapter testProducer = (MockSpanAdapter) spans.get(0);
         MockSpanAdapter direct = (MockSpanAdapter) spans.get(1);
         MockSpanAdapter innerLog = (MockSpanAdapter) spans.get(2);
         MockSpanAdapter bean = (MockSpanAdapter) spans.get(3);
         MockSpanAdapter beanMethod = (MockSpanAdapter) spans.get(4);
         MockSpanAdapter log = (MockSpanAdapter) spans.get(5);
-        MockSpanAdapter innerToLog = (MockSpanAdapter) spans.get(6);
 
         // Validate span completion
         assertEquals("true", testProducer.getTag("isDone"));
@@ -75,15 +73,13 @@ public class SpanBeanTest extends ExchangeTestSupport {
         assertEquals("true", bean.getTag("isDone"));
         assertEquals("true", beanMethod.getTag("isDone"));
         assertEquals("true", log.getTag("isDone"));
-        assertEquals("true", innerToLog.getTag("isDone"));
 
         // Validate same trace
         assertEquals(testProducer.getTag("traceid"), direct.getTag("traceid"));
         assertEquals(testProducer.getTag("traceid"), 
innerLog.getTag("traceid"));
-        assertEquals(testProducer.getTag("traceid"), log.getTag("traceid"));
         assertEquals(testProducer.getTag("traceid"), bean.getTag("traceid"));
         assertEquals(testProducer.getTag("traceid"), 
beanMethod.getTag("traceid"));
-        assertEquals(testProducer.getTag("traceid"), 
innerToLog.getTag("traceid"));
+        assertEquals(testProducer.getTag("traceid"), log.getTag("traceid"));
 
         // Validate op
         assertEquals(Op.EVENT_RECEIVED.toString(), direct.getTag("op"));
@@ -92,10 +88,9 @@ public class SpanBeanTest extends ExchangeTestSupport {
         assertNull(testProducer.getTag("parentSpan"));
         assertEquals(testProducer.getTag("spanid"), 
direct.getTag("parentSpan"));
         assertEquals(direct.getTag("spanid"), innerLog.getTag("parentSpan"));
-        assertEquals(direct.getTag("spanid"), log.getTag("parentSpan"));
         assertEquals(direct.getTag("spanid"), bean.getTag("parentSpan"));
         assertEquals(bean.getTag("spanid"), beanMethod.getTag("parentSpan"));
-        assertEquals(log.getTag("spanid"), innerToLog.getTag("parentSpan"));
+        assertEquals(direct.getTag("spanid"), log.getTag("parentSpan"));
 
         // Validate operations
         assertEquals(Op.EVENT_SENT.toString(), testProducer.getTag("op"));
@@ -105,7 +100,7 @@ public class SpanBeanTest extends ExchangeTestSupport {
         assertEquals("A message", 
innerLog.logEntries().get(0).fields().get("message"));
         assertEquals(
                 "Exchange[ExchangePattern: InOnly, BodyType: String, Body: 
my-body]",
-                innerToLog.logEntries().get(0).fields().get("message"));
+                log.logEntries().get(0).fields().get("message"));
     }
 
     @Override
diff --git 
a/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/SpanToBeanTest.java
 
b/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/SpanToBeanTest.java
index 0e4433ea4654..8debbdfb4632 100644
--- 
a/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/SpanToBeanTest.java
+++ 
b/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/SpanToBeanTest.java
@@ -57,36 +57,29 @@ public class SpanToBeanTest extends ExchangeTestSupport {
 
     private void checkTrace(MockTrace trace) {
         List<Span> spans = trace.spans();
-        assertEquals(8, spans.size());
-        // Cast to implementation object to be able to
-        // inspect the status of the Span.
+        // to("bean:myBean") and to("log:info") no longer produce processor 
spans (SendProcessor implements EndpointSending)
+        assertEquals(6, spans.size());
         MockSpanAdapter testProducer = (MockSpanAdapter) spans.get(0);
         MockSpanAdapter direct = (MockSpanAdapter) spans.get(1);
         MockSpanAdapter innerLog = (MockSpanAdapter) spans.get(2);
-        MockSpanAdapter toBean = (MockSpanAdapter) spans.get(3);
-        MockSpanAdapter bean = (MockSpanAdapter) spans.get(4);
-        MockSpanAdapter beanMethod = (MockSpanAdapter) spans.get(5);
-        MockSpanAdapter log = (MockSpanAdapter) spans.get(6);
-        MockSpanAdapter innerToLog = (MockSpanAdapter) spans.get(7);
+        MockSpanAdapter bean = (MockSpanAdapter) spans.get(3);
+        MockSpanAdapter beanMethod = (MockSpanAdapter) spans.get(4);
+        MockSpanAdapter log = (MockSpanAdapter) spans.get(5);
 
         // Validate span completion
         assertEquals("true", testProducer.getTag("isDone"));
         assertEquals("true", direct.getTag("isDone"));
         assertEquals("true", innerLog.getTag("isDone"));
-        assertEquals("true", toBean.getTag("isDone"));
         assertEquals("true", bean.getTag("isDone"));
         assertEquals("true", beanMethod.getTag("isDone"));
         assertEquals("true", log.getTag("isDone"));
-        assertEquals("true", innerToLog.getTag("isDone"));
 
         // Validate same trace
         assertEquals(testProducer.getTag("traceid"), direct.getTag("traceid"));
         assertEquals(testProducer.getTag("traceid"), 
innerLog.getTag("traceid"));
-        assertEquals(testProducer.getTag("traceid"), log.getTag("traceid"));
-        assertEquals(testProducer.getTag("traceid"), toBean.getTag("traceid"));
         assertEquals(testProducer.getTag("traceid"), bean.getTag("traceid"));
         assertEquals(testProducer.getTag("traceid"), 
beanMethod.getTag("traceid"));
-        assertEquals(testProducer.getTag("traceid"), 
innerToLog.getTag("traceid"));
+        assertEquals(testProducer.getTag("traceid"), log.getTag("traceid"));
 
         // Validate op
         assertEquals(Op.EVENT_RECEIVED.toString(), direct.getTag("op"));
@@ -95,11 +88,9 @@ public class SpanToBeanTest extends ExchangeTestSupport {
         assertNull(testProducer.getTag("parentSpan"));
         assertEquals(testProducer.getTag("spanid"), 
direct.getTag("parentSpan"));
         assertEquals(direct.getTag("spanid"), innerLog.getTag("parentSpan"));
-        assertEquals(direct.getTag("spanid"), log.getTag("parentSpan"));
-        assertEquals(direct.getTag("spanid"), toBean.getTag("parentSpan"));
-        assertEquals(toBean.getTag("spanid"), bean.getTag("parentSpan"));
+        assertEquals(direct.getTag("spanid"), bean.getTag("parentSpan"));
         assertEquals(bean.getTag("spanid"), beanMethod.getTag("parentSpan"));
-        assertEquals(log.getTag("spanid"), innerToLog.getTag("parentSpan"));
+        assertEquals(direct.getTag("spanid"), log.getTag("parentSpan"));
 
         // Validate operations
         assertEquals(Op.EVENT_SENT.toString(), testProducer.getTag("op"));
@@ -109,7 +100,7 @@ public class SpanToBeanTest extends ExchangeTestSupport {
         assertEquals("A message", 
innerLog.logEntries().get(0).fields().get("message"));
         assertEquals(
                 "Exchange[ExchangePattern: InOnly, BodyType: String, Body: 
my-body]",
-                innerToLog.logEntries().get(0).fields().get("message"));
+                log.logEntries().get(0).fields().get("message"));
     }
 
     @Override
diff --git a/core/camel-api/src/main/java/org/apache/camel/EndpointSending.java 
b/core/camel-api/src/main/java/org/apache/camel/EndpointSending.java
new file mode 100644
index 000000000000..e56002ed660e
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/EndpointSending.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * Marker interface for {@link Processor} implementations that send an {@link 
org.apache.camel.Exchange} to an
+ * {@link Endpoint} as part of their processing. This applies to both static 
endpoints (resolved at route build time)
+ * and dynamic endpoints (computed at runtime from an expression).
+ *
+ * @since 4.21
+ */
+public interface EndpointSending {
+}
diff --git 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/Enricher.java
 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/Enricher.java
index 54dd1e7ca154..fd8a0053061f 100644
--- 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/Enricher.java
+++ 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/Enricher.java
@@ -23,6 +23,7 @@ import org.apache.camel.AsyncCallback;
 import org.apache.camel.CamelContext;
 import org.apache.camel.CamelContextAware;
 import org.apache.camel.CamelExchangeException;
+import org.apache.camel.EndpointSending;
 import org.apache.camel.Exchange;
 import org.apache.camel.ExchangePattern;
 import org.apache.camel.ExchangePropertyKey;
@@ -50,7 +51,8 @@ import static 
org.apache.camel.support.ExchangeHelper.copyResultsPreservePattern
  *
  * @see PollEnricher
  */
-public class Enricher extends BaseProcessorSupport implements IdAware, 
RouteIdAware, StepIdAware, CamelContextAware {
+public class Enricher extends BaseProcessorSupport
+        implements EndpointSending, IdAware, RouteIdAware, StepIdAware, 
CamelContextAware {
 
     private CamelContext camelContext;
     private String id;
diff --git 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
index 86ff6459d115..543c8f04c730 100644
--- 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
+++ 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
@@ -23,6 +23,7 @@ import org.apache.camel.CamelContext;
 import org.apache.camel.CamelContextAware;
 import org.apache.camel.Component;
 import org.apache.camel.Endpoint;
+import org.apache.camel.EndpointSending;
 import org.apache.camel.Exchange;
 import org.apache.camel.ExchangePattern;
 import org.apache.camel.ExchangePropertyKey;
@@ -54,7 +55,7 @@ import org.slf4j.LoggerFactory;
  * @see org.apache.camel.processor.SendProcessor
  */
 public class SendDynamicProcessor extends BaseProcessorSupport
-        implements IdAware, RouteIdAware, StepIdAware, CamelContextAware {
+        implements EndpointSending, IdAware, RouteIdAware, StepIdAware, 
CamelContextAware {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(SendDynamicProcessor.class);
 
diff --git 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendProcessor.java
 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendProcessor.java
index 5745d5ec39ff..48f135653234 100644
--- 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendProcessor.java
+++ 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendProcessor.java
@@ -24,6 +24,7 @@ import org.apache.camel.AsyncProducer;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Endpoint;
 import org.apache.camel.EndpointAware;
+import org.apache.camel.EndpointSending;
 import org.apache.camel.Exchange;
 import org.apache.camel.ExchangePattern;
 import org.apache.camel.ExchangePropertyKey;
@@ -52,7 +53,7 @@ import org.slf4j.LoggerFactory;
  * @see SendDynamicProcessor
  */
 public class SendProcessor extends BaseProcessorSupport
-        implements Traceable, EndpointAware, IdAware, RouteIdAware, 
StepIdAware {
+        implements Traceable, EndpointAware, EndpointSending, IdAware, 
RouteIdAware, StepIdAware {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(SendProcessor.class);
 
diff --git 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/WireTapProcessor.java
 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/WireTapProcessor.java
index bda30103fb71..67a6132fa8a9 100644
--- 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/WireTapProcessor.java
+++ 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/WireTapProcessor.java
@@ -24,6 +24,7 @@ import org.apache.camel.AsyncCallback;
 import org.apache.camel.AsyncProcessor;
 import org.apache.camel.CamelContext;
 import org.apache.camel.CamelContextAware;
+import org.apache.camel.EndpointSending;
 import org.apache.camel.Exchange;
 import org.apache.camel.ExchangePattern;
 import org.apache.camel.ExchangePropertyKey;
@@ -49,7 +50,7 @@ import org.slf4j.LoggerFactory;
  * Processor for wire tapping exchanges to an endpoint destination.
  */
 public class WireTapProcessor extends BaseProcessorSupport
-        implements Traceable, ShutdownAware, IdAware, RouteIdAware, 
StepIdAware, CamelContextAware {
+        implements Traceable, ShutdownAware, EndpointSending, IdAware, 
RouteIdAware, StepIdAware, CamelContextAware {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(WireTapProcessor.class);
 
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 346a9aa8e2ad..b02c23dcc37a 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
@@ -485,6 +485,25 @@ extend from:
 - `AbstractHttpSpanDecorator` (returns `CLIENT` for `EVENT_SENT`, `SERVER` for 
`EVENT_RECEIVED`)
 - `AbstractMessagingSpanDecorator` (returns `PRODUCER` for `EVENT_SENT`, 
`CONSUMER` for `EVENT_RECEIVED`)
 
+==== Reduced span verbosity for endpoint-sending processors
+
+Processors that send to an endpoint (`to`, `toD`, `wireTap`, `enrich`) no 
longer emit a redundant
+processor span (`EVENT_PROCESS`) wrapping the endpoint event span 
(`EVENT_SENT`). Previously these
+EIPs produced two spans in a parent-child relationship — the processor span 
added no value because
+the event span already captures the destination, timing, and status.
+
+A new `org.apache.camel.EndpointSending` marker interface in `camel-api` 
identifies processors that
+will produce their own endpoint span via `ExchangeSendingEvent`. The telemetry 
intercept strategy
+skips creating a processor span for these processors.
+
+This reduces span tree depth and total span count for routes that use `to`, 
`toD`, `wireTap`, or
+`enrich`. Non-send EIPs (`log`, `bean`, `split`, `aggregate`, etc.) continue 
to produce processor
+spans as before.
+
+If you have custom telemetry code that relies on the processor span existing 
as a parent of the
+endpoint span for `to`, `toD`, `wireTap`, or `enrich`, you will need to adjust 
your span hierarchy
+expectations.
+
 === camel-opentelemetry2
 
 In order to prevent a potential leak when running asynchronous components we 
need to rethink the implementation details of `camel-opentelemetry2` and remove 
the  `Scope` wrapping that, when asynchronous, was opening the `Scope` in a 
thread and closing in another (what we had called "dirty" context). We are now 
removing this wrapping and moving this part exclusively in the custom Camel 
`Processors`. Here Camel will take care to open the Opentelemetry scope and 
close it within the same thread.


Reply via email to