This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch feature/CAMEL-24003-activity-span-enrichment in repository https://gitbox.apache.org/repos/asf/camel.git
commit 684b49dcda6ff3a27711c309288448ef89850b40 Author: Claus Ibsen <[email protected]> AuthorDate: Sat Jul 11 22:45:24 2026 +0200 CAMEL-24003: Enrich activity data with span decorator attributes from camel-telemetry Co-Authored-By: Claude Opus 4.6 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../org/apache/camel/telemetry/RecordingSpan.java | 78 ++++++++++++++++++++++ .../java/org/apache/camel/telemetry/Tracer.java | 26 +++++++- .../src/main/java/org/apache/camel/Exchange.java | 8 +++ .../java/org/apache/camel/ExchangePropertyKey.java | 5 +- .../java/org/apache/camel/spi/BacklogTracer.java | 9 +++ 5 files changed, 124 insertions(+), 2 deletions(-) diff --git a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/RecordingSpan.java b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/RecordingSpan.java new file mode 100644 index 000000000000..28f40223d963 --- /dev/null +++ b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/RecordingSpan.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.telemetry; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * A {@link Span} wrapper that records all {@code setTag} and {@code setComponent} calls into a {@link Map} while + * delegating every operation to the underlying span. This is used to capture span decorator attributes for the + * BacklogTracer activity enrichment without running decorator logic twice. + * <p> + * The recording is only activated when the BacklogTracer activity feature is enabled, so in production there is no + * wrapping overhead. + */ +class RecordingSpan implements Span { + + private final Span delegate; + private final Map<String, String> tags = new LinkedHashMap<>(); + + RecordingSpan(Span delegate) { + this.delegate = delegate; + } + + @Override + public void setTag(String key, String value) { + delegate.setTag(key, value); + if (value != null) { + tags.put(key, value); + } + } + + @Override + public void setComponent(String component) { + delegate.setComponent(component); + if (component != null) { + tags.put("component", component); + } + } + + @Override + public void setError(boolean isError) { + delegate.setError(isError); + } + + @Override + public void log(Map<String, String> fields) { + delegate.log(fields); + } + + /** + * Returns the real span that this wrapper delegates to. + */ + Span getDelegate() { + return delegate; + } + + /** + * Returns the recorded tags as an unmodifiable snapshot. + */ + Map<String, String> getRecordedTags() { + return Map.copyOf(tags); + } +} 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 40d148fd5c88..91806d3fd62c 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 @@ -24,11 +24,13 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.camel.CamelContext; import org.apache.camel.Endpoint; import org.apache.camel.Exchange; +import org.apache.camel.ExchangePropertyKey; import org.apache.camel.NamedNode; import org.apache.camel.Route; import org.apache.camel.RuntimeCamelException; import org.apache.camel.StaticService; import org.apache.camel.api.management.ManagedAttribute; +import org.apache.camel.spi.BacklogTracer; import org.apache.camel.spi.CamelEvent; import org.apache.camel.spi.CamelLogger; import org.apache.camel.spi.CamelTracingService; @@ -68,6 +70,7 @@ public abstract class Tracer extends ServiceSupport implements CamelTracingServi private final TracingEventNotifier eventNotifier = new TracingEventNotifier(); private final SpanStorageManager spanStorageManager = new SpanStorageManagerExchange(); private final SpanDecoratorManager spanDecoratorManager = new SpanDecoratorManagerImpl(); + private boolean activityEnabled; /* * It has to be provided by the specific implementation @@ -209,6 +212,13 @@ public abstract class Tracer extends ServiceSupport implements CamelTracingServi InterceptStrategy interceptStrategy = new TraceProcessorsInterceptStrategy(this); camelContext.getCamelContextExtension().addInterceptStrategy(interceptStrategy); + // check if BacklogTracer activity is enabled so we can enrich activity data with span attributes + BacklogTracer backlogTracer + = camelContext.getCamelContextExtension().getContextPlugin(BacklogTracer.class); + if (backlogTracer != null) { + activityEnabled = backlogTracer.isActivityEnabled(); + } + initTracer(); ServiceHelper.startService(eventNotifier); } @@ -349,7 +359,21 @@ public abstract class Tracer extends ServiceSupport implements CamelTracingServi Span span = spanLifecycleManager.create(spanName, spanKind, parentSpan, spanDecorator.getExtractor(exchange)); span.setTag(TagConstants.OP, op.toString()); - spanDecorator.beforeTracingEvent(span, exchange, endpoint); + + if (activityEnabled && op == Op.EVENT_SENT) { + // wrap with recording span to capture decorator attributes for activity enrichment + RecordingSpan recording = new RecordingSpan(span); + spanDecorator.beforeTracingEvent(recording, exchange, endpoint); + // store recorded tags on exchange for BacklogTracer activity to pick up + Map<String, String> tags = recording.getRecordedTags(); + if (!tags.isEmpty()) { + exchange.setProperty(ExchangePropertyKey.ACTIVITY_SPAN_TAGS, tags); + } + // continue with the real span for activation/storage + } else { + spanDecorator.beforeTracingEvent(span, exchange, endpoint); + } + spanLifecycleManager.activate(span); spanStorageManager.push(exchange, span); spanLifecycleManager.inject(span, spanDecorator.getInjector(exchange), this.traceHeadersInclusion); diff --git a/core/camel-api/src/main/java/org/apache/camel/Exchange.java b/core/camel-api/src/main/java/org/apache/camel/Exchange.java index 27607438f0a8..ac8ec7044d75 100644 --- a/core/camel-api/src/main/java/org/apache/camel/Exchange.java +++ b/core/camel-api/src/main/java/org/apache/camel/Exchange.java @@ -327,6 +327,14 @@ public interface Exchange extends VariableAware { String OTEL_ACTIVE_SPAN = "OpenTracing.activeSpan"; @Deprecated(since = "4.19.0") String OTEL_CLOSE_CLIENT_SCOPE = "OpenTracing.closeClientScope"; + /** + * Exchange property set by tracing implementations (e.g., camel-telemetry) containing span decorator attributes as a + * {@code Map<String, String>}. Used by the BacklogTracer activity feature to enrich endpoint send entries with + * component-specific details (e.g., Kafka topic, SQL query, HTTP method). + * + * @since 4.22 + */ + String ACTIVITY_SPAN_TAGS = "CamelActivitySpanTags"; /** * Returns the {@link ExchangePattern} (MEP) of this exchange. diff --git a/core/camel-api/src/main/java/org/apache/camel/ExchangePropertyKey.java b/core/camel-api/src/main/java/org/apache/camel/ExchangePropertyKey.java index 8b4dd6415e0c..c3d9ed220f05 100644 --- a/core/camel-api/src/main/java/org/apache/camel/ExchangePropertyKey.java +++ b/core/camel-api/src/main/java/org/apache/camel/ExchangePropertyKey.java @@ -100,7 +100,8 @@ public enum ExchangePropertyKey { @Deprecated(since = "4.19.0") OTEL_ACTIVE_SPAN(Exchange.OTEL_ACTIVE_SPAN), @Deprecated(since = "4.19.0") - OTEL_CLOSE_CLIENT_SCOPE(Exchange.OTEL_CLOSE_CLIENT_SCOPE); + OTEL_CLOSE_CLIENT_SCOPE(Exchange.OTEL_CLOSE_CLIENT_SCOPE), + ACTIVITY_SPAN_TAGS(Exchange.ACTIVITY_SPAN_TAGS); private final String name; @@ -246,6 +247,8 @@ public enum ExchangePropertyKey { // Deprecated since 4.19.0 case Exchange.OTEL_CLOSE_CLIENT_SCOPE: return OTEL_CLOSE_CLIENT_SCOPE; + case Exchange.ACTIVITY_SPAN_TAGS: + return ACTIVITY_SPAN_TAGS; default: return null; } diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracer.java b/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracer.java index d56acdc2dde5..2596cd53ee3d 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracer.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracer.java @@ -43,6 +43,15 @@ import org.jspecify.annotations.Nullable; */ public interface BacklogTracer { + /** + * Whether the activity tracking feature is enabled. + * + * @since 4.22 + */ + default boolean isActivityEnabled() { + return false; + } + /** * Is the tracer enabled. */
