This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch feature/CAMEL-23712-traceCustomIdOnly in repository https://gitbox.apache.org/repos/asf/camel.git
commit a97b00b0687ddffb066099653610bce6357c7535 Author: Claus Ibsen <[email protected]> AuthorDate: Mon Jun 8 16:43:34 2026 +0200 CAMEL-23712: Add traceCustomIdOnly option to only trace processors with custom IDs Co-Authored-By: Claude Opus 4.6 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../MicrometerObservabilityTracerConfigurer.java | 6 ++ .../OpenTelemetryTracerConfigurer.java | 6 ++ .../src/main/docs/opentelemetry2.adoc | 1 + .../telemetrydev/TelemetryDevTracerConfigurer.java | 6 ++ .../TraceProcessorsInterceptStrategy.java | 4 + .../java/org/apache/camel/telemetry/Tracer.java | 10 ++ .../camel/telemetry/TraceCustomIdOnlyTest.java | 116 +++++++++++++++++++++ .../src/main/java/org/apache/camel/NamedNode.java | 9 ++ 8 files changed, 158 insertions(+) diff --git a/components/camel-micrometer-observability/src/generated/java/org/apache/camel/micrometer/observability/MicrometerObservabilityTracerConfigurer.java b/components/camel-micrometer-observability/src/generated/java/org/apache/camel/micrometer/observability/MicrometerObservabilityTracerConfigurer.java index 24459132c651..aa5b235acc4c 100644 --- a/components/camel-micrometer-observability/src/generated/java/org/apache/camel/micrometer/observability/MicrometerObservabilityTracerConfigurer.java +++ b/components/camel-micrometer-observability/src/generated/java/org/apache/camel/micrometer/observability/MicrometerObservabilityTracerConfigurer.java @@ -36,6 +36,8 @@ public class MicrometerObservabilityTracerConfigurer extends org.apache.camel.su case "propagator": target.setPropagator(property(camelContext, io.micrometer.tracing.propagation.Propagator.class, value)); return true; case "spanlifecyclemanager": case "spanLifecycleManager": target.setSpanLifecycleManager(property(camelContext, org.apache.camel.telemetry.SpanLifecycleManager.class, value)); return true; + case "tracecustomidonly": + case "traceCustomIdOnly": target.setTraceCustomIdOnly(property(camelContext, boolean.class, value)); return true; case "traceheadersinclusion": case "traceHeadersInclusion": target.setTraceHeadersInclusion(property(camelContext, boolean.class, value)); return true; case "traceprocessors": @@ -61,6 +63,8 @@ public class MicrometerObservabilityTracerConfigurer extends org.apache.camel.su case "propagator": return io.micrometer.tracing.propagation.Propagator.class; case "spanlifecyclemanager": case "spanLifecycleManager": return org.apache.camel.telemetry.SpanLifecycleManager.class; + case "tracecustomidonly": + case "traceCustomIdOnly": return boolean.class; case "traceheadersinclusion": case "traceHeadersInclusion": return boolean.class; case "traceprocessors": @@ -87,6 +91,8 @@ public class MicrometerObservabilityTracerConfigurer extends org.apache.camel.su case "propagator": return target.getPropagator(); case "spanlifecyclemanager": case "spanLifecycleManager": return target.getSpanLifecycleManager(); + case "tracecustomidonly": + case "traceCustomIdOnly": return target.isTraceCustomIdOnly(); case "traceheadersinclusion": case "traceHeadersInclusion": return target.isTraceHeadersInclusion(); case "traceprocessors": diff --git a/components/camel-opentelemetry2/src/generated/java/org/apache/camel/opentelemetry2/OpenTelemetryTracerConfigurer.java b/components/camel-opentelemetry2/src/generated/java/org/apache/camel/opentelemetry2/OpenTelemetryTracerConfigurer.java index 98722d317372..16a6995a0166 100644 --- a/components/camel-opentelemetry2/src/generated/java/org/apache/camel/opentelemetry2/OpenTelemetryTracerConfigurer.java +++ b/components/camel-opentelemetry2/src/generated/java/org/apache/camel/opentelemetry2/OpenTelemetryTracerConfigurer.java @@ -33,6 +33,8 @@ public class OpenTelemetryTracerConfigurer extends org.apache.camel.support.comp case "includePatterns": target.setIncludePatterns(property(camelContext, java.lang.String.class, value)); return true; case "spanlifecyclemanager": case "spanLifecycleManager": target.setSpanLifecycleManager(property(camelContext, org.apache.camel.telemetry.SpanLifecycleManager.class, value)); return true; + case "tracecustomidonly": + case "traceCustomIdOnly": target.setTraceCustomIdOnly(property(camelContext, boolean.class, value)); return true; case "traceheadersinclusion": case "traceHeadersInclusion": target.setTraceHeadersInclusion(property(camelContext, boolean.class, value)); return true; case "traceprocessors": @@ -54,6 +56,8 @@ public class OpenTelemetryTracerConfigurer extends org.apache.camel.support.comp case "includePatterns": return java.lang.String.class; case "spanlifecyclemanager": case "spanLifecycleManager": return org.apache.camel.telemetry.SpanLifecycleManager.class; + case "tracecustomidonly": + case "traceCustomIdOnly": return boolean.class; case "traceheadersinclusion": case "traceHeadersInclusion": return boolean.class; case "traceprocessors": @@ -76,6 +80,8 @@ public class OpenTelemetryTracerConfigurer extends org.apache.camel.support.comp case "includePatterns": return target.getIncludePatterns(); case "spanlifecyclemanager": case "spanLifecycleManager": return target.getSpanLifecycleManager(); + case "tracecustomidonly": + case "traceCustomIdOnly": return target.isTraceCustomIdOnly(); case "traceheadersinclusion": case "traceHeadersInclusion": return target.isTraceHeadersInclusion(); case "traceprocessors": diff --git a/components/camel-opentelemetry2/src/main/docs/opentelemetry2.adoc b/components/camel-opentelemetry2/src/main/docs/opentelemetry2.adoc index 9179c7bb1547..36112d5975fd 100644 --- a/components/camel-opentelemetry2/src/main/docs/opentelemetry2.adoc +++ b/components/camel-opentelemetry2/src/main/docs/opentelemetry2.adoc @@ -27,6 +27,7 @@ The configuration properties for the OpenTelemetry2 tracer are: |`enabled`| false | Turn the tracing on/off. |`traceProcessors`| false | Trace inner custom processors (i.e., any `process` configured in the route). When disabled, custom processors are not visible from the OpenTelemetry perspective and have no active span or context. |`disableCoreProcessors`| false | Disable any inner core processors (any core DSL processor provided in the route, for example `bean`, `log`, ...). +|`traceCustomIdOnly`| false | When enabled, only trace processors and endpoints where the route author explicitly assigned a custom `.id()`. Processors without a custom id (auto-generated ids like `log1`, `setHeader2`) are skipped. This provides intent-based filtering — assign ids to steps that have business meaning, and only those produce spans. | `excludePatterns` | | A comma-separated list of patterns (e.g., `log*,direct*,setBody*`) to exclude from tracing. Spans matching these patterns will be disabled. If nothing is specified, no processors are excluded by default. | `includePatterns` | | A comma-separated list of patterns (e.g., `log*,direct*,setBody*`) to explicitly include in a trace. Spans matching these patterns will be enabled. If nothing is specified, all processors are included by default. | `traceHeadersInclusion`| `false` | If set to `true`, adds the generated telemetry `CAMEL_TRACE_ID` and `CAMEL_SPAN_ID` Exchange headers. diff --git a/components/camel-telemetry-dev/src/generated/java/org/apache/camel/telemetrydev/TelemetryDevTracerConfigurer.java b/components/camel-telemetry-dev/src/generated/java/org/apache/camel/telemetrydev/TelemetryDevTracerConfigurer.java index 47fb2d5a9040..da78a684e0e8 100644 --- a/components/camel-telemetry-dev/src/generated/java/org/apache/camel/telemetrydev/TelemetryDevTracerConfigurer.java +++ b/components/camel-telemetry-dev/src/generated/java/org/apache/camel/telemetrydev/TelemetryDevTracerConfigurer.java @@ -33,6 +33,8 @@ public class TelemetryDevTracerConfigurer extends org.apache.camel.support.compo case "includePatterns": target.setIncludePatterns(property(camelContext, java.lang.String.class, value)); return true; case "spanlifecyclemanager": case "spanLifecycleManager": target.setSpanLifecycleManager(property(camelContext, org.apache.camel.telemetry.SpanLifecycleManager.class, value)); return true; + case "tracecustomidonly": + case "traceCustomIdOnly": target.setTraceCustomIdOnly(property(camelContext, boolean.class, value)); return true; case "traceformat": case "traceFormat": target.setTraceFormat(property(camelContext, java.lang.String.class, value)); return true; case "traceheadersinclusion": @@ -56,6 +58,8 @@ public class TelemetryDevTracerConfigurer extends org.apache.camel.support.compo case "includePatterns": return java.lang.String.class; case "spanlifecyclemanager": case "spanLifecycleManager": return org.apache.camel.telemetry.SpanLifecycleManager.class; + case "tracecustomidonly": + case "traceCustomIdOnly": return boolean.class; case "traceformat": case "traceFormat": return java.lang.String.class; case "traceheadersinclusion": @@ -80,6 +84,8 @@ public class TelemetryDevTracerConfigurer extends org.apache.camel.support.compo case "includePatterns": return target.getIncludePatterns(); case "spanlifecyclemanager": case "spanLifecycleManager": return target.getSpanLifecycleManager(); + case "tracecustomidonly": + case "traceCustomIdOnly": return target.isTraceCustomIdOnly(); case "traceformat": case "traceFormat": return target.getTraceFormat(); case "traceheadersinclusion": 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 2185881e7077..4ba143cf20d3 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 @@ -111,6 +111,10 @@ public class TraceProcessorsInterceptStrategy implements InterceptStrategy { if (isEndpointSending(processor)) { return false; } + // when traceCustomIdOnly is enabled, only trace processors with an explicit .id() + if (tracer.isTraceCustomIdOnly() && !processorDefinition.hasCustomIdAssigned()) { + return false; + } String shortName = processorDefinition.getShortName(); boolean enabled = isCoreProcessEnabled(shortName) || isCustomProcessEnabled(shortName); return enabled && tracer.match(processorName, exchange.getContext()); 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 f3bf066d0bcb..95c26be5dd54 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 @@ -59,6 +59,7 @@ public abstract class Tracer extends ServiceSupport implements CamelTracingServi private String includePatterns; private boolean traceProcessors; private boolean disableCoreProcessors; + private boolean traceCustomIdOnly; private boolean traceHeadersInclusion; private final TracingEventNotifier eventNotifier = new TracingEventNotifier(); @@ -127,6 +128,15 @@ public abstract class Tracer extends ServiceSupport implements CamelTracingServi this.disableCoreProcessors = disableCoreProcessors; } + @ManagedAttribute + public boolean isTraceCustomIdOnly() { + return traceCustomIdOnly; + } + + public void setTraceCustomIdOnly(boolean traceCustomIdOnly) { + this.traceCustomIdOnly = traceCustomIdOnly; + } + public SpanLifecycleManager getSpanLifecycleManager() { return this.spanLifecycleManager; } diff --git a/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/TraceCustomIdOnlyTest.java b/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/TraceCustomIdOnlyTest.java new file mode 100644 index 000000000000..ea7a40fff119 --- /dev/null +++ b/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/TraceCustomIdOnlyTest.java @@ -0,0 +1,116 @@ +/* + * 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; + +import java.util.List; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.telemetry.mock.MockSpanAdapter; +import org.apache.camel.telemetry.mock.MockTrace; +import org.apache.camel.telemetry.mock.MockTracer; +import org.apache.camel.test.junit6.ExchangeTestSupport; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class TraceCustomIdOnlyTest extends ExchangeTestSupport { + + MockTracer mockTracer; + + @Override + protected CamelContext createCamelContext() throws Exception { + CamelContext context = super.createCamelContext(); + this.mockTracer = new MockTracer(); + mockTracer.setTraceProcessors(true); + mockTracer.setTraceCustomIdOnly(true); + CamelContextAware.trySetCamelContext(mockTracer, context); + mockTracer.init(context); + return context; + } + + @Test + void testOnlyCustomIdProcessorsTraced() { + template.sendBody("direct:start", "my-body"); + Map<String, MockTrace> traces = mockTracer.traces(); + assertEquals(1, traces.size()); + checkTrace(traces.values().iterator().next()); + } + + private void checkTrace(MockTrace trace) { + List<Span> spans = trace.spans(); + // Expected spans: + // 1. EVENT_SENT (direct:start) — event span from sending + // 2. EVENT_RECEIVED (direct:start) — event span from route policy + // 3. EVENT_PROCESS (myProcessor) — custom .id("myProcessor") processor + // 4. EVENT_SENT (log:info) — event span from to("log:info") + // Processors WITHOUT custom id (log, setHeader) should NOT produce spans + assertEquals(4, spans.size()); + + MockSpanAdapter testProducer = (MockSpanAdapter) spans.get(0); + MockSpanAdapter direct = (MockSpanAdapter) spans.get(1); + MockSpanAdapter customProcessor = (MockSpanAdapter) spans.get(2); + MockSpanAdapter log = (MockSpanAdapter) spans.get(3); + + // Validate span completion + assertEquals("true", testProducer.getTag("isDone")); + assertEquals("true", direct.getTag("isDone")); + assertEquals("true", customProcessor.getTag("isDone")); + assertEquals("true", log.getTag("isDone")); + + // Validate same trace + assertEquals(testProducer.getTag("traceid"), direct.getTag("traceid")); + assertEquals(testProducer.getTag("traceid"), customProcessor.getTag("traceid")); + assertEquals(testProducer.getTag("traceid"), log.getTag("traceid")); + + // Validate op types + assertEquals(Op.EVENT_RECEIVED.toString(), direct.getTag("op")); + assertEquals(Op.EVENT_PROCESS.toString(), customProcessor.getTag("op")); + + // Validate hierarchy + assertNull(testProducer.getTag("parentSpan")); + assertEquals(testProducer.getTag("spanid"), direct.getTag("parentSpan")); + assertEquals(direct.getTag("spanid"), customProcessor.getTag("parentSpan")); + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:start") + .routeId("start") + .log("A message") + .setHeader("foo", constant("bar")) + .process(new Processor() { + @Override + public void process(Exchange exchange) throws Exception { + exchange.getIn().setHeader("operation", "fake"); + } + }).id("myProcessor") + .to("log:info"); + } + }; + } + +} diff --git a/core/camel-api/src/main/java/org/apache/camel/NamedNode.java b/core/camel-api/src/main/java/org/apache/camel/NamedNode.java index 2716a839829d..87578be91ca7 100644 --- a/core/camel-api/src/main/java/org/apache/camel/NamedNode.java +++ b/core/camel-api/src/main/java/org/apache/camel/NamedNode.java @@ -116,6 +116,15 @@ public interface NamedNode extends LineNumberAware { return Collections.emptyList(); } + /** + * Returns whether a custom id has been assigned (vs auto-generated by Camel). + * + * @since 4.21 + */ + default boolean hasCustomIdAssigned() { + return false; + } + /** * Special methods for Choice EIP */
