From 6ffabd5b391e692faf13d1272d1fdd5bb3082244 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 15 Jul 2026 17:58:59 -0700 Subject: [PATCH 01/18] Pin messaging semantic conventions to v1.43 behind preview --- .../build.gradle.kts | 18 +- .../semconv/db/SqlQueryAnalyzer.java | 4 +- .../semconv/messaging/MessageOperation.java | 7 +- .../MessagingAttributesExtractor.java | 74 +++++- .../MessagingAttributesExtractorBuilder.java | 10 +- .../messaging/MessagingConsumerMetrics.java | 104 +++++++-- .../messaging/MessagingMetricsAdvice.java | 91 +++++++- .../semconv/messaging/MessagingOperation.java | 54 +++++ .../messaging/MessagingProcessMetrics.java | 97 ++++++++ .../messaging/MessagingProducerMetrics.java | 78 ++++++- .../messaging/MessagingSpanKindExtractor.java | 41 ++++ .../messaging/MessagingSpanNameExtractor.java | 33 ++- .../MessagingAttributesExtractorTest.java | 58 ++++- .../MessagingConsumerMetricsTest.java | 218 ++++++++++++++++++ .../messaging/MessagingMetricsAdviceTest.java | 47 ++++ .../MessagingProcessMetricsTest.java | 93 ++++++++ .../MessagingProducerMetricsTest.java | 180 +++++++++------ .../MessagingSpanKindExtractorTest.java | 34 +++ .../MessagingSpanNameExtractorTest.java | 86 ++++++- .../api/internal/SemconvStability.java | 4 + .../api/internal/SemconvStabilityTest.java | 66 ++++++ .../v0_11/KafkaClientDefaultTest.java | 61 ++++- .../v0_11/internal/KafkaClientBaseTest.java | 30 ++- .../v2_6/AbstractInterceptorsTest.java | 6 +- .../InterceptorsSuppressReceiveSpansTest.java | 4 +- .../v2_6/WrapperSuppressReceiveSpansTest.java | 4 +- .../kafkaclients/v2_6/WrapperTest.java | 6 +- .../v2_8/telemetry/PulsarSingletons.java | 4 +- 28 files changed, 1324 insertions(+), 188 deletions(-) create mode 100644 instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingOperation.java create mode 100644 instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProcessMetrics.java create mode 100644 instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractor.java create mode 100644 instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingConsumerMetricsTest.java create mode 100644 instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingMetricsAdviceTest.java create mode 100644 instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProcessMetricsTest.java create mode 100644 instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractorTest.java diff --git a/instrumentation-api-incubator/build.gradle.kts b/instrumentation-api-incubator/build.gradle.kts index 1a59ad1364a8..2c1404df92ee 100644 --- a/instrumentation-api-incubator/build.gradle.kts +++ b/instrumentation-api-incubator/build.gradle.kts @@ -92,6 +92,7 @@ tasks { testClassesDirs = sourceSets.test.get().output.classesDirs classpath = sourceSets.test.get().runtimeClasspath jvmArgs("-Dotel.semconv-stability.opt-in=database,code,service.peer,rpc") + jvmArgs("-Dotel.semconv-stability.preview=messaging") inputs.dir(jflexOutputDir) } @@ -99,6 +100,15 @@ tasks { testClassesDirs = sourceSets.test.get().output.classesDirs classpath = sourceSets.test.get().runtimeClasspath jvmArgs("-Dotel.semconv-stability.opt-in=database/dup,code/dup,service.peer/dup,rpc/dup") + jvmArgs("-Dotel.semconv-stability.preview=messaging/dup") + inputs.dir(jflexOutputDir) + } + + val testV3Preview = register("testV3Preview") { + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath + jvmArgs("-Dotel.instrumentation.common.v3-preview=true") + jvmArgs("-Dotel.semconv-stability.preview=messaging") inputs.dir(jflexOutputDir) } @@ -117,6 +127,12 @@ tasks { } check { - dependsOn(testStableSemconv, testBothSemconv, testExceptionSignalLogs, testExceptionSignalLogsDup) + dependsOn( + testStableSemconv, + testBothSemconv, + testV3Preview, + testExceptionSignalLogs, + testExceptionSignalLogsDup, + ) } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlQueryAnalyzer.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlQueryAnalyzer.java index 6e4841154818..efea8d828d78 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlQueryAnalyzer.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlQueryAnalyzer.java @@ -78,7 +78,9 @@ private static SqlQuery analyzeWithSummaryImpl(String query, SqlDialect dialect) // visible for tests static boolean isCached(String query, SqlDialect dialect) { - return sqlToQueryCache.get(CacheKey.create(query, dialect)) != null; + Cache cache = + SemconvStability.v3Preview() ? sqlToQueryCacheWithSummary : sqlToQueryCache; + return cache.get(CacheKey.create(query, dialect)) != null; } @AutoValue diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessageOperation.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessageOperation.java index e7ef5c479ed0..ef87253e22a1 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessageOperation.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessageOperation.java @@ -9,7 +9,7 @@ /** * Represents type of operations + * href="https://github.com/open-telemetry/semantic-conventions/blob/v1.43.0/docs/messaging/messaging-spans.md#operation-types">operations * that may be used in a messaging system. */ public enum MessageOperation { @@ -18,9 +18,8 @@ public enum MessageOperation { PROCESS; /** - * Returns the operation name as defined in the - * specification. + * Returns the legacy operation name. The v1.43 operation name defaults to this value unless an + * instrumentation supplies a system-specific override. */ String operationName() { return name().toLowerCase(Locale.ROOT); diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractor.java index 3ad89017a047..b93a5abb734d 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractor.java @@ -5,6 +5,10 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldMessagingSemconv; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; +import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; + import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; @@ -17,7 +21,7 @@ /** * Extractor of messaging + * href="https://github.com/open-telemetry/semantic-conventions/blob/v1.43.0/docs/messaging/messaging-spans.md">messaging * attributes. * *

This class delegates to a type-specific {@link MessagingAttributesGetter} for individual @@ -29,8 +33,10 @@ public final class MessagingAttributesExtractor // copied from MessagingIncubatingAttributes private static final AttributeKey MESSAGING_BATCH_MESSAGE_COUNT = AttributeKey.longKey("messaging.batch.message_count"); - private static final AttributeKey MESSAGING_CLIENT_ID = + private static final AttributeKey MESSAGING_CLIENT_ID_OLD = AttributeKey.stringKey("messaging.client_id"); + private static final AttributeKey MESSAGING_CLIENT_ID = + AttributeKey.stringKey("messaging.client.id"); private static final AttributeKey MESSAGING_DESTINATION_ANONYMOUS = AttributeKey.booleanKey("messaging.destination.anonymous"); private static final AttributeKey MESSAGING_DESTINATION_NAME = @@ -51,6 +57,10 @@ public final class MessagingAttributesExtractor AttributeKey.stringKey("messaging.message.id"); private static final AttributeKey MESSAGING_OPERATION = AttributeKey.stringKey("messaging.operation"); + private static final AttributeKey MESSAGING_OPERATION_NAME = + AttributeKey.stringKey("messaging.operation.name"); + private static final AttributeKey MESSAGING_OPERATION_TYPE = + AttributeKey.stringKey("messaging.operation.type"); private static final AttributeKey MESSAGING_SYSTEM = AttributeKey.stringKey("messaging.system"); @@ -61,26 +71,51 @@ public final class MessagingAttributesExtractor * with default configuration. */ public static AttributesExtractor create( - MessagingAttributesGetter getter, MessageOperation operation) { + MessagingAttributesGetter getter, @Nullable MessageOperation operation) { return builder(getter, operation).build(); } + /** + * Creates the messaging attributes extractor with a system-specific v1.43 operation name. + * + *

The {@code operationName} is emitted as {@code messaging.operation.name}. The legacy {@code + * messaging.operation} value remains derived from {@code operation}. + */ + public static AttributesExtractor create( + MessagingAttributesGetter getter, + MessageOperation operation, + String operationName) { + return builder(getter, operation, operationName).build(); + } + /** * Returns a new {@link MessagingAttributesExtractorBuilder} for the given {@link MessageOperation * operation} that can be used to configure the messaging attributes extractor. */ public static MessagingAttributesExtractorBuilder builder( - MessagingAttributesGetter getter, MessageOperation operation) { + MessagingAttributesGetter getter, @Nullable MessageOperation operation) { return new MessagingAttributesExtractorBuilder<>(getter, operation); } + /** + * Returns a new {@link MessagingAttributesExtractorBuilder} with a system-specific v1.43 + * operation name. + */ + public static MessagingAttributesExtractorBuilder builder( + MessagingAttributesGetter getter, + MessageOperation operation, + String operationName) { + return new MessagingAttributesExtractorBuilder<>( + getter, MessagingOperation.create(operation, operationName)); + } + private final MessagingAttributesGetter getter; - private final MessageOperation operation; + @Nullable private final MessagingOperation operation; private final List capturedHeaders; MessagingAttributesExtractor( MessagingAttributesGetter getter, - MessageOperation operation, + @Nullable MessagingOperation operation, List capturedHeaders) { this.getter = getter; this.operation = operation; @@ -93,7 +128,12 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST boolean isTemporaryDestination = getter.isTemporaryDestination(request); if (isTemporaryDestination) { attributes.put(MESSAGING_DESTINATION_TEMPORARY, true); - attributes.put(MESSAGING_DESTINATION_NAME, TEMP_DESTINATION_NAME); + if (emitStableMessagingSemconv()) { + attributes.put(MESSAGING_DESTINATION_NAME, getter.getDestination(request)); + attributes.put(MESSAGING_DESTINATION_TEMPLATE, getter.getDestinationTemplate(request)); + } else { + attributes.put(MESSAGING_DESTINATION_NAME, TEMP_DESTINATION_NAME); + } } else { attributes.put(MESSAGING_DESTINATION_NAME, getter.getDestination(request)); attributes.put(MESSAGING_DESTINATION_TEMPLATE, getter.getDestinationTemplate(request)); @@ -106,9 +146,20 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST attributes.put(MESSAGING_MESSAGE_CONVERSATION_ID, getter.getConversationId(request)); attributes.put(MESSAGING_MESSAGE_BODY_SIZE, getter.getMessageBodySize(request)); attributes.put(MESSAGING_MESSAGE_ENVELOPE_SIZE, getter.getMessageEnvelopeSize(request)); - attributes.put(MESSAGING_CLIENT_ID, getter.getClientId(request)); + if (emitOldMessagingSemconv()) { + attributes.put(MESSAGING_CLIENT_ID_OLD, getter.getClientId(request)); + } + if (emitStableMessagingSemconv()) { + attributes.put(MESSAGING_CLIENT_ID, getter.getClientId(request)); + } if (operation != null) { - attributes.put(MESSAGING_OPERATION, operation.operationName()); + if (emitOldMessagingSemconv()) { + attributes.put(MESSAGING_OPERATION, operation.operation().operationName()); + } + if (emitStableMessagingSemconv()) { + attributes.put(MESSAGING_OPERATION_NAME, operation.name()); + attributes.put(MESSAGING_OPERATION_TYPE, operation.type()); + } } } @@ -121,6 +172,9 @@ public void onEnd( @Nullable Throwable error) { attributes.put(MESSAGING_MESSAGE_ID, getter.getMessageId(request, response)); attributes.put(MESSAGING_BATCH_MESSAGE_COUNT, getter.getBatchMessageCount(request, response)); + if (emitStableMessagingSemconv() && error != null) { + attributes.put(ERROR_TYPE, error.getClass().getName()); + } for (String name : capturedHeaders) { List values = getter.getMessageHeader(request, name); @@ -141,7 +195,7 @@ public SpanKey internalGetSpanKey() { return null; } - switch (operation) { + switch (operation.operation()) { case PUBLISH: return SpanKey.PRODUCER; case RECEIVE: diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorBuilder.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorBuilder.java index 61f6c6916549..fc1035e91389 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorBuilder.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorBuilder.java @@ -12,16 +12,22 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import javax.annotation.Nullable; /** A builder of {@link MessagingAttributesExtractor}. */ public final class MessagingAttributesExtractorBuilder { final MessagingAttributesGetter getter; - final MessageOperation operation; + @Nullable final MessagingOperation operation; List capturedHeaders = emptyList(); MessagingAttributesExtractorBuilder( - MessagingAttributesGetter getter, MessageOperation operation) { + MessagingAttributesGetter getter, @Nullable MessageOperation operation) { + this(getter, MessagingOperation.createNullable(operation)); + } + + MessagingAttributesExtractorBuilder( + MessagingAttributesGetter getter, @Nullable MessagingOperation operation) { this.getter = getter; this.operation = operation; } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingConsumerMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingConsumerMetrics.java index 87a98b106d74..f642643e8c67 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingConsumerMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingConsumerMetrics.java @@ -5,6 +5,9 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldMessagingSemconv; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; +import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.logging.Level.FINE; @@ -23,10 +26,11 @@ import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil; import java.util.logging.Logger; +import javax.annotation.Nullable; /** * {@link OperationListener} which keeps track of Consumer + * href="https://github.com/open-telemetry/semantic-conventions/blob/v1.43.0/docs/messaging/messaging-metrics.md#consumer-metrics">consumer * metrics. */ public final class MessagingConsumerMetrics implements OperationListener { @@ -39,26 +43,17 @@ public final class MessagingConsumerMetrics implements OperationListener { ContextKey.named("messaging-consumer-metrics-state"); private static final Logger logger = Logger.getLogger(MessagingConsumerMetrics.class.getName()); - private final DoubleHistogram receiveDurationHistogram; - private final LongCounter receiveMessageCount; + @Nullable private final DoubleHistogram receiveDurationHistogram; + @Nullable private final LongCounter receiveMessageCount; + @Nullable private final DoubleHistogram clientOperationDurationHistogram; + @Nullable private final LongCounter consumedMessagesCounter; private MessagingConsumerMetrics(Meter meter) { - DoubleHistogramBuilder durationBuilder = - meter - .histogramBuilder("messaging.receive.duration") - .setDescription("Measures the duration of receive operation.") - .setExplicitBucketBoundariesAdvice(MessagingMetricsAdvice.DURATION_SECONDS_BUCKETS) - .setUnit("s"); - MessagingMetricsAdvice.applyReceiveDurationAdvice(durationBuilder); - receiveDurationHistogram = durationBuilder.build(); - - LongCounterBuilder longCounterBuilder = - meter - .counterBuilder("messaging.receive.messages") - .setDescription("Measures the number of received messages.") - .setUnit("{message}"); - MessagingMetricsAdvice.applyReceiveMessagesAdvice(longCounterBuilder); - receiveMessageCount = longCounterBuilder.build(); + receiveDurationHistogram = emitOldMessagingSemconv() ? buildReceiveDuration(meter) : null; + receiveMessageCount = emitOldMessagingSemconv() ? buildReceiveMessages(meter) : null; + clientOperationDurationHistogram = + emitStableMessagingSemconv() ? buildClientOperationDuration(meter) : null; + consumedMessagesCounter = emitStableMessagingSemconv() ? buildConsumedMessages(meter) : null; } public static OperationMetrics get() { @@ -85,11 +80,26 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { } Attributes attributes = state.startAttributes().toBuilder().putAll(endAttributes).build(); - receiveDurationHistogram.record( - (endNanos - state.startTimeNanos()) / NANOS_PER_S, attributes, context); + double duration = (endNanos - state.startTimeNanos()) / NANOS_PER_S; + if (receiveDurationHistogram != null) { + receiveDurationHistogram.record(duration, attributes, context); + } + Attributes filteredAttributes = + clientOperationDurationHistogram != null || consumedMessagesCounter != null + ? MessagingMetricsAdvice.filterAttributes(attributes) + : attributes; + if (clientOperationDurationHistogram != null) { + clientOperationDurationHistogram.record(duration, filteredAttributes, context); + } long receiveMessagesCount = getReceiveMessagesCount(state.startAttributes(), endAttributes); - receiveMessageCount.add(receiveMessagesCount, attributes, context); + if (receiveMessageCount != null) { + receiveMessageCount.add(receiveMessagesCount, attributes, context); + } + if (consumedMessagesCounter != null) { + consumedMessagesCounter.add( + getConsumedMessagesCount(attributes, receiveMessagesCount), filteredAttributes, context); + } } private static long getReceiveMessagesCount(Attributes... attributesList) { @@ -102,6 +112,56 @@ private static long getReceiveMessagesCount(Attributes... attributesList) { return 1; } + private static long getConsumedMessagesCount(Attributes attributes, long receiveMessagesCount) { + return attributes.get(MESSAGING_BATCH_MESSAGE_COUNT) == null + && attributes.get(ERROR_TYPE) != null + ? 0 + : receiveMessagesCount; + } + + private static DoubleHistogram buildReceiveDuration(Meter meter) { + DoubleHistogramBuilder builder = + meter + .histogramBuilder("messaging.receive.duration") + .setDescription("Measures the duration of receive operation.") + .setExplicitBucketBoundariesAdvice(MessagingMetricsAdvice.DURATION_SECONDS_BUCKETS) + .setUnit("s"); + MessagingMetricsAdvice.applyOldDurationAdvice(builder); + return builder.build(); + } + + private static LongCounter buildReceiveMessages(Meter meter) { + LongCounterBuilder builder = + meter + .counterBuilder("messaging.receive.messages") + .setDescription("Measures the number of received messages.") + .setUnit("{message}"); + MessagingMetricsAdvice.applyOldMessagesAdvice(builder); + return builder.build(); + } + + private static DoubleHistogram buildClientOperationDuration(Meter meter) { + DoubleHistogramBuilder builder = + meter + .histogramBuilder("messaging.client.operation.duration") + .setDescription( + "Duration of messaging operation initiated by a producer or consumer client.") + .setExplicitBucketBoundariesAdvice(MessagingMetricsAdvice.DURATION_SECONDS_BUCKETS) + .setUnit("s"); + MessagingMetricsAdvice.applyClientOperationDurationAdvice(builder); + return builder.build(); + } + + private static LongCounter buildConsumedMessages(Meter meter) { + LongCounterBuilder builder = + meter + .counterBuilder("messaging.client.consumed.messages") + .setDescription("Number of messages that were delivered to the application.") + .setUnit("{message}"); + MessagingMetricsAdvice.applyConsumedMessagesAdvice(builder); + return builder.build(); + } + @AutoValue abstract static class State { diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingMetricsAdvice.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingMetricsAdvice.java index ddc4115ea7f4..eab44d157a68 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingMetricsAdvice.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingMetricsAdvice.java @@ -12,10 +12,12 @@ import static java.util.Collections.unmodifiableList; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.incubator.metrics.ExtendedDoubleHistogramBuilder; import io.opentelemetry.api.incubator.metrics.ExtendedLongCounterBuilder; import io.opentelemetry.api.metrics.DoubleHistogramBuilder; import io.opentelemetry.api.metrics.LongCounterBuilder; +import java.util.ArrayList; import java.util.List; final class MessagingMetricsAdvice { @@ -28,14 +30,26 @@ final class MessagingMetricsAdvice { AttributeKey.stringKey("messaging.system"); private static final AttributeKey MESSAGING_DESTINATION_NAME = AttributeKey.stringKey("messaging.destination.name"); + private static final AttributeKey MESSAGING_DESTINATION_ANONYMOUS = + AttributeKey.booleanKey("messaging.destination.anonymous"); + private static final AttributeKey MESSAGING_DESTINATION_TEMPORARY = + AttributeKey.booleanKey("messaging.destination.temporary"); private static final AttributeKey MESSAGING_OPERATION = AttributeKey.stringKey("messaging.operation"); + private static final AttributeKey MESSAGING_OPERATION_NAME = + AttributeKey.stringKey("messaging.operation.name"); + private static final AttributeKey MESSAGING_OPERATION_TYPE = + AttributeKey.stringKey("messaging.operation.type"); + private static final AttributeKey MESSAGING_CONSUMER_GROUP_NAME = + AttributeKey.stringKey("messaging.consumer.group.name"); + private static final AttributeKey MESSAGING_DESTINATION_SUBSCRIPTION_NAME = + AttributeKey.stringKey("messaging.destination.subscription.name"); private static final AttributeKey MESSAGING_DESTINATION_PARTITION_ID = AttributeKey.stringKey("messaging.destination.partition.id"); private static final AttributeKey MESSAGING_DESTINATION_TEMPLATE = AttributeKey.stringKey("messaging.destination.template"); - private static final List> MESSAGING_ATTRIBUTES = + private static final List> OLD_ATTRIBUTES = asList( MESSAGING_SYSTEM, MESSAGING_DESTINATION_NAME, @@ -46,25 +60,88 @@ final class MessagingMetricsAdvice { SERVER_PORT, SERVER_ADDRESS); - static void applyPublishDurationAdvice(DoubleHistogramBuilder builder) { + private static final List> CLIENT_OPERATION_DURATION_ATTRIBUTES = + buildAttributes(true, true); + private static final List> SENT_MESSAGES_ATTRIBUTES = + buildAttributes(false, false); + private static final List> CONSUMED_MESSAGES_ATTRIBUTES = + buildAttributes(true, false); + private static final List> PROCESS_DURATION_ATTRIBUTES = + buildAttributes(true, false); + + private static List> buildAttributes( + boolean includeConsumerAttributes, boolean includeOperationType) { + List> attributes = new ArrayList<>(); + attributes.add(MESSAGING_OPERATION_NAME); + attributes.add(MESSAGING_SYSTEM); + attributes.add(ERROR_TYPE); + if (includeConsumerAttributes) { + attributes.add(MESSAGING_CONSUMER_GROUP_NAME); + } + attributes.add(MESSAGING_DESTINATION_NAME); + if (includeConsumerAttributes) { + attributes.add(MESSAGING_DESTINATION_SUBSCRIPTION_NAME); + } + attributes.add(MESSAGING_DESTINATION_TEMPLATE); + if (includeOperationType) { + attributes.add(MESSAGING_OPERATION_TYPE); + } + attributes.add(SERVER_ADDRESS); + attributes.add(MESSAGING_DESTINATION_PARTITION_ID); + attributes.add(SERVER_PORT); + return unmodifiableList(attributes); + } + + static Attributes filterAttributes(Attributes attributes) { + if (attributes.get(MESSAGING_DESTINATION_TEMPLATE) == null + && !Boolean.TRUE.equals(attributes.get(MESSAGING_DESTINATION_ANONYMOUS)) + && !Boolean.TRUE.equals(attributes.get(MESSAGING_DESTINATION_TEMPORARY))) { + return attributes; + } + return attributes.toBuilder().remove(MESSAGING_DESTINATION_NAME).build(); + } + + static void applyOldDurationAdvice(DoubleHistogramBuilder builder) { + if (!(builder instanceof ExtendedDoubleHistogramBuilder)) { + return; + } + ((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(OLD_ATTRIBUTES); + } + + static void applyClientOperationDurationAdvice(DoubleHistogramBuilder builder) { if (!(builder instanceof ExtendedDoubleHistogramBuilder)) { return; } - ((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(MESSAGING_ATTRIBUTES); + ((ExtendedDoubleHistogramBuilder) builder) + .setAttributesAdvice(CLIENT_OPERATION_DURATION_ATTRIBUTES); } - static void applyReceiveDurationAdvice(DoubleHistogramBuilder builder) { + static void applyProcessDurationAdvice(DoubleHistogramBuilder builder) { if (!(builder instanceof ExtendedDoubleHistogramBuilder)) { return; } - ((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(MESSAGING_ATTRIBUTES); + ((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(PROCESS_DURATION_ATTRIBUTES); + } + + static void applyOldMessagesAdvice(LongCounterBuilder builder) { + if (!(builder instanceof ExtendedLongCounterBuilder)) { + return; + } + ((ExtendedLongCounterBuilder) builder).setAttributesAdvice(OLD_ATTRIBUTES); + } + + static void applySentMessagesAdvice(LongCounterBuilder builder) { + if (!(builder instanceof ExtendedLongCounterBuilder)) { + return; + } + ((ExtendedLongCounterBuilder) builder).setAttributesAdvice(SENT_MESSAGES_ATTRIBUTES); } - static void applyReceiveMessagesAdvice(LongCounterBuilder builder) { + static void applyConsumedMessagesAdvice(LongCounterBuilder builder) { if (!(builder instanceof ExtendedLongCounterBuilder)) { return; } - ((ExtendedLongCounterBuilder) builder).setAttributesAdvice(MESSAGING_ATTRIBUTES); + ((ExtendedLongCounterBuilder) builder).setAttributesAdvice(CONSUMED_MESSAGES_ATTRIBUTES); } private MessagingMetricsAdvice() {} diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingOperation.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingOperation.java new file mode 100644 index 000000000000..3c1d763d9261 --- /dev/null +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingOperation.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; + +import static java.util.Objects.requireNonNull; + +import javax.annotation.Nullable; + +final class MessagingOperation { + + static MessagingOperation create(MessageOperation operation) { + return create(operation, operation.operationName()); + } + + static MessagingOperation create(MessageOperation operation, String name) { + return new MessagingOperation(operation, name); + } + + @Nullable + static MessagingOperation createNullable(@Nullable MessageOperation operation) { + return operation == null ? null : create(operation, operation.operationName()); + } + + private final MessageOperation operation; + private final String name; + + private MessagingOperation(MessageOperation operation, String name) { + this.operation = requireNonNull(operation, "operation"); + this.name = requireNonNull(name, "name"); + } + + MessageOperation operation() { + return operation; + } + + String name() { + return name; + } + + String type() { + switch (operation) { + case PUBLISH: + return "send"; + case RECEIVE: + return "receive"; + case PROCESS: + return "process"; + } + throw new IllegalStateException("Can't possibly happen"); + } +} diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProcessMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProcessMetrics.java new file mode 100644 index 000000000000..287741bea625 --- /dev/null +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProcessMetrics.java @@ -0,0 +1,97 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; + +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; +import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.logging.Level.FINE; + +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.DoubleHistogramBuilder; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextKey; +import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; +import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; +import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +/** + * {@link OperationListener} which keeps track of message + * processing metrics. + */ +public final class MessagingProcessMetrics implements OperationListener { + private static final double NANOS_PER_S = SECONDS.toNanos(1); + + private static final ContextKey MESSAGING_PROCESS_METRICS_STATE = + ContextKey.named("messaging-process-metrics-state"); + private static final Logger logger = Logger.getLogger(MessagingProcessMetrics.class.getName()); + + @Nullable private final DoubleHistogram processDurationHistogram; + + private MessagingProcessMetrics(Meter meter) { + processDurationHistogram = emitStableMessagingSemconv() ? buildProcessDuration(meter) : null; + } + + public static OperationMetrics get() { + return OperationMetricsUtil.create("messaging process", MessagingProcessMetrics::new); + } + + @Override + @CanIgnoreReturnValue + public Context onStart(Context context, Attributes startAttributes, long startNanos) { + if (processDurationHistogram == null) { + return context; + } + return context.with( + MESSAGING_PROCESS_METRICS_STATE, + new AutoValue_MessagingProcessMetrics_State(startAttributes, startNanos)); + } + + @Override + public void onEnd(Context context, Attributes endAttributes, long endNanos) { + if (processDurationHistogram == null) { + return; + } + State state = context.get(MESSAGING_PROCESS_METRICS_STATE); + if (state == null) { + logger.log( + FINE, + "No state present when ending context {0}. Cannot record messaging process metrics.", + context); + return; + } + + Attributes attributes = state.startAttributes().toBuilder().putAll(endAttributes).build(); + processDurationHistogram.record( + (endNanos - state.startTimeNanos()) / NANOS_PER_S, + MessagingMetricsAdvice.filterAttributes(attributes), + context); + } + + private static DoubleHistogram buildProcessDuration(Meter meter) { + DoubleHistogramBuilder builder = + meter + .histogramBuilder("messaging.process.duration") + .setDescription("Duration of processing operation.") + .setExplicitBucketBoundariesAdvice(MessagingMetricsAdvice.DURATION_SECONDS_BUCKETS) + .setUnit("s"); + MessagingMetricsAdvice.applyProcessDurationAdvice(builder); + return builder.build(); + } + + @AutoValue + abstract static class State { + abstract Attributes startAttributes(); + + abstract long startTimeNanos(); + } +} diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProducerMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProducerMetrics.java index ee79fd0c224e..8368ad7320ed 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProducerMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProducerMetrics.java @@ -5,14 +5,19 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldMessagingSemconv; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.logging.Level.FINE; import com.google.auto.value.AutoValue; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.DoubleHistogram; import io.opentelemetry.api.metrics.DoubleHistogramBuilder; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.LongCounterBuilder; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.context.Context; import io.opentelemetry.context.ContextKey; @@ -20,30 +25,32 @@ import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil; import java.util.logging.Logger; +import javax.annotation.Nullable; /** * {@link OperationListener} which keeps track of Producer + * href="https://github.com/open-telemetry/semantic-conventions/blob/v1.43.0/docs/messaging/messaging-metrics.md#producer-metrics">producer * metrics. */ public final class MessagingProducerMetrics implements OperationListener { private static final double NANOS_PER_S = SECONDS.toNanos(1); + // copied from MessagingIncubatingAttributes + private static final AttributeKey MESSAGING_BATCH_MESSAGE_COUNT = + AttributeKey.longKey("messaging.batch.message_count"); private static final ContextKey MESSAGING_PRODUCER_METRICS_STATE = ContextKey.named("messaging-producer-metrics-state"); private static final Logger logger = Logger.getLogger(MessagingProducerMetrics.class.getName()); - private final DoubleHistogram publishDurationHistogram; + @Nullable private final DoubleHistogram publishDurationHistogram; + @Nullable private final DoubleHistogram clientOperationDurationHistogram; + @Nullable private final LongCounter sentMessagesCounter; private MessagingProducerMetrics(Meter meter) { - DoubleHistogramBuilder durationBuilder = - meter - .histogramBuilder("messaging.publish.duration") - .setDescription("Measures the duration of publish operation.") - .setExplicitBucketBoundariesAdvice(MessagingMetricsAdvice.DURATION_SECONDS_BUCKETS) - .setUnit("s"); - MessagingMetricsAdvice.applyPublishDurationAdvice(durationBuilder); - publishDurationHistogram = durationBuilder.build(); + publishDurationHistogram = emitOldMessagingSemconv() ? buildPublishDuration(meter) : null; + clientOperationDurationHistogram = + emitStableMessagingSemconv() ? buildClientOperationDuration(meter) : null; + sentMessagesCounter = emitStableMessagingSemconv() ? buildSentMessages(meter) : null; } public static OperationMetrics get() { @@ -70,9 +77,56 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { } Attributes attributes = state.startAttributes().toBuilder().putAll(endAttributes).build(); + double duration = (endNanos - state.startTimeNanos()) / NANOS_PER_S; + + if (publishDurationHistogram != null) { + publishDurationHistogram.record(duration, attributes, context); + } + Attributes filteredAttributes = + clientOperationDurationHistogram != null || sentMessagesCounter != null + ? MessagingMetricsAdvice.filterAttributes(attributes) + : attributes; + if (clientOperationDurationHistogram != null) { + clientOperationDurationHistogram.record(duration, filteredAttributes, context); + } + if (sentMessagesCounter != null) { + Long batchMessageCount = attributes.get(MESSAGING_BATCH_MESSAGE_COUNT); + sentMessagesCounter.add( + batchMessageCount == null ? 1 : batchMessageCount, filteredAttributes, context); + } + } - publishDurationHistogram.record( - (endNanos - state.startTimeNanos()) / NANOS_PER_S, attributes, context); + private static DoubleHistogram buildPublishDuration(Meter meter) { + DoubleHistogramBuilder builder = + meter + .histogramBuilder("messaging.publish.duration") + .setDescription("Measures the duration of publish operation.") + .setExplicitBucketBoundariesAdvice(MessagingMetricsAdvice.DURATION_SECONDS_BUCKETS) + .setUnit("s"); + MessagingMetricsAdvice.applyOldDurationAdvice(builder); + return builder.build(); + } + + private static DoubleHistogram buildClientOperationDuration(Meter meter) { + DoubleHistogramBuilder builder = + meter + .histogramBuilder("messaging.client.operation.duration") + .setDescription( + "Duration of messaging operation initiated by a producer or consumer client.") + .setExplicitBucketBoundariesAdvice(MessagingMetricsAdvice.DURATION_SECONDS_BUCKETS) + .setUnit("s"); + MessagingMetricsAdvice.applyClientOperationDurationAdvice(builder); + return builder.build(); + } + + private static LongCounter buildSentMessages(Meter meter) { + LongCounterBuilder builder = + meter + .counterBuilder("messaging.client.sent.messages") + .setDescription("Number of messages producer attempted to send to the broker.") + .setUnit("{message}"); + MessagingMetricsAdvice.applySentMessagesAdvice(builder); + return builder.build(); } @AutoValue diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractor.java new file mode 100644 index 000000000000..5372abd194e1 --- /dev/null +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractor.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; + +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; + +/** Selects messaging span kinds according to the configured semantic convention version. */ +public final class MessagingSpanKindExtractor { + + /** + * Returns a span kind extractor following the v1.43 + * messaging span kind conventions when latest messaging conventions are enabled. + */ + public static SpanKindExtractor create(MessageOperation operation) { + SpanKind spanKind; + switch (operation) { + case PUBLISH: + spanKind = SpanKind.PRODUCER; + break; + case RECEIVE: + spanKind = emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER; + break; + case PROCESS: + spanKind = SpanKind.CONSUMER; + break; + default: + throw new IllegalStateException("Can't possibly happen"); + } + SpanKind result = spanKind; + return request -> result; + } + + private MessagingSpanKindExtractor() {} +} diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractor.java index 624d3b8e00bf..ec6f5fa4e56c 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractor.java @@ -5,14 +5,16 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; + import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; public final class MessagingSpanNameExtractor implements SpanNameExtractor { /** * Returns a {@link SpanNameExtractor} that constructs the span name according to - * messaging semantic conventions: {@code }. + * href="https://github.com/open-telemetry/semantic-conventions/blob/v1.43.0/docs/messaging/messaging-spans.md#span-name"> + * messaging semantic conventions. * * @see MessagingAttributesGetter#getDestination(Object) used to extract {@code }. @@ -20,20 +22,39 @@ public final class MessagingSpanNameExtractor implements SpanNameExtrac */ public static SpanNameExtractor create( MessagingAttributesGetter getter, MessageOperation operation) { - return new MessagingSpanNameExtractor<>(getter, operation); + return new MessagingSpanNameExtractor<>(getter, MessagingOperation.create(operation)); + } + + /** Returns a {@link SpanNameExtractor} with a system-specific v1.43 operation name. */ + public static SpanNameExtractor create( + MessagingAttributesGetter getter, + MessageOperation operation, + String operationName) { + return new MessagingSpanNameExtractor<>( + getter, MessagingOperation.create(operation, operationName)); } private final MessagingAttributesGetter getter; - private final MessageOperation operation; + private final MessagingOperation operation; private MessagingSpanNameExtractor( - MessagingAttributesGetter getter, MessageOperation operation) { + MessagingAttributesGetter getter, MessagingOperation operation) { this.getter = getter; this.operation = operation; } @Override public String extract(REQUEST request) { + if (emitStableMessagingSemconv()) { + String destinationName = getter.getDestinationTemplate(request); + if (destinationName == null + && !getter.isTemporaryDestination(request) + && !getter.isAnonymousDestination(request)) { + destinationName = getter.getDestination(request); + } + return destinationName == null ? operation.name() : operation.name() + " " + destinationName; + } + String destinationName = getter.isTemporaryDestination(request) ? MessagingAttributesExtractor.TEMP_DESTINATION_NAME @@ -42,6 +63,6 @@ public String extract(REQUEST request) { destinationName = "unknown"; } - return destinationName + " " + operation.operationName(); + return destinationName + " " + operation.operation().operationName(); } } diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorTest.java index a8d56f7e4f98..41dfc31371ec 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorTest.java @@ -6,7 +6,10 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldMessagingSemconv; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_BATCH_MESSAGE_COUNT; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_ANONYMOUS; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; @@ -17,9 +20,12 @@ import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_ENVELOPE_SIZE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_ID; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_TYPE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.entry; +import static org.junit.jupiter.params.provider.Arguments.argumentSet; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; @@ -48,7 +54,7 @@ void shouldExtractAllAvailableAttributes( boolean anonymous, String destination, MessageOperation operation, - String expectedDestination) { + String operationName) { // given Map request = new HashMap<>(); request.put("system", "myQueue"); @@ -69,7 +75,7 @@ void shouldExtractAllAvailableAttributes( request.put("batchMessageCount", "2"); AttributesExtractor, String> underTest = - MessagingAttributesExtractor.create(TestGetter.INSTANCE, operation); + MessagingAttributesExtractor.create(TestGetter.INSTANCE, operation, operationName); Context context = Context.root(); @@ -78,16 +84,22 @@ void shouldExtractAllAvailableAttributes( underTest.onStart(startAttributes, context, request); AttributesBuilder endAttributes = Attributes.builder(); - underTest.onEnd(endAttributes, context, request, "42", null); + underTest.onEnd(endAttributes, context, request, "42", new IllegalStateException()); // then List, Object>> expectedEntries = new ArrayList<>(); expectedEntries.add(entry(MESSAGING_SYSTEM, "myQueue")); - expectedEntries.add(entry(MESSAGING_DESTINATION_NAME, expectedDestination)); if (temporary) { expectedEntries.add(entry(MESSAGING_DESTINATION_TEMPORARY, true)); + if (emitStableMessagingSemconv()) { + expectedEntries.add(entry(MESSAGING_DESTINATION_NAME, destination)); + expectedEntries.add(entry(MESSAGING_DESTINATION_TEMPLATE, destination)); + } else { + expectedEntries.add(entry(MESSAGING_DESTINATION_NAME, "(temporary)")); + } } else { - expectedEntries.add(entry(MESSAGING_DESTINATION_TEMPLATE, expectedDestination)); + expectedEntries.add(entry(MESSAGING_DESTINATION_NAME, destination)); + expectedEntries.add(entry(MESSAGING_DESTINATION_TEMPLATE, destination)); } if (anonymous) { expectedEntries.add(entry(MESSAGING_DESTINATION_ANONYMOUS, true)); @@ -95,22 +107,46 @@ void shouldExtractAllAvailableAttributes( expectedEntries.add(entry(MESSAGING_MESSAGE_CONVERSATION_ID, "42")); expectedEntries.add(entry(MESSAGING_MESSAGE_BODY_SIZE, 100L)); expectedEntries.add(entry(MESSAGING_MESSAGE_ENVELOPE_SIZE, 120L)); - expectedEntries.add(entry(stringKey("messaging.client_id"), "43")); - expectedEntries.add(entry(MESSAGING_OPERATION, operation.operationName())); + if (emitOldMessagingSemconv()) { + expectedEntries.add(entry(stringKey("messaging.client_id"), "43")); + expectedEntries.add(entry(MESSAGING_OPERATION, operation.operationName())); + } + if (emitStableMessagingSemconv()) { + expectedEntries.add(entry(stringKey("messaging.client.id"), "43")); + expectedEntries.add(entry(MESSAGING_OPERATION_NAME, operationName)); + expectedEntries.add( + entry(MESSAGING_OPERATION_TYPE, MessagingOperation.create(operation).type())); + } @SuppressWarnings({"unchecked", "rawtypes"}) MapEntry, ?>[] expectedEntriesArr = expectedEntries.toArray(new MapEntry[0]); assertThat(startAttributes.build()).containsOnly(expectedEntriesArr); - assertThat(endAttributes.build()) - .containsOnly(entry(MESSAGING_MESSAGE_ID, "42"), entry(MESSAGING_BATCH_MESSAGE_COUNT, 2L)); + if (emitStableMessagingSemconv()) { + assertThat(endAttributes.build()) + .containsOnly( + entry(MESSAGING_MESSAGE_ID, "42"), + entry(MESSAGING_BATCH_MESSAGE_COUNT, 2L), + entry(ERROR_TYPE, IllegalStateException.class.getName())); + } else { + assertThat(endAttributes.build()) + .containsOnly( + entry(MESSAGING_MESSAGE_ID, "42"), entry(MESSAGING_BATCH_MESSAGE_COUNT, 2L)); + } } static Stream destinations() { return Stream.of( - Arguments.of(false, false, "destination", MessageOperation.RECEIVE, "destination"), - Arguments.of(true, true, null, MessageOperation.PROCESS, "(temporary)")); + argumentSet( + "regular destination", false, false, "destination", MessageOperation.RECEIVE, "poll"), + argumentSet( + "temporary anonymous destination", + true, + true, + "generated-destination", + MessageOperation.PROCESS, + "process")); } @Test diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingConsumerMetricsTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingConsumerMetricsTest.java new file mode 100644 index 000000000000..9d519ee02cbe --- /dev/null +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingConsumerMetricsTest.java @@ -0,0 +1,218 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; + +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldMessagingSemconv; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_BATCH_MESSAGE_COUNT; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_CONSUMER_GROUP_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_SUBSCRIPTION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_TEMPLATE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_TYPE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import java.util.Collection; +import org.junit.jupiter.api.Test; + +class MessagingConsumerMetricsTest { + + private static final double[] DURATION_BUCKETS = + MessagingMetricsAdvice.DURATION_SECONDS_BUCKETS.stream().mapToDouble(d -> d).toArray(); + + @Test + @SuppressWarnings("deprecation") // using deprecated semconv + void collectsMetricsAndCountsBatchOnce() { + InMemoryMetricReader metricReader = InMemoryMetricReader.createDelta(); + SdkMeterProvider meterProvider = + SdkMeterProvider.builder().registerMetricReader(metricReader).build(); + OperationListener listener = MessagingConsumerMetrics.get().create(meterProvider.get("test")); + + Attributes requestAttributes = + Attributes.builder() + .put(MESSAGING_SYSTEM, "pulsar") + .put(MESSAGING_DESTINATION_NAME, "topic") + .put(MESSAGING_DESTINATION_TEMPLATE, "topic-{id}") + .put(MESSAGING_OPERATION, emitOldMessagingSemconv() ? "receive" : null) + .put(MESSAGING_OPERATION_NAME, emitStableMessagingSemconv() ? "receive" : null) + .put(MESSAGING_OPERATION_TYPE, emitStableMessagingSemconv() ? "receive" : null) + .put(MESSAGING_CONSUMER_GROUP_NAME, "group") + .put(MESSAGING_DESTINATION_SUBSCRIPTION_NAME, "subscription") + .build(); + Attributes responseAttributes = + Attributes.builder() + .put(MESSAGING_BATCH_MESSAGE_COUNT, 3) + .put( + ERROR_TYPE, + emitStableMessagingSemconv() ? IllegalStateException.class.getName() : null) + .build(); + + Context context = listener.onStart(Context.root(), requestAttributes, nanos(100)); + listener.onEnd(context, responseAttributes, nanos(300)); + + Collection metrics = metricReader.collectAllMetrics(); + assertThat(metrics) + .hasSize((emitOldMessagingSemconv() ? 2 : 0) + (emitStableMessagingSemconv() ? 2 : 0)); + + if (emitOldMessagingSemconv()) { + assertThat(metrics) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("messaging.receive.duration") + .hasUnit("s") + .hasDescription("Measures the duration of receive operation.") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(0.2) + .hasBucketBoundaries(DURATION_BUCKETS) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_SYSTEM, "pulsar"), + equalTo(MESSAGING_DESTINATION_NAME, "topic"), + equalTo(MESSAGING_DESTINATION_TEMPLATE, "topic-{id}"), + equalTo(MESSAGING_OPERATION, "receive"), + equalTo( + ERROR_TYPE, + emitStableMessagingSemconv() + ? IllegalStateException.class.getName() + : null))))) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("messaging.receive.messages") + .hasUnit("{message}") + .hasDescription("Measures the number of received messages.") + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(3) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_SYSTEM, "pulsar"), + equalTo(MESSAGING_DESTINATION_NAME, "topic"), + equalTo(MESSAGING_DESTINATION_TEMPLATE, "topic-{id}"), + equalTo(MESSAGING_OPERATION, "receive"), + equalTo( + ERROR_TYPE, + emitStableMessagingSemconv() + ? IllegalStateException.class.getName() + : null))))); + } + if (emitStableMessagingSemconv()) { + assertThat(metrics) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("messaging.client.operation.duration") + .hasUnit("s") + .hasDescription( + "Duration of messaging operation initiated by a producer or consumer client.") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(0.2) + .hasBucketBoundaries(DURATION_BUCKETS) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION_NAME, "receive"), + equalTo(MESSAGING_SYSTEM, "pulsar"), + equalTo(MESSAGING_CONSUMER_GROUP_NAME, "group"), + equalTo(MESSAGING_DESTINATION_TEMPLATE, "topic-{id}"), + equalTo( + MESSAGING_DESTINATION_SUBSCRIPTION_NAME, + "subscription"), + equalTo( + ERROR_TYPE, + IllegalStateException.class.getName()), + equalTo(MESSAGING_OPERATION_TYPE, "receive"))))) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("messaging.client.consumed.messages") + .hasUnit("{message}") + .hasDescription("Number of messages that were delivered to the application.") + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(3) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION_NAME, "receive"), + equalTo(MESSAGING_SYSTEM, "pulsar"), + equalTo(MESSAGING_CONSUMER_GROUP_NAME, "group"), + equalTo(MESSAGING_DESTINATION_TEMPLATE, "topic-{id}"), + equalTo( + MESSAGING_DESTINATION_SUBSCRIPTION_NAME, + "subscription"), + equalTo( + ERROR_TYPE, + IllegalStateException.class.getName()))))); + } + } + + @Test + @SuppressWarnings("deprecation") // using deprecated semconv + void failedReceiveWithoutBatchCountCountsNoConsumedMessages() { + InMemoryMetricReader metricReader = InMemoryMetricReader.createDelta(); + SdkMeterProvider meterProvider = + SdkMeterProvider.builder().registerMetricReader(metricReader).build(); + OperationListener listener = MessagingConsumerMetrics.get().create(meterProvider.get("test")); + + Attributes requestAttributes = + Attributes.builder() + .put(MESSAGING_SYSTEM, "pulsar") + .put(MESSAGING_OPERATION, emitOldMessagingSemconv() ? "receive" : null) + .put(MESSAGING_OPERATION_NAME, emitStableMessagingSemconv() ? "receive" : null) + .build(); + Attributes responseAttributes = + Attributes.of(ERROR_TYPE, IllegalStateException.class.getName()); + + Context context = listener.onStart(Context.root(), requestAttributes, nanos(100)); + listener.onEnd(context, responseAttributes, nanos(300)); + + Collection metrics = metricReader.collectAllMetrics(); + if (emitOldMessagingSemconv()) { + assertThat(metrics) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("messaging.receive.messages") + .hasLongSumSatisfying( + sum -> sum.hasPointsSatisfying(point -> point.hasValue(1)))); + } + if (emitStableMessagingSemconv()) { + assertThat(metrics) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("messaging.client.consumed.messages") + .hasLongSumSatisfying( + sum -> sum.hasPointsSatisfying(point -> point.hasValue(0)))); + } + } + + private static long nanos(int millis) { + return MILLISECONDS.toNanos(millis); + } +} diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingMetricsAdviceTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingMetricsAdviceTest.java new file mode 100644 index 000000000000..d070e72b9998 --- /dev/null +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingMetricsAdviceTest.java @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_ANONYMOUS; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_TEMPLATE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_TEMPORARY; + +import io.opentelemetry.api.common.Attributes; +import org.junit.jupiter.api.Test; + +class MessagingMetricsAdviceTest { + + @Test + @SuppressWarnings("deprecation") // using deprecated semconv + void filtersHighCardinalityDestinationNames() { + Attributes lowCardinality = Attributes.of(MESSAGING_DESTINATION_NAME, "orders"); + assertThat(MessagingMetricsAdvice.filterAttributes(lowCardinality)).isSameAs(lowCardinality); + + assertThat( + MessagingMetricsAdvice.filterAttributes( + Attributes.builder() + .put(MESSAGING_DESTINATION_NAME, "orders-42") + .put(MESSAGING_DESTINATION_TEMPLATE, "orders-{id}") + .build())) + .isEqualTo(Attributes.of(MESSAGING_DESTINATION_TEMPLATE, "orders-{id}")); + assertThat( + MessagingMetricsAdvice.filterAttributes( + Attributes.builder() + .put(MESSAGING_DESTINATION_NAME, "tmp-42") + .put(MESSAGING_DESTINATION_TEMPORARY, true) + .build())) + .isEqualTo(Attributes.of(MESSAGING_DESTINATION_TEMPORARY, true)); + assertThat( + MessagingMetricsAdvice.filterAttributes( + Attributes.builder() + .put(MESSAGING_DESTINATION_NAME, "generated-42") + .put(MESSAGING_DESTINATION_ANONYMOUS, true) + .build())) + .isEqualTo(Attributes.of(MESSAGING_DESTINATION_ANONYMOUS, true)); + } +} diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProcessMetricsTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProcessMetricsTest.java new file mode 100644 index 000000000000..1f2d32d82b71 --- /dev/null +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProcessMetricsTest.java @@ -0,0 +1,93 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; + +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldMessagingSemconv; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_TEMPLATE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_TYPE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import org.junit.jupiter.api.Test; + +class MessagingProcessMetricsTest { + + private static final double[] DURATION_BUCKETS = + MessagingMetricsAdvice.DURATION_SECONDS_BUCKETS.stream().mapToDouble(d -> d).toArray(); + + @Test + @SuppressWarnings("deprecation") // using deprecated semconv + void emitsOnlyLatestProcessDuration() { + InMemoryMetricReader metricReader = InMemoryMetricReader.createDelta(); + SdkMeterProvider meterProvider = + SdkMeterProvider.builder().registerMetricReader(metricReader).build(); + OperationListener listener = MessagingProcessMetrics.get().create(meterProvider.get("test")); + + Attributes attributes = + Attributes.builder() + .put(MESSAGING_SYSTEM, "pulsar") + .put(MESSAGING_DESTINATION_NAME, "topic") + .put(MESSAGING_DESTINATION_TEMPLATE, "topic-{id}") + .put(MESSAGING_OPERATION, emitOldMessagingSemconv() ? "process" : null) + .put(MESSAGING_OPERATION_NAME, emitStableMessagingSemconv() ? "process" : null) + .put(MESSAGING_OPERATION_TYPE, emitStableMessagingSemconv() ? "process" : null) + .build(); + + Context root = Context.root(); + Context context = listener.onStart(root, attributes, nanos(100)); + Attributes endAttributes = + Attributes.builder() + .put( + ERROR_TYPE, + emitStableMessagingSemconv() ? IllegalStateException.class.getName() : null) + .build(); + listener.onEnd(context, endAttributes, nanos(350)); + + if (!emitStableMessagingSemconv()) { + assertThat(context).isSameAs(root); + assertThat(metricReader.collectAllMetrics()).isEmpty(); + return; + } + + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactly( + metric -> + assertThat(metric) + .hasName("messaging.process.duration") + .hasUnit("s") + .hasDescription("Duration of processing operation.") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(0.25) + .hasBucketBoundaries(DURATION_BUCKETS) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION_NAME, "process"), + equalTo(MESSAGING_SYSTEM, "pulsar"), + equalTo(MESSAGING_DESTINATION_TEMPLATE, "topic-{id}"), + equalTo( + ERROR_TYPE, + IllegalStateException.class.getName()))))); + } + + private static long nanos(int millis) { + return MILLISECONDS.toNanos(millis); + } +} diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProducerMetricsTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProducerMetricsTest.java index f155a613b349..946eec949c84 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProducerMetricsTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProducerMetricsTest.java @@ -5,26 +5,30 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldMessagingSemconv; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_BATCH_MESSAGE_COUNT; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_PARTITION_ID; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_ID; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_TEMPLATE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_TYPE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; import static java.util.concurrent.TimeUnit.MILLISECONDS; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.TraceFlags; -import io.opentelemetry.api.trace.TraceState; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import java.util.Collection; import org.junit.jupiter.api.Test; class MessagingProducerMetricsTest { @@ -32,89 +36,121 @@ class MessagingProducerMetricsTest { private static final double[] DURATION_BUCKETS = MessagingMetricsAdvice.DURATION_SECONDS_BUCKETS.stream().mapToDouble(d -> d).toArray(); - @SuppressWarnings("deprecation") // using deprecated semconv @Test + @SuppressWarnings("deprecation") // using deprecated semconv void collectsMetrics() { - InMemoryMetricReader metricReader = InMemoryMetricReader.create(); + InMemoryMetricReader metricReader = InMemoryMetricReader.createDelta(); SdkMeterProvider meterProvider = SdkMeterProvider.builder().registerMetricReader(metricReader).build(); - OperationListener listener = MessagingProducerMetrics.get().create(meterProvider.get("test")); Attributes requestAttributes = Attributes.builder() .put(MESSAGING_SYSTEM, "pulsar") - .put(MESSAGING_DESTINATION_NAME, "persistent://public/default/topic") - .put(MESSAGING_OPERATION, "publish") - .put(SERVER_PORT, 6650) + .put(MESSAGING_DESTINATION_NAME, "topic") + .put(MESSAGING_DESTINATION_TEMPLATE, "topic-{id}") + .put(MESSAGING_OPERATION, emitOldMessagingSemconv() ? "publish" : null) + .put(MESSAGING_OPERATION_NAME, emitStableMessagingSemconv() ? "send" : null) + .put(MESSAGING_OPERATION_TYPE, emitStableMessagingSemconv() ? "send" : null) .put(SERVER_ADDRESS, "localhost") + .put(SERVER_PORT, 6650) .build(); - Attributes responseAttributes = Attributes.builder() - .put(MESSAGING_MESSAGE_ID, "1:1:0:0") .put(MESSAGING_DESTINATION_PARTITION_ID, "1") + .put(MESSAGING_BATCH_MESSAGE_COUNT, 2) + .put( + ERROR_TYPE, + emitStableMessagingSemconv() ? IllegalStateException.class.getName() : null) .build(); - Context parent = - Context.root() - .with( - Span.wrap( - SpanContext.create( - "ff01020304050600ff0a0b0c0d0e0f00", - "090a0b0c0d0e0f00", - TraceFlags.getSampled(), - TraceState.getDefault()))); - - Context context1 = listener.onStart(parent, requestAttributes, nanos(100)); - - assertThat(metricReader.collectAllMetrics()).isEmpty(); - - Context context2 = listener.onStart(Context.root(), requestAttributes, nanos(150)); - - assertThat(metricReader.collectAllMetrics()).isEmpty(); - - listener.onEnd(context1, responseAttributes, nanos(250)); - - assertThat(metricReader.collectAllMetrics()) - .satisfiesExactlyInAnyOrder( - metric -> - assertThat(metric) - .hasName("messaging.publish.duration") - .hasUnit("s") - .hasDescription("Measures the duration of publish operation.") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying( - point -> - point - .hasSum(0.15 /* seconds */) - .hasAttributesSatisfying( - equalTo(MESSAGING_SYSTEM, "pulsar"), - equalTo(MESSAGING_DESTINATION_PARTITION_ID, "1"), - equalTo( - MESSAGING_DESTINATION_NAME, - "persistent://public/default/topic"), - equalTo(SERVER_PORT, 6650), - equalTo(SERVER_ADDRESS, "localhost")) - .hasExemplarsSatisfying( - exemplar -> - exemplar - .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") - .hasSpanId("090a0b0c0d0e0f00")) - .hasBucketBoundaries(DURATION_BUCKETS)))); - - listener.onEnd(context2, responseAttributes, nanos(300)); - - assertThat(metricReader.collectAllMetrics()) - .satisfiesExactlyInAnyOrder( - metric -> - assertThat(metric) - .hasName("messaging.publish.duration") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying( - point -> point.hasSum(0.3 /* seconds */)))); + Context context = listener.onStart(Context.root(), requestAttributes, nanos(100)); + listener.onEnd(context, responseAttributes, nanos(250)); + + Collection metrics = metricReader.collectAllMetrics(); + assertThat(metrics) + .hasSize((emitOldMessagingSemconv() ? 1 : 0) + (emitStableMessagingSemconv() ? 2 : 0)); + + if (emitOldMessagingSemconv()) { + assertThat(metrics) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("messaging.publish.duration") + .hasUnit("s") + .hasDescription("Measures the duration of publish operation.") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(0.15) + .hasBucketBoundaries(DURATION_BUCKETS) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_SYSTEM, "pulsar"), + equalTo(MESSAGING_DESTINATION_NAME, "topic"), + equalTo(MESSAGING_OPERATION, "publish"), + equalTo(MESSAGING_DESTINATION_PARTITION_ID, "1"), + equalTo(MESSAGING_DESTINATION_TEMPLATE, "topic-{id}"), + equalTo( + ERROR_TYPE, + emitStableMessagingSemconv() + ? IllegalStateException.class.getName() + : null), + equalTo(SERVER_PORT, 6650), + equalTo(SERVER_ADDRESS, "localhost"))))); + } + if (emitStableMessagingSemconv()) { + assertThat(metrics) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("messaging.client.operation.duration") + .hasUnit("s") + .hasDescription( + "Duration of messaging operation initiated by a producer or consumer client.") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(0.15) + .hasBucketBoundaries(DURATION_BUCKETS) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION_NAME, "send"), + equalTo(MESSAGING_SYSTEM, "pulsar"), + equalTo(MESSAGING_DESTINATION_TEMPLATE, "topic-{id}"), + equalTo(MESSAGING_OPERATION_TYPE, "send"), + equalTo( + ERROR_TYPE, + IllegalStateException.class.getName()), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(MESSAGING_DESTINATION_PARTITION_ID, "1"), + equalTo(SERVER_PORT, 6650))))) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("messaging.client.sent.messages") + .hasUnit("{message}") + .hasDescription( + "Number of messages producer attempted to send to the broker.") + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(2) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION_NAME, "send"), + equalTo(MESSAGING_SYSTEM, "pulsar"), + equalTo(MESSAGING_DESTINATION_TEMPLATE, "topic-{id}"), + equalTo( + ERROR_TYPE, + IllegalStateException.class.getName()), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(MESSAGING_DESTINATION_PARTITION_ID, "1"), + equalTo(SERVER_PORT, 6650))))); + } } private static long nanos(int millis) { diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractorTest.java new file mode 100644 index 000000000000..458a832a2f70 --- /dev/null +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; + +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.argumentSet; + +import io.opentelemetry.api.trace.SpanKind; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class MessagingSpanKindExtractorTest { + + @ParameterizedTest + @MethodSource("spanKinds") + void extractsSpanKind(MessageOperation operation, SpanKind oldKind, SpanKind kind) { + SpanKind actualKind = MessagingSpanKindExtractor.create(operation).extract(new Object()); + + assertThat(actualKind).isEqualTo(emitStableMessagingSemconv() ? kind : oldKind); + } + + private static Stream spanKinds() { + return Stream.of( + argumentSet("publish", MessageOperation.PUBLISH, SpanKind.PRODUCER, SpanKind.PRODUCER), + argumentSet("receive", MessageOperation.RECEIVE, SpanKind.CONSUMER, SpanKind.CLIENT), + argumentSet("process", MessageOperation.PROCESS, SpanKind.CONSUMER, SpanKind.CONSUMER)); + } +} diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractorTest.java index 2d658df59f87..a98517b68f3c 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractorTest.java @@ -5,7 +5,9 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.argumentSet; import static org.mockito.Mockito.when; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; @@ -26,32 +28,96 @@ class MessagingSpanNameExtractorTest { @MethodSource("spanNameParams") void shouldExtractSpanName( boolean isTemporaryQueue, + boolean isAnonymousQueue, String destinationName, + String destinationTemplate, MessageOperation operation, - String expectedSpanName) { + String operationName, + String oldSpanName, + String spanName) { // given Message message = new Message(); - if (isTemporaryQueue) { - when(getter.isTemporaryDestination(message)).thenReturn(true); + if (emitStableMessagingSemconv()) { + when(getter.getDestinationTemplate(message)).thenReturn(destinationTemplate); + if (destinationTemplate == null) { + when(getter.isTemporaryDestination(message)).thenReturn(isTemporaryQueue); + if (!isTemporaryQueue) { + when(getter.isAnonymousDestination(message)).thenReturn(isAnonymousQueue); + if (!isAnonymousQueue) { + when(getter.getDestination(message)).thenReturn(destinationName); + } + } + } } else { - when(getter.getDestination(message)).thenReturn(destinationName); + when(getter.isTemporaryDestination(message)).thenReturn(isTemporaryQueue); + if (!isTemporaryQueue) { + when(getter.getDestination(message)).thenReturn(destinationName); + } } - SpanNameExtractor underTest = MessagingSpanNameExtractor.create(getter, operation); + SpanNameExtractor underTest = + MessagingSpanNameExtractor.create(getter, operation, operationName); // when - String spanName = underTest.extract(message); + String actualSpanName = underTest.extract(message); // then - assertThat(spanName).isEqualTo(expectedSpanName); + assertThat(actualSpanName).isEqualTo(emitStableMessagingSemconv() ? spanName : oldSpanName); } static Stream spanNameParams() { return Stream.of( - Arguments.of(false, "destination", MessageOperation.PUBLISH, "destination publish"), - Arguments.of(true, null, MessageOperation.PROCESS, "(temporary) process"), - Arguments.of(false, null, MessageOperation.RECEIVE, "unknown receive")); + argumentSet( + "operation name override", + false, + false, + "destination", + null, + MessageOperation.PUBLISH, + "send", + "destination publish", + "send destination"), + argumentSet( + "temporary destination", + true, + false, + "generated", + null, + MessageOperation.PROCESS, + "process", + "(temporary) process", + "process"), + argumentSet( + "missing destination", + false, + false, + null, + null, + MessageOperation.RECEIVE, + "receive", + "unknown receive", + "receive"), + argumentSet( + "destination template", + false, + false, + "customer-42", + "customer-{id}", + MessageOperation.PUBLISH, + "send", + "customer-42 publish", + "send customer-{id}"), + argumentSet( + "anonymous destination", + false, + true, + "generated", + null, + MessageOperation.PROCESS, + "process", + "generated process", + "process")); } static class Message {} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SemconvStability.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SemconvStability.java index 8efdbfe4ee0b..bd6935a2817d 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SemconvStability.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SemconvStability.java @@ -173,6 +173,10 @@ public static boolean emitOldMessagingSemconv() { return emitOldMessagingSemconv; } + /** + * Returns whether the selected v1 experimental messaging semantic conventions should be emitted. + * The method name is retained for 2.x compatibility with other semconv selectors. + */ public static boolean emitStableMessagingSemconv() { return emitStableMessagingSemconv; } diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/SemconvStabilityTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/SemconvStabilityTest.java index 32e316457df2..bc1d672f08bd 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/SemconvStabilityTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/SemconvStabilityTest.java @@ -9,6 +9,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.emptySet; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.argumentSet; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import io.opentelemetry.common.ComponentLoader; @@ -22,6 +23,8 @@ import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; class SemconvStabilityTest { @@ -379,6 +382,69 @@ void previewFallbackAppliesToPreviewDomains(boolean v3Preview) { assertThat(messaging).isEqualTo(SemconvMode.V1_EXPERIMENTAL); } + @ParameterizedTest + @MethodSource("messagingSelectionModes") + void messagingSelectionMatrix( + boolean v3Preview, Set stableOptIn, Set preview, SemconvMode expectedMode) { + SemconvMode messaging = + new SemconvSelectionResolver(general(), v3Preview, stableOptIn, preview).messaging(); + + assertThat(messaging).isEqualTo(expectedMode); + } + + private static List messagingSelectionModes() { + return asList( + argumentSet("legacy default", false, noStableOptIn(), noPreview(), SemconvMode.V0_STABLE), + argumentSet( + "legacy opt-in property", + false, + stableOptIn("messaging"), + noPreview(), + SemconvMode.V1_EXPERIMENTAL), + argumentSet( + "preview property", + false, + noStableOptIn(), + preview("messaging"), + SemconvMode.V1_EXPERIMENTAL), + argumentSet( + "legacy opt-in dual emit", + false, + stableOptIn("messaging/dup"), + noPreview(), + SemconvMode.V1_EXPERIMENTAL.withDualEmit()), + argumentSet( + "preview dual emit", + false, + noStableOptIn(), + preview("messaging/dup"), + SemconvMode.V1_EXPERIMENTAL.withDualEmit()), + argumentSet( + "v3 activation remains staged", + true, + noStableOptIn(), + noPreview(), + SemconvMode.V0_STABLE), + argumentSet( + "v3 ignores legacy opt-in property", + true, + stableOptIn("messaging"), + noPreview(), + SemconvMode.V0_STABLE), + argumentSet( + "v3 ignores legacy opt-in dual emit", + true, + stableOptIn("messaging/dup"), + noPreview(), + SemconvMode.V0_STABLE), + argumentSet( + "v3 with explicit preview", + true, + noStableOptIn(), + preview("messaging"), + SemconvMode.V1_EXPERIMENTAL)); + } + @SafeVarargs private static DeclarativeConfigProperties general(Entry... entries) { Map result = new HashMap(); diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java index fbcb2b086025..ea8055f306e1 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.kafkaclients.v0_11; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanKind; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; @@ -82,7 +83,10 @@ void testKafkaProducerAndConsumerSpan(boolean testHeaders) throws Exception { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span -> - span.hasName(SHARED_TOPIC + " publish") + span.hasName( + emitStableMessagingSemconv() + ? "publish " + SHARED_TOPIC + : SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes("10", greeting, testHeaders)), @@ -95,12 +99,18 @@ void testKafkaProducerAndConsumerSpan(boolean testHeaders) throws Exception { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " receive") + span.hasName( + emitStableMessagingSemconv() + ? "receive " + SHARED_TOPIC + : SHARED_TOPIC + " receive") .hasKind(SpanKind.CONSUMER) .hasNoParent() .hasAttributesSatisfyingExactly(receiveAttributes(testHeaders)), span -> - span.hasName(SHARED_TOPIC + " process") + span.hasName( + emitStableMessagingSemconv() + ? "process " + SHARED_TOPIC + : SHARED_TOPIC + " process") .hasKind(SpanKind.CONSUMER) .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) .hasParent(trace.getSpan(0)) @@ -129,7 +139,10 @@ void testPassThroughTombstone() throws Exception { trace -> { trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " publish") + span.hasName( + emitStableMessagingSemconv() + ? "publish " + SHARED_TOPIC + : SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasNoParent() .hasAttributesSatisfyingExactly(sendAttributes(null, null, false))); @@ -138,12 +151,18 @@ void testPassThroughTombstone() throws Exception { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " receive") + span.hasName( + emitStableMessagingSemconv() + ? "receive " + SHARED_TOPIC + : SHARED_TOPIC + " receive") .hasKind(SpanKind.CONSUMER) .hasNoParent() .hasAttributesSatisfyingExactly(receiveAttributes(false)), span -> - span.hasName(SHARED_TOPIC + " process") + span.hasName( + emitStableMessagingSemconv() + ? "process " + SHARED_TOPIC + : SHARED_TOPIC + " process") .hasKind(SpanKind.CONSUMER) .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) .hasParent(trace.getSpan(0)) @@ -188,7 +207,10 @@ void testRecordsWithTopicPartitionKafkaConsume(boolean testListIterator) throws trace -> { trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " publish") + span.hasName( + emitStableMessagingSemconv() + ? "publish " + SHARED_TOPIC + : SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasNoParent() .hasAttributesSatisfyingExactly(sendAttributes(null, greeting, false))); @@ -197,12 +219,18 @@ void testRecordsWithTopicPartitionKafkaConsume(boolean testListIterator) throws trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " receive") + span.hasName( + emitStableMessagingSemconv() + ? "receive " + SHARED_TOPIC + : SHARED_TOPIC + " receive") .hasKind(SpanKind.CONSUMER) .hasNoParent() .hasAttributesSatisfyingExactly(receiveAttributes(false)), span -> - span.hasName(SHARED_TOPIC + " process") + span.hasName( + emitStableMessagingSemconv() + ? "process " + SHARED_TOPIC + : SHARED_TOPIC + " process") .hasKind(SpanKind.CONSUMER) .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) .hasParent(trace.getSpan(0)) @@ -253,7 +281,10 @@ void testKafkaHeaderNull() throws Exception { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span -> - span.hasName(SHARED_TOPIC + " publish") + span.hasName( + emitStableMessagingSemconv() + ? "publish " + SHARED_TOPIC + : SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes("10", greeting, false)), @@ -266,12 +297,18 @@ void testKafkaHeaderNull() throws Exception { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " receive") + span.hasName( + emitStableMessagingSemconv() + ? "receive " + SHARED_TOPIC + : SHARED_TOPIC + " receive") .hasKind(SpanKind.CONSUMER) .hasNoParent() .hasAttributesSatisfyingExactly(receiveAttributes(false)), span -> - span.hasName(SHARED_TOPIC + " process") + span.hasName( + emitStableMessagingSemconv() + ? "process " + SHARED_TOPIC + : SHARED_TOPIC + " process") .hasKind(SpanKind.CONSUMER) .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) .hasParent(trace.getSpan(0)) diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/testing/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaClientBaseTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/testing/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaClientBaseTest.java index 8dde894d4afd..32ffc0518ad4 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/testing/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaClientBaseTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/testing/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaClientBaseTest.java @@ -23,6 +23,8 @@ import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_OFFSET; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_TYPE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; @@ -74,8 +76,10 @@ public abstract class KafkaClientBaseTest { @RegisterExtension final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); protected static final String SHARED_TOPIC = "shared.topic"; - protected static final AttributeKey MESSAGING_CLIENT_ID = + protected static final AttributeKey MESSAGING_CLIENT_ID_OLD = AttributeKey.stringKey("messaging.client_id"); + private static final AttributeKey MESSAGING_CLIENT_ID = + AttributeKey.stringKey("messaging.client.id"); private KafkaContainer kafka; protected Producer producer; @@ -181,8 +185,12 @@ protected static List sendAttributes( asList( equalTo(MESSAGING_SYSTEM, "kafka"), equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(MESSAGING_OPERATION, "publish"), - satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("producer")), + equalTo(MESSAGING_OPERATION, emitOldMessagingSemconv() ? "publish" : null), + equalTo(MESSAGING_OPERATION_NAME, emitStableMessagingSemconv() ? "publish" : null), + equalTo(MESSAGING_OPERATION_TYPE, emitStableMessagingSemconv() ? "send" : null), + satisfies( + emitStableMessagingSemconv() ? MESSAGING_CLIENT_ID : MESSAGING_CLIENT_ID_OLD, + val -> val.startsWith("producer")), satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty))); if (emitOldMessagingSemconv()) { assertions.add(satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative)); @@ -215,8 +223,12 @@ protected static List receiveAttributes(boolean testHeaders) asList( equalTo(MESSAGING_SYSTEM, "kafka"), equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(MESSAGING_OPERATION, "receive"), - satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("consumer")), + equalTo(MESSAGING_OPERATION, emitOldMessagingSemconv() ? "receive" : null), + equalTo(MESSAGING_OPERATION_NAME, emitStableMessagingSemconv() ? "receive" : null), + equalTo(MESSAGING_OPERATION_TYPE, emitStableMessagingSemconv() ? "receive" : null), + satisfies( + emitStableMessagingSemconv() ? MESSAGING_CLIENT_ID : MESSAGING_CLIENT_ID_OLD, + val -> val.startsWith("consumer")), satisfies(MESSAGING_BATCH_MESSAGE_COUNT, AbstractLongAssert::isPositive))); // consumer group is not available in version 0.11 if (testLatestDeps()) { @@ -236,8 +248,12 @@ protected static List processAttributes( asList( equalTo(MESSAGING_SYSTEM, "kafka"), equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(MESSAGING_OPERATION, "process"), - satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("consumer")), + equalTo(MESSAGING_OPERATION, emitOldMessagingSemconv() ? "process" : null), + equalTo(MESSAGING_OPERATION_NAME, emitStableMessagingSemconv() ? "process" : null), + equalTo(MESSAGING_OPERATION_TYPE, emitStableMessagingSemconv() ? "process" : null), + satisfies( + emitStableMessagingSemconv() ? MESSAGING_CLIENT_ID : MESSAGING_CLIENT_ID_OLD, + val -> val.startsWith("consumer")), satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty))); if (EXPERIMENTAL_ATTRIBUTES) { assertions.add( diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java index a62c8e475f19..31d5f1a5b0e0 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java @@ -164,7 +164,7 @@ private static List publishAttributes(boolean experimental) equalTo(MESSAGING_SYSTEM, "kafka"), equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), equalTo(MESSAGING_OPERATION, "publish"), - satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("producer")), + satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("producer")), satisfies( stringKey("messaging.kafka.bootstrap.servers"), val -> { @@ -181,7 +181,7 @@ private static List receiveAttributes() { equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), equalTo(MESSAGING_OPERATION, "receive"), equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), - satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("consumer")), + satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("consumer")), equalTo(MESSAGING_BATCH_MESSAGE_COUNT, 1)); } @@ -195,7 +195,7 @@ private static List processAttributes(boolean experimental) satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty), satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative), equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), - satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("consumer")), + satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("consumer")), satisfies( longKey("kafka.record.queue_time_ms"), val -> { diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsSuppressReceiveSpansTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsSuppressReceiveSpansTest.java index 8555895bd51c..dd5082d33c26 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsSuppressReceiveSpansTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsSuppressReceiveSpansTest.java @@ -51,7 +51,7 @@ void assertTraces() { equalTo(MESSAGING_SYSTEM, "kafka"), equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), equalTo(MESSAGING_OPERATION, "publish"), - satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("producer"))), + satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("producer"))), span -> span.hasName(SHARED_TOPIC + " process") .hasKind(SpanKind.CONSUMER) @@ -67,7 +67,7 @@ void assertTraces() { satisfies( MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative), equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), - satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("consumer")), + satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("consumer")), equalTo(stringKey("test-baggage-key-1"), "test-baggage-value-1"), equalTo(stringKey("test-baggage-key-2"), "test-baggage-value-2")), span -> diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperSuppressReceiveSpansTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperSuppressReceiveSpansTest.java index a549ce1aa7f4..efb45c1dd6b5 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperSuppressReceiveSpansTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperSuppressReceiveSpansTest.java @@ -71,7 +71,7 @@ static List sendAttributes(boolean testHeaders, boolean test equalTo(MESSAGING_SYSTEM, "kafka"), equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), equalTo(MESSAGING_OPERATION, "publish"), - satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("producer")), + satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("producer")), satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty), satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative))); if (testHeaders) { @@ -98,7 +98,7 @@ static List processAttributes( satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty), satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative), equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), - satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("consumer")))); + satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("consumer")))); if (testHeaders) { assertions.add(equalTo(headerAttributeKey("Test-Message-Header"), singletonList("test"))); } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperTest.java index c9f784651554..45e22fcf8e1e 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperTest.java @@ -106,7 +106,7 @@ protected static List sendAttributes( equalTo(MESSAGING_SYSTEM, "kafka"), equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), equalTo(MESSAGING_OPERATION, "publish"), - satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("producer")), + satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("producer")), satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty), satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative))); if (testHeaders) { @@ -135,7 +135,7 @@ private static List processAttributes( satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty), satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative), equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), - satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("consumer")))); + satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("consumer")))); if (testHeaders) { assertions.add( equalTo( @@ -156,7 +156,7 @@ protected static List receiveAttributes(boolean testHeaders) equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), equalTo(MESSAGING_OPERATION, "receive"), equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), - satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("consumer")), + satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("consumer")), equalTo(MESSAGING_BATCH_MESSAGE_COUNT, 1))); if (testHeaders) { assertions.add( diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java index 4cd04c0a4a32..d235b487720d 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java @@ -19,6 +19,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingConsumerMetrics; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingProcessMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingProducerMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; @@ -122,7 +123,8 @@ private static Instrumenter createConsumerProcessInstrument INSTRUMENTATION_NAME, MessagingSpanNameExtractor.create(getter, MessageOperation.PROCESS)) .addAttributesExtractor( - createMessagingAttributesExtractor(getter, MessageOperation.PROCESS)); + createMessagingAttributesExtractor(getter, MessageOperation.PROCESS)) + .addOperationMetrics(MessagingProcessMetrics.get()); setMessagingProcessExceptionEventExtractor(instrumenterBuilder); if (receiveInstrumentationEnabled) { From 49704b0305bb7354be820e494f06deceb7ff60d3 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 16 Jul 2026 15:57:05 -0700 Subject: [PATCH 02/18] Address review comment from trask --- .../semconv/messaging/MessageOperation.java | 14 ++++- .../MessagingAttributesExtractor.java | 45 ++++++++++------ .../MessagingAttributesExtractorBuilder.java | 18 +++---- .../messaging/MessagingConsumerMetrics.java | 3 ++ .../semconv/messaging/MessagingOperation.java | 54 ------------------- .../messaging/MessagingSpanKindExtractor.java | 2 +- .../messaging/MessagingSpanNameExtractor.java | 25 +++++---- .../MessagingAttributesExtractorTest.java | 5 +- .../MessagingProcessMetricsTest.java | 2 +- .../api/internal/SemconvStability.java | 11 ++-- .../v0_11/internal/KafkaClientBaseTest.java | 17 ++---- .../v2_6/AbstractInterceptorsTest.java | 6 +-- .../InterceptorsSuppressReceiveSpansTest.java | 4 +- .../v2_6/WrapperSuppressReceiveSpansTest.java | 4 +- .../kafkaclients/v2_6/WrapperTest.java | 6 +-- .../pulsar-2.8/javaagent/build.gradle.kts | 16 +++++- .../pulsar/v2_8/AbstractPulsarClientTest.java | 2 +- .../pulsar/v2_8/PulsarClientTest.java | 26 +++++++++ .../SemconvMessagingStabilityUtil.java | 36 +++++++++++++ 19 files changed, 170 insertions(+), 126 deletions(-) delete mode 100644 instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingOperation.java create mode 100644 testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/message/SemconvMessagingStabilityUtil.java diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessageOperation.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessageOperation.java index ef87253e22a1..e2d3ffccdb16 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessageOperation.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessageOperation.java @@ -21,7 +21,19 @@ public enum MessageOperation { * Returns the legacy operation name. The v1.43 operation name defaults to this value unless an * instrumentation supplies a system-specific override. */ - String operationName() { + String legacyOperationName() { return name().toLowerCase(Locale.ROOT); } + + String operationType() { + switch (this) { + case PUBLISH: + return "send"; + case RECEIVE: + return "receive"; + case PROCESS: + return "process"; + } + throw new IllegalStateException("Can't possibly happen"); + } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractor.java index b93a5abb734d..bfed16beb2e3 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractor.java @@ -8,6 +8,7 @@ import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldMessagingSemconv; import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; +import static java.util.Objects.requireNonNull; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributesBuilder; @@ -71,8 +72,9 @@ public final class MessagingAttributesExtractor * with default configuration. */ public static AttributesExtractor create( - MessagingAttributesGetter getter, @Nullable MessageOperation operation) { - return builder(getter, operation).build(); + MessagingAttributesGetter getter, + @Nullable MessageOperation messageOperation) { + return builder(getter, messageOperation).build(); } /** @@ -83,9 +85,9 @@ public static AttributesExtractor create( */ public static AttributesExtractor create( MessagingAttributesGetter getter, - MessageOperation operation, + MessageOperation messageOperation, String operationName) { - return builder(getter, operation, operationName).build(); + return builder(getter, messageOperation, operationName).build(); } /** @@ -93,8 +95,12 @@ public static AttributesExtractor create( * operation} that can be used to configure the messaging attributes extractor. */ public static MessagingAttributesExtractorBuilder builder( - MessagingAttributesGetter getter, @Nullable MessageOperation operation) { - return new MessagingAttributesExtractorBuilder<>(getter, operation); + MessagingAttributesGetter getter, + @Nullable MessageOperation messageOperation) { + return new MessagingAttributesExtractorBuilder<>( + getter, + messageOperation, + messageOperation == null ? null : messageOperation.legacyOperationName()); } /** @@ -103,22 +109,27 @@ public static MessagingAttributesExtractorBuilder MessagingAttributesExtractorBuilder builder( MessagingAttributesGetter getter, - MessageOperation operation, + MessageOperation messageOperation, String operationName) { return new MessagingAttributesExtractorBuilder<>( - getter, MessagingOperation.create(operation, operationName)); + getter, + requireNonNull(messageOperation, "messageOperation"), + requireNonNull(operationName, "operationName")); } private final MessagingAttributesGetter getter; - @Nullable private final MessagingOperation operation; + @Nullable private final MessageOperation messageOperation; + @Nullable private final String operationName; private final List capturedHeaders; MessagingAttributesExtractor( MessagingAttributesGetter getter, - @Nullable MessagingOperation operation, + @Nullable MessageOperation messageOperation, + @Nullable String operationName, List capturedHeaders) { this.getter = getter; - this.operation = operation; + this.messageOperation = messageOperation; + this.operationName = operationName; this.capturedHeaders = new ArrayList<>(capturedHeaders); } @@ -152,13 +163,13 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST if (emitStableMessagingSemconv()) { attributes.put(MESSAGING_CLIENT_ID, getter.getClientId(request)); } - if (operation != null) { + if (messageOperation != null) { if (emitOldMessagingSemconv()) { - attributes.put(MESSAGING_OPERATION, operation.operation().operationName()); + attributes.put(MESSAGING_OPERATION, messageOperation.legacyOperationName()); } if (emitStableMessagingSemconv()) { - attributes.put(MESSAGING_OPERATION_NAME, operation.name()); - attributes.put(MESSAGING_OPERATION_TYPE, operation.type()); + attributes.put(MESSAGING_OPERATION_NAME, operationName); + attributes.put(MESSAGING_OPERATION_TYPE, messageOperation.operationType()); } } } @@ -191,11 +202,11 @@ public void onEnd( @Override @Nullable public SpanKey internalGetSpanKey() { - if (operation == null) { + if (messageOperation == null) { return null; } - switch (operation.operation()) { + switch (messageOperation) { case PUBLISH: return SpanKey.PRODUCER; case RECEIVE: diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorBuilder.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorBuilder.java index fc1035e91389..67ef5906e7eb 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorBuilder.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorBuilder.java @@ -18,18 +18,17 @@ public final class MessagingAttributesExtractorBuilder { final MessagingAttributesGetter getter; - @Nullable final MessagingOperation operation; + @Nullable final MessageOperation messageOperation; + @Nullable final String operationName; List capturedHeaders = emptyList(); MessagingAttributesExtractorBuilder( - MessagingAttributesGetter getter, @Nullable MessageOperation operation) { - this(getter, MessagingOperation.createNullable(operation)); - } - - MessagingAttributesExtractorBuilder( - MessagingAttributesGetter getter, @Nullable MessagingOperation operation) { + MessagingAttributesGetter getter, + @Nullable MessageOperation messageOperation, + @Nullable String operationName) { this.getter = getter; - this.operation = operation; + this.messageOperation = messageOperation; + this.operationName = operationName; } /** @@ -53,6 +52,7 @@ public MessagingAttributesExtractorBuilder setCapturedHeaders * MessagingAttributesExtractorBuilder}. */ public AttributesExtractor build() { - return new MessagingAttributesExtractor<>(getter, operation, capturedHeaders); + return new MessagingAttributesExtractor<>( + getter, messageOperation, operationName, capturedHeaders); } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingConsumerMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingConsumerMetrics.java index f642643e8c67..16358c158261 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingConsumerMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingConsumerMetrics.java @@ -84,6 +84,9 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { if (receiveDurationHistogram != null) { receiveDurationHistogram.record(duration, attributes, context); } + // Metric view attribute advice can only select keys statically. The concrete destination name + // must be omitted when a template is available or the destination is temporary or anonymous, + // so this conditional requirement must be enforced before recording. Attributes filteredAttributes = clientOperationDurationHistogram != null || consumedMessagesCounter != null ? MessagingMetricsAdvice.filterAttributes(attributes) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingOperation.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingOperation.java deleted file mode 100644 index 3c1d763d9261..000000000000 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingOperation.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; - -import static java.util.Objects.requireNonNull; - -import javax.annotation.Nullable; - -final class MessagingOperation { - - static MessagingOperation create(MessageOperation operation) { - return create(operation, operation.operationName()); - } - - static MessagingOperation create(MessageOperation operation, String name) { - return new MessagingOperation(operation, name); - } - - @Nullable - static MessagingOperation createNullable(@Nullable MessageOperation operation) { - return operation == null ? null : create(operation, operation.operationName()); - } - - private final MessageOperation operation; - private final String name; - - private MessagingOperation(MessageOperation operation, String name) { - this.operation = requireNonNull(operation, "operation"); - this.name = requireNonNull(name, "name"); - } - - MessageOperation operation() { - return operation; - } - - String name() { - return name; - } - - String type() { - switch (operation) { - case PUBLISH: - return "send"; - case RECEIVE: - return "receive"; - case PROCESS: - return "process"; - } - throw new IllegalStateException("Can't possibly happen"); - } -} diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractor.java index 5372abd194e1..4de6aa855b8a 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractor.java @@ -16,7 +16,7 @@ public final class MessagingSpanKindExtractor { /** * Returns a span kind extractor following the v1.43 - * messaging span kind conventions when latest messaging conventions are enabled. + * messaging span kind conventions. */ public static SpanKindExtractor create(MessageOperation operation) { SpanKind spanKind; diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractor.java index ec6f5fa4e56c..0214fd3fc23f 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractor.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; +import static java.util.Objects.requireNonNull; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; @@ -21,26 +22,30 @@ public final class MessagingSpanNameExtractor implements SpanNameExtrac * @see MessageOperation used to extract {@code }. */ public static SpanNameExtractor create( - MessagingAttributesGetter getter, MessageOperation operation) { - return new MessagingSpanNameExtractor<>(getter, MessagingOperation.create(operation)); + MessagingAttributesGetter getter, MessageOperation messageOperation) { + return new MessagingSpanNameExtractor<>( + getter, messageOperation, messageOperation.legacyOperationName()); } /** Returns a {@link SpanNameExtractor} with a system-specific v1.43 operation name. */ public static SpanNameExtractor create( MessagingAttributesGetter getter, - MessageOperation operation, + MessageOperation messageOperation, String operationName) { - return new MessagingSpanNameExtractor<>( - getter, MessagingOperation.create(operation, operationName)); + return new MessagingSpanNameExtractor<>(getter, messageOperation, operationName); } private final MessagingAttributesGetter getter; - private final MessagingOperation operation; + private final MessageOperation messageOperation; + private final String operationName; private MessagingSpanNameExtractor( - MessagingAttributesGetter getter, MessagingOperation operation) { + MessagingAttributesGetter getter, + MessageOperation messageOperation, + String operationName) { this.getter = getter; - this.operation = operation; + this.messageOperation = requireNonNull(messageOperation, "messageOperation"); + this.operationName = requireNonNull(operationName, "operationName"); } @Override @@ -52,7 +57,7 @@ public String extract(REQUEST request) { && !getter.isAnonymousDestination(request)) { destinationName = getter.getDestination(request); } - return destinationName == null ? operation.name() : operation.name() + " " + destinationName; + return destinationName == null ? operationName : operationName + " " + destinationName; } String destinationName = @@ -63,6 +68,6 @@ public String extract(REQUEST request) { destinationName = "unknown"; } - return destinationName + " " + operation.operation().operationName(); + return destinationName + " " + messageOperation.legacyOperationName(); } } diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorTest.java index 41dfc31371ec..30418f464171 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorTest.java @@ -109,13 +109,12 @@ void shouldExtractAllAvailableAttributes( expectedEntries.add(entry(MESSAGING_MESSAGE_ENVELOPE_SIZE, 120L)); if (emitOldMessagingSemconv()) { expectedEntries.add(entry(stringKey("messaging.client_id"), "43")); - expectedEntries.add(entry(MESSAGING_OPERATION, operation.operationName())); + expectedEntries.add(entry(MESSAGING_OPERATION, operation.legacyOperationName())); } if (emitStableMessagingSemconv()) { expectedEntries.add(entry(stringKey("messaging.client.id"), "43")); expectedEntries.add(entry(MESSAGING_OPERATION_NAME, operationName)); - expectedEntries.add( - entry(MESSAGING_OPERATION_TYPE, MessagingOperation.create(operation).type())); + expectedEntries.add(entry(MESSAGING_OPERATION_TYPE, operation.operationType())); } @SuppressWarnings({"unchecked", "rawtypes"}) diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProcessMetricsTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProcessMetricsTest.java index 1f2d32d82b71..19b20b4888e6 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProcessMetricsTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingProcessMetricsTest.java @@ -32,7 +32,7 @@ class MessagingProcessMetricsTest { @Test @SuppressWarnings("deprecation") // using deprecated semconv - void emitsOnlyLatestProcessDuration() { + void emitsProcessDurationAccordingToConfiguration() { InMemoryMetricReader metricReader = InMemoryMetricReader.createDelta(); SdkMeterProvider meterProvider = SdkMeterProvider.builder().registerMetricReader(metricReader).build(); diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SemconvStability.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SemconvStability.java index bd6935a2817d..d4465850c5ad 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SemconvStability.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SemconvStability.java @@ -169,15 +169,14 @@ private static boolean emitStable(SemconvMode mode) { return mode.version() >= 1; } - public static boolean emitOldMessagingSemconv() { + public static boolean emitOldMessagingSemconv() { // to be removed in 3.0 return emitOldMessagingSemconv; } - /** - * Returns whether the selected v1 experimental messaging semantic conventions should be emitted. - * The method name is retained for 2.x compatibility with other semconv selectors. - */ - public static boolean emitStableMessagingSemconv() { + // Returns whether the selected v1 experimental messaging semantic conventions should be emitted. + // The method name follows the existing pattern; it does not indicate that the messaging + // conventions are stable. + public static boolean emitStableMessagingSemconv() { // to be removed in 3.0 return emitStableMessagingSemconv; } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/testing/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaClientBaseTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/testing/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaClientBaseTest.java index 32ffc0518ad4..c6a8301f571e 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/testing/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaClientBaseTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/testing/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaClientBaseTest.java @@ -10,6 +10,7 @@ import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldMessagingSemconv; import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.instrumentation.testing.junit.message.MessageHeaderUtil.headerAttributeKey; +import static io.opentelemetry.instrumentation.testing.junit.message.SemconvMessagingStabilityUtil.effectiveKey; import static io.opentelemetry.instrumentation.testing.util.TestLatestDeps.testLatestDeps; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; @@ -76,10 +77,8 @@ public abstract class KafkaClientBaseTest { @RegisterExtension final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); protected static final String SHARED_TOPIC = "shared.topic"; - protected static final AttributeKey MESSAGING_CLIENT_ID_OLD = + protected static final AttributeKey MESSAGING_CLIENT_ID = AttributeKey.stringKey("messaging.client_id"); - private static final AttributeKey MESSAGING_CLIENT_ID = - AttributeKey.stringKey("messaging.client.id"); private KafkaContainer kafka; protected Producer producer; @@ -188,9 +187,7 @@ protected static List sendAttributes( equalTo(MESSAGING_OPERATION, emitOldMessagingSemconv() ? "publish" : null), equalTo(MESSAGING_OPERATION_NAME, emitStableMessagingSemconv() ? "publish" : null), equalTo(MESSAGING_OPERATION_TYPE, emitStableMessagingSemconv() ? "send" : null), - satisfies( - emitStableMessagingSemconv() ? MESSAGING_CLIENT_ID : MESSAGING_CLIENT_ID_OLD, - val -> val.startsWith("producer")), + satisfies(effectiveKey(MESSAGING_CLIENT_ID), val -> val.startsWith("producer")), satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty))); if (emitOldMessagingSemconv()) { assertions.add(satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative)); @@ -226,9 +223,7 @@ protected static List receiveAttributes(boolean testHeaders) equalTo(MESSAGING_OPERATION, emitOldMessagingSemconv() ? "receive" : null), equalTo(MESSAGING_OPERATION_NAME, emitStableMessagingSemconv() ? "receive" : null), equalTo(MESSAGING_OPERATION_TYPE, emitStableMessagingSemconv() ? "receive" : null), - satisfies( - emitStableMessagingSemconv() ? MESSAGING_CLIENT_ID : MESSAGING_CLIENT_ID_OLD, - val -> val.startsWith("consumer")), + satisfies(effectiveKey(MESSAGING_CLIENT_ID), val -> val.startsWith("consumer")), satisfies(MESSAGING_BATCH_MESSAGE_COUNT, AbstractLongAssert::isPositive))); // consumer group is not available in version 0.11 if (testLatestDeps()) { @@ -251,9 +246,7 @@ protected static List processAttributes( equalTo(MESSAGING_OPERATION, emitOldMessagingSemconv() ? "process" : null), equalTo(MESSAGING_OPERATION_NAME, emitStableMessagingSemconv() ? "process" : null), equalTo(MESSAGING_OPERATION_TYPE, emitStableMessagingSemconv() ? "process" : null), - satisfies( - emitStableMessagingSemconv() ? MESSAGING_CLIENT_ID : MESSAGING_CLIENT_ID_OLD, - val -> val.startsWith("consumer")), + satisfies(effectiveKey(MESSAGING_CLIENT_ID), val -> val.startsWith("consumer")), satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty))); if (EXPERIMENTAL_ATTRIBUTES) { assertions.add( diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java index 31d5f1a5b0e0..a62c8e475f19 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java @@ -164,7 +164,7 @@ private static List publishAttributes(boolean experimental) equalTo(MESSAGING_SYSTEM, "kafka"), equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), equalTo(MESSAGING_OPERATION, "publish"), - satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("producer")), + satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("producer")), satisfies( stringKey("messaging.kafka.bootstrap.servers"), val -> { @@ -181,7 +181,7 @@ private static List receiveAttributes() { equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), equalTo(MESSAGING_OPERATION, "receive"), equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), - satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("consumer")), + satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("consumer")), equalTo(MESSAGING_BATCH_MESSAGE_COUNT, 1)); } @@ -195,7 +195,7 @@ private static List processAttributes(boolean experimental) satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty), satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative), equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), - satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("consumer")), + satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("consumer")), satisfies( longKey("kafka.record.queue_time_ms"), val -> { diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsSuppressReceiveSpansTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsSuppressReceiveSpansTest.java index dd5082d33c26..8555895bd51c 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsSuppressReceiveSpansTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsSuppressReceiveSpansTest.java @@ -51,7 +51,7 @@ void assertTraces() { equalTo(MESSAGING_SYSTEM, "kafka"), equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), equalTo(MESSAGING_OPERATION, "publish"), - satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("producer"))), + satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("producer"))), span -> span.hasName(SHARED_TOPIC + " process") .hasKind(SpanKind.CONSUMER) @@ -67,7 +67,7 @@ void assertTraces() { satisfies( MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative), equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), - satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("consumer")), + satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("consumer")), equalTo(stringKey("test-baggage-key-1"), "test-baggage-value-1"), equalTo(stringKey("test-baggage-key-2"), "test-baggage-value-2")), span -> diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperSuppressReceiveSpansTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperSuppressReceiveSpansTest.java index efb45c1dd6b5..a549ce1aa7f4 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperSuppressReceiveSpansTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperSuppressReceiveSpansTest.java @@ -71,7 +71,7 @@ static List sendAttributes(boolean testHeaders, boolean test equalTo(MESSAGING_SYSTEM, "kafka"), equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), equalTo(MESSAGING_OPERATION, "publish"), - satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("producer")), + satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("producer")), satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty), satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative))); if (testHeaders) { @@ -98,7 +98,7 @@ static List processAttributes( satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty), satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative), equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), - satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("consumer")))); + satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("consumer")))); if (testHeaders) { assertions.add(equalTo(headerAttributeKey("Test-Message-Header"), singletonList("test"))); } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperTest.java index 45e22fcf8e1e..c9f784651554 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperTest.java @@ -106,7 +106,7 @@ protected static List sendAttributes( equalTo(MESSAGING_SYSTEM, "kafka"), equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), equalTo(MESSAGING_OPERATION, "publish"), - satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("producer")), + satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("producer")), satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty), satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative))); if (testHeaders) { @@ -135,7 +135,7 @@ private static List processAttributes( satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty), satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative), equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), - satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("consumer")))); + satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("consumer")))); if (testHeaders) { assertions.add( equalTo( @@ -156,7 +156,7 @@ protected static List receiveAttributes(boolean testHeaders) equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), equalTo(MESSAGING_OPERATION, "receive"), equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), - satisfies(MESSAGING_CLIENT_ID_OLD, val -> val.startsWith("consumer")), + satisfies(MESSAGING_CLIENT_ID, val -> val.startsWith("consumer")), equalTo(MESSAGING_BATCH_MESSAGE_COUNT, 1))); if (testHeaders) { assertions.add( diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts b/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts index 5bcbd464c1c0..2d027dc7745b 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts @@ -48,6 +48,20 @@ tasks { systemProperty("metadataConfig", "otel.instrumentation.pulsar.experimental-span-attributes=true") } + // The task name follows the existing pattern; it does not indicate that the messaging + // conventions are stable. + val testStableSemconv = register("testStableSemconv") { + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath + filter { + includeTestsMatching("PulsarClientTest.testConsumeNonPartitionedTopic") + } + jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=true") + jvmArgs("-Dotel.semconv-stability.preview=messaging") + jvmArgs("-Dotel.instrumentation.common.v3-preview=true") + systemProperty("metadataConfig", "otel.semconv-stability.opt-in=messaging") + } + test { filter { excludeTestsMatching("PulsarClientSuppressReceiveSpansTest") @@ -60,7 +74,7 @@ tasks { } check { - dependsOn(testReceiveSpanDisabled, testExperimental) + dependsOn(testReceiveSpanDisabled, testExperimental, testStableSemconv) } if (otelProps.denyUnsafe) { diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/AbstractPulsarClientTest.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/AbstractPulsarClientTest.java index 083ceac22210..26ce8f5b6d36 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/AbstractPulsarClientTest.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/AbstractPulsarClientTest.java @@ -67,7 +67,7 @@ static T experimental(T value) { private static final Logger logger = LoggerFactory.getLogger(AbstractPulsarClientTest.class); - private static final String INSTRUMENTATION_NAME = "io.opentelemetry.pulsar-2.8"; + static final String INSTRUMENTATION_NAME = "io.opentelemetry.pulsar-2.8"; private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("apachepulsar/pulsar:2.8.0"); diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.java index 9d3603afa177..3199f39a9680 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.pulsar.v2_8; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanKind; import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanName; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; @@ -13,6 +14,7 @@ import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_NAME; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; import static java.util.Arrays.asList; import static java.util.concurrent.TimeUnit.MINUTES; @@ -63,6 +65,30 @@ void testConsumeNonPartitionedTopic() throws Exception { latch.await(1, MINUTES); + if (emitStableMessagingSemconv()) { + testing.waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "messaging.process.duration", + metrics -> + metrics.satisfiesExactly( + metric -> + assertThat(metric) + .hasUnit("s") + .hasDescription("Duration of processing operation.") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSumGreaterThan(0.0) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION_NAME, "process"), + equalTo(MESSAGING_SYSTEM, "pulsar"), + equalTo(MESSAGING_DESTINATION_NAME, topic)) + .hasBucketBoundaries(DURATION_BUCKETS))))); + return; + } + AtomicReference producerSpan = new AtomicReference<>(); testing.waitAndAssertSortedTraces( orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/message/SemconvMessagingStabilityUtil.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/message/SemconvMessagingStabilityUtil.java new file mode 100644 index 000000000000..1da1081527d8 --- /dev/null +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/message/SemconvMessagingStabilityUtil.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.testing.junit.message; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; + +import io.opentelemetry.api.common.AttributeKey; +import java.util.HashMap; +import java.util.Map; + +// supports asserting on old messaging semconv, to be removed in 3.0 +public class SemconvMessagingStabilityUtil { + + private static final Map, AttributeKey> oldToNewMap = buildMap(); + + private static Map, AttributeKey> buildMap() { + Map, AttributeKey> map = new HashMap<>(); + map.put(stringKey("messaging.client_id"), stringKey("messaging.client.id")); + return map; + } + + @SuppressWarnings("unchecked") + public static AttributeKey effectiveKey(AttributeKey oldKey) { + // not testing messaging/dup + if (emitStableMessagingSemconv()) { + return (AttributeKey) oldToNewMap.get(oldKey); + } + return oldKey; + } + + private SemconvMessagingStabilityUtil() {} +} From fd4f3284dbb9d51614a4bb70b0258166d7b3dbff Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 16 Jul 2026 18:11:01 -0700 Subject: [PATCH 03/18] Address more review comments from trask --- CHANGELOG.md | 2 + .../semconv/messaging/MessageOperation.java | 37 ++---- .../MessagingAttributesExtractor.java | 86 ++++++------ .../MessagingAttributesExtractorBuilder.java | 22 +++- .../messaging/MessagingAttributesGetter.java | 15 +++ .../messaging/MessagingConsumerMetrics.java | 23 ++-- .../messaging/MessagingOperationType.java | 35 +++++ .../messaging/MessagingSpanKindExtractor.java | 36 ++++- .../messaging/MessagingSpanNameExtractor.java | 51 ++++--- .../MessagingSpanNameExtractorBuilder.java | 41 ++++++ .../MessagingAttributesExtractorTest.java | 124 ++++++++++++++++-- .../MessagingSpanKindExtractorTest.java | 42 +++++- .../MessagingSpanNameExtractorTest.java | 16 ++- .../instrumentation/api/internal/SpanKey.java | 6 + .../internal/AwsSdkInstrumenterFactory.java | 24 ++-- .../internal/AwsSdkInstrumenterFactory.java | 24 ++-- .../common/v1_1/JmsInstrumenterFactory.java | 24 ++-- .../internal/KafkaInstrumenterFactory.java | 30 ++--- .../v2_6/KafkaConnectSingletons.java | 6 +- .../internal/NatsInstrumenterFactory.java | 10 +- .../v2_8/telemetry/PulsarSingletons.java | 22 ++-- .../rabbitmq/v2_7/RabbitSingletons.java | 13 +- .../v4_8/RocketMqInstrumenterFactory.java | 18 +-- ...ocketMqConsumerProcessAttributeGetter.java | 7 + .../v5_0/RocketMqInstrumenterFactory.java | 24 ++-- .../SpringIntegrationTelemetryBuilder.java | 10 +- .../pulsar/v1_0/SpringPulsarSingletons.java | 8 +- .../rabbit/v1_0/SpringRabbitSingletons.java | 8 +- 28 files changed, 527 insertions(+), 237 deletions(-) create mode 100644 instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingOperationType.java create mode 100644 instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractorBuilder.java diff --git a/CHANGELOG.md b/CHANGELOG.md index aa7a222aa6ea..4d926d16ac1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### 🚫 Deprecations +- Deprecate `MessageOperation` in favor of `MessagingOperationType`. + ([#19233](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/19233)) - Deprecate `DeclarativeConfigPropertiesBridgeBuilder`. Read declarative component configuration through `DeclarativeConfigProperties` directly. To expose `ConfigProperties` through the declarative configuration API, use `ConfigPropertiesBackedConfigProvider`. diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessageOperation.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessageOperation.java index e2d3ffccdb16..43dd28e2834f 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessageOperation.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessageOperation.java @@ -5,35 +5,24 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; -import java.util.Locale; - /** - * Represents type of operations - * that may be used in a messaging system. + * Represents an operation that may be used in a messaging system. + * + * @deprecated Use {@link MessagingOperationType}. Will be removed in 3.0. */ +@Deprecated // to be removed in 3.0 public enum MessageOperation { - PUBLISH, - RECEIVE, - PROCESS; + PUBLISH(MessagingOperationType.SEND), + RECEIVE(MessagingOperationType.RECEIVE), + PROCESS(MessagingOperationType.PROCESS); + + private final MessagingOperationType operationType; - /** - * Returns the legacy operation name. The v1.43 operation name defaults to this value unless an - * instrumentation supplies a system-specific override. - */ - String legacyOperationName() { - return name().toLowerCase(Locale.ROOT); + MessageOperation(MessagingOperationType operationType) { + this.operationType = operationType; } - String operationType() { - switch (this) { - case PUBLISH: - return "send"; - case RECEIVE: - return "receive"; - case PROCESS: - return "process"; - } - throw new IllegalStateException("Can't possibly happen"); + MessagingOperationType type() { + return operationType; } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractor.java index bfed16beb2e3..4a26f57d3fc5 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractor.java @@ -8,7 +8,6 @@ import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldMessagingSemconv; import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; -import static java.util.Objects.requireNonNull; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributesBuilder; @@ -67,68 +66,55 @@ public final class MessagingAttributesExtractor static final String TEMP_DESTINATION_NAME = "(temporary)"; - /** - * Creates the messaging attributes extractor for the given {@link MessageOperation operation} - * with default configuration. - */ + /** Creates the messaging attributes extractor for the given operation type. */ public static AttributesExtractor create( MessagingAttributesGetter getter, - @Nullable MessageOperation messageOperation) { - return builder(getter, messageOperation).build(); + @Nullable MessagingOperationType operationType) { + return builder(getter, operationType).build(); } /** - * Creates the messaging attributes extractor with a system-specific v1.43 operation name. - * - *

The {@code operationName} is emitted as {@code messaging.operation.name}. The legacy {@code - * messaging.operation} value remains derived from {@code operation}. + * @deprecated Use {@link #create(MessagingAttributesGetter, MessagingOperationType)}. Will be + * removed in 3.0. */ + @Deprecated // to be removed in 3.0 public static AttributesExtractor create( - MessagingAttributesGetter getter, - MessageOperation messageOperation, - String operationName) { - return builder(getter, messageOperation, operationName).build(); + MessagingAttributesGetter getter, @Nullable MessageOperation operation) { + return create(getter, operation == null ? null : operation.type()); } /** - * Returns a new {@link MessagingAttributesExtractorBuilder} for the given {@link MessageOperation - * operation} that can be used to configure the messaging attributes extractor. + * Returns a new {@link MessagingAttributesExtractorBuilder} configured for the given operation + * type. */ public static MessagingAttributesExtractorBuilder builder( MessagingAttributesGetter getter, - @Nullable MessageOperation messageOperation) { - return new MessagingAttributesExtractorBuilder<>( - getter, - messageOperation, - messageOperation == null ? null : messageOperation.legacyOperationName()); + @Nullable MessagingOperationType operationType) { + return new MessagingAttributesExtractorBuilder<>(getter, operationType); } /** - * Returns a new {@link MessagingAttributesExtractorBuilder} with a system-specific v1.43 - * operation name. + * @deprecated Use {@link #builder(MessagingAttributesGetter, MessagingOperationType)}. Will be + * removed in 3.0. */ + @Deprecated // to be removed in 3.0 public static MessagingAttributesExtractorBuilder builder( - MessagingAttributesGetter getter, - MessageOperation messageOperation, - String operationName) { - return new MessagingAttributesExtractorBuilder<>( - getter, - requireNonNull(messageOperation, "messageOperation"), - requireNonNull(operationName, "operationName")); + MessagingAttributesGetter getter, @Nullable MessageOperation operation) { + return builder(getter, operation == null ? null : operation.type()); } private final MessagingAttributesGetter getter; - @Nullable private final MessageOperation messageOperation; + @Nullable private final MessagingOperationType operationType; @Nullable private final String operationName; private final List capturedHeaders; MessagingAttributesExtractor( MessagingAttributesGetter getter, - @Nullable MessageOperation messageOperation, + @Nullable MessagingOperationType operationType, @Nullable String operationName, List capturedHeaders) { this.getter = getter; - this.messageOperation = messageOperation; + this.operationType = operationType; this.operationName = operationName; this.capturedHeaders = new ArrayList<>(capturedHeaders); } @@ -163,13 +149,13 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST if (emitStableMessagingSemconv()) { attributes.put(MESSAGING_CLIENT_ID, getter.getClientId(request)); } - if (messageOperation != null) { - if (emitOldMessagingSemconv()) { - attributes.put(MESSAGING_OPERATION, messageOperation.legacyOperationName()); - } - if (emitStableMessagingSemconv()) { - attributes.put(MESSAGING_OPERATION_NAME, operationName); - attributes.put(MESSAGING_OPERATION_TYPE, messageOperation.operationType()); + if (emitOldMessagingSemconv() && operationType != null) { + attributes.put(MESSAGING_OPERATION, operationType.defaultOperationName()); + } + if (emitStableMessagingSemconv()) { + attributes.put(MESSAGING_OPERATION_NAME, operationName); + if (operationType != null) { + attributes.put(MESSAGING_OPERATION_TYPE, operationType.value()); } } } @@ -183,8 +169,12 @@ public void onEnd( @Nullable Throwable error) { attributes.put(MESSAGING_MESSAGE_ID, getter.getMessageId(request, response)); attributes.put(MESSAGING_BATCH_MESSAGE_COUNT, getter.getBatchMessageCount(request, response)); - if (emitStableMessagingSemconv() && error != null) { - attributes.put(ERROR_TYPE, error.getClass().getName()); + if (emitStableMessagingSemconv()) { + String errorType = getter.getErrorType(request, response, error); + if (errorType == null && error != null) { + errorType = error.getClass().getName(); + } + attributes.put(ERROR_TYPE, errorType); } for (String name : capturedHeaders) { @@ -202,17 +192,21 @@ public void onEnd( @Override @Nullable public SpanKey internalGetSpanKey() { - if (messageOperation == null) { + if (operationType == null) { return null; } - switch (messageOperation) { - case PUBLISH: + switch (operationType) { + case CREATE: + return SpanKey.PRODUCER_CREATE; + case SEND: return SpanKey.PRODUCER; case RECEIVE: return SpanKey.CONSUMER_RECEIVE; case PROCESS: return SpanKey.CONSUMER_PROCESS; + case SETTLE: + return SpanKey.CONSUMER_SETTLE; } throw new IllegalStateException("Can't possibly happen"); } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorBuilder.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorBuilder.java index 67ef5906e7eb..1055863eda52 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorBuilder.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorBuilder.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; import static java.util.Collections.emptyList; +import static java.util.Objects.requireNonNull; import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; @@ -18,17 +19,24 @@ public final class MessagingAttributesExtractorBuilder { final MessagingAttributesGetter getter; - @Nullable final MessageOperation messageOperation; - @Nullable final String operationName; + @Nullable MessagingOperationType operationType; + @Nullable String operationName; List capturedHeaders = emptyList(); MessagingAttributesExtractorBuilder( MessagingAttributesGetter getter, - @Nullable MessageOperation messageOperation, - @Nullable String operationName) { + @Nullable MessagingOperationType operationType) { this.getter = getter; - this.messageOperation = messageOperation; - this.operationName = operationName; + this.operationType = operationType; + this.operationName = operationType == null ? null : operationType.defaultOperationName(); + } + + /** Configures the system-specific operation name emitted as {@code messaging.operation.name}. */ + @CanIgnoreReturnValue + public MessagingAttributesExtractorBuilder setOperationName( + String operationName) { + this.operationName = requireNonNull(operationName, "operationName"); + return this; } /** @@ -53,6 +61,6 @@ public MessagingAttributesExtractorBuilder setCapturedHeaders */ public AttributesExtractor build() { return new MessagingAttributesExtractor<>( - getter, messageOperation, operationName, capturedHeaders); + getter, operationType, operationName, capturedHeaders); } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesGetter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesGetter.java index ae0b29471cf0..eaea16aaf74f 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesGetter.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesGetter.java @@ -55,6 +55,21 @@ default String getDestinationPartitionId(REQUEST request) { return null; } + /** + * Returns a description of a class of error the operation ended with. + * + *

If this method returns {@code null}, the exception class name (if any) will be used as error + * type. + * + *

The cardinality of the error type should be low. The instrumentations implementing this + * method are recommended to document the custom values they support. + */ + @Nullable + default String getErrorType( + REQUEST request, @Nullable RESPONSE response, @Nullable Throwable error) { + return null; + } + /** * Extracts all values of header named {@code name} from the request, or an empty list if there * were none. diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingConsumerMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingConsumerMetrics.java index 16358c158261..2329d65776db 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingConsumerMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingConsumerMetrics.java @@ -95,31 +95,34 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { clientOperationDurationHistogram.record(duration, filteredAttributes, context); } - long receiveMessagesCount = getReceiveMessagesCount(state.startAttributes(), endAttributes); + Long batchMessageCount = getBatchMessageCount(state.startAttributes(), endAttributes); if (receiveMessageCount != null) { - receiveMessageCount.add(receiveMessagesCount, attributes, context); + receiveMessageCount.add( + batchMessageCount == null ? 1 : batchMessageCount, attributes, context); } if (consumedMessagesCounter != null) { consumedMessagesCounter.add( - getConsumedMessagesCount(attributes, receiveMessagesCount), filteredAttributes, context); + getConsumedMessagesCount(attributes, batchMessageCount), filteredAttributes, context); } } - private static long getReceiveMessagesCount(Attributes... attributesList) { + @Nullable + private static Long getBatchMessageCount(Attributes... attributesList) { for (Attributes attributes : attributesList) { Long value = attributes.get(MESSAGING_BATCH_MESSAGE_COUNT); if (value != null) { return value; } } - return 1; + return null; } - private static long getConsumedMessagesCount(Attributes attributes, long receiveMessagesCount) { - return attributes.get(MESSAGING_BATCH_MESSAGE_COUNT) == null - && attributes.get(ERROR_TYPE) != null - ? 0 - : receiveMessagesCount; + private static long getConsumedMessagesCount( + Attributes attributes, @Nullable Long batchMessageCount) { + if (batchMessageCount != null) { + return batchMessageCount; + } + return attributes.get(ERROR_TYPE) == null ? 1 : 0; } private static DoubleHistogram buildReceiveDuration(Meter meter) { diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingOperationType.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingOperationType.java new file mode 100644 index 000000000000..53b90807068b --- /dev/null +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingOperationType.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; + +/** + * Represents a messaging + * operation type. + */ +public enum MessagingOperationType { + CREATE("create", "create"), + SEND("send", "publish"), + RECEIVE("receive", "receive"), + PROCESS("process", "process"), + SETTLE("settle", "settle"); + + private final String value; + private final String defaultOperationName; + + MessagingOperationType(String value, String defaultOperationName) { + this.value = value; + this.defaultOperationName = defaultOperationName; + } + + String value() { + return value; + } + + String defaultOperationName() { + return defaultOperationName; + } +} diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractor.java index 4de6aa855b8a..3759bcd3e388 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractor.java @@ -18,18 +18,40 @@ public final class MessagingSpanKindExtractor { * href="https://github.com/open-telemetry/semantic-conventions/blob/v1.43.0/docs/messaging/messaging-spans.md#span-kind">v1.43 * messaging span kind conventions. */ - public static SpanKindExtractor create(MessageOperation operation) { + public static SpanKindExtractor create(MessagingOperationType operationType) { + return create(operationType, true); + } + + /** + * Returns a span kind extractor following the v1.43 + * messaging span kind conventions. + * + * @param isSpanContextPropagated whether the context of a {@link MessagingOperationType#SEND} + * span is propagated as the message creation context; ignored for other operation types + */ + public static SpanKindExtractor create( + MessagingOperationType operationType, boolean isSpanContextPropagated) { SpanKind spanKind; - switch (operation) { - case PUBLISH: + switch (operationType) { + case CREATE: spanKind = SpanKind.PRODUCER; break; + case SEND: + spanKind = + emitStableMessagingSemconv() && !isSpanContextPropagated + ? SpanKind.CLIENT + : SpanKind.PRODUCER; + break; case RECEIVE: spanKind = emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER; break; case PROCESS: spanKind = SpanKind.CONSUMER; break; + case SETTLE: + spanKind = SpanKind.CLIENT; + break; default: throw new IllegalStateException("Can't possibly happen"); } @@ -37,5 +59,13 @@ public static SpanKindExtractor create(MessageOperation opera return request -> result; } + /** + * @deprecated Use {@link #create(MessagingOperationType)}. Will be removed in 3.0. + */ + @Deprecated // to be removed in 3.0 + public static SpanKindExtractor create(MessageOperation operation) { + return create(operation.type()); + } + private MessagingSpanKindExtractor() {} } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractor.java index 0214fd3fc23f..3c56d08d475c 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractor.java @@ -6,7 +6,6 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; -import static java.util.Objects.requireNonNull; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; @@ -19,33 +18,53 @@ public final class MessagingSpanNameExtractor implements SpanNameExtrac * * @see MessagingAttributesGetter#getDestination(Object) used to extract {@code }. - * @see MessageOperation used to extract {@code }. + * @see MessagingOperationType used to extract {@code }. */ public static SpanNameExtractor create( - MessagingAttributesGetter getter, MessageOperation messageOperation) { - return new MessagingSpanNameExtractor<>( - getter, messageOperation, messageOperation.legacyOperationName()); + MessagingAttributesGetter getter, MessagingOperationType operationType) { + return builder(getter, operationType).build(); } - /** Returns a {@link SpanNameExtractor} with a system-specific v1.43 operation name. */ + /** + * @deprecated Use {@link #create(MessagingAttributesGetter, MessagingOperationType)}. Will be + * removed in 3.0. + */ + @Deprecated // to be removed in 3.0 public static SpanNameExtractor create( - MessagingAttributesGetter getter, - MessageOperation messageOperation, - String operationName) { - return new MessagingSpanNameExtractor<>(getter, messageOperation, operationName); + MessagingAttributesGetter getter, MessageOperation operation) { + return create(getter, operation.type()); + } + + /** + * Returns a new {@link MessagingSpanNameExtractorBuilder} that can be used to configure the + * messaging span name extractor. + */ + public static MessagingSpanNameExtractorBuilder builder( + MessagingAttributesGetter getter, MessagingOperationType operationType) { + return new MessagingSpanNameExtractorBuilder<>(getter, operationType); + } + + /** + * @deprecated Use {@link #builder(MessagingAttributesGetter, MessagingOperationType)}. Will be + * removed in 3.0. + */ + @Deprecated // to be removed in 3.0 + public static MessagingSpanNameExtractorBuilder builder( + MessagingAttributesGetter getter, MessageOperation operation) { + return builder(getter, operation.type()); } private final MessagingAttributesGetter getter; - private final MessageOperation messageOperation; + private final MessagingOperationType operationType; private final String operationName; - private MessagingSpanNameExtractor( + MessagingSpanNameExtractor( MessagingAttributesGetter getter, - MessageOperation messageOperation, + MessagingOperationType operationType, String operationName) { this.getter = getter; - this.messageOperation = requireNonNull(messageOperation, "messageOperation"); - this.operationName = requireNonNull(operationName, "operationName"); + this.operationType = operationType; + this.operationName = operationName; } @Override @@ -68,6 +87,6 @@ public String extract(REQUEST request) { destinationName = "unknown"; } - return destinationName + " " + messageOperation.legacyOperationName(); + return destinationName + " " + operationType.defaultOperationName(); } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractorBuilder.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractorBuilder.java new file mode 100644 index 000000000000..661c83ad173f --- /dev/null +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractorBuilder.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.messaging; + +import static java.util.Objects.requireNonNull; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; + +/** A builder of {@link MessagingSpanNameExtractor}. */ +public final class MessagingSpanNameExtractorBuilder { + + private final MessagingAttributesGetter getter; + private final MessagingOperationType operationType; + private String operationName; + + MessagingSpanNameExtractorBuilder( + MessagingAttributesGetter getter, MessagingOperationType operationType) { + this.getter = getter; + this.operationType = requireNonNull(operationType, "operationType"); + this.operationName = operationType.defaultOperationName(); + } + + /** Configures the system-specific operation name used in the v1.43 messaging span name. */ + @CanIgnoreReturnValue + public MessagingSpanNameExtractorBuilder setOperationName(String operationName) { + this.operationName = requireNonNull(operationName, "operationName"); + return this; + } + + /** + * Returns a new {@link MessagingSpanNameExtractor} with the settings of this {@link + * MessagingSpanNameExtractorBuilder}. + */ + public SpanNameExtractor build() { + return new MessagingSpanNameExtractor<>(getter, operationType, operationName); + } +} diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorTest.java index 30418f464171..db89597abce6 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractorTest.java @@ -32,6 +32,7 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.internal.SpanKey; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -53,7 +54,7 @@ void shouldExtractAllAvailableAttributes( boolean temporary, boolean anonymous, String destination, - MessageOperation operation, + MessagingOperationType operationType, String operationName) { // given Map request = new HashMap<>(); @@ -69,13 +70,16 @@ void shouldExtractAllAvailableAttributes( } request.put("url", "http://broker/topic"); request.put("conversationId", "42"); + request.put("messageId", "42"); request.put("bodySize", "100"); request.put("envelopeSize", "120"); request.put("clientId", "43"); request.put("batchMessageCount", "2"); AttributesExtractor, String> underTest = - MessagingAttributesExtractor.create(TestGetter.INSTANCE, operation, operationName); + MessagingAttributesExtractor.builder(TestGetter.INSTANCE, operationType) + .setOperationName(operationName) + .build(); Context context = Context.root(); @@ -109,12 +113,12 @@ void shouldExtractAllAvailableAttributes( expectedEntries.add(entry(MESSAGING_MESSAGE_ENVELOPE_SIZE, 120L)); if (emitOldMessagingSemconv()) { expectedEntries.add(entry(stringKey("messaging.client_id"), "43")); - expectedEntries.add(entry(MESSAGING_OPERATION, operation.legacyOperationName())); + expectedEntries.add(entry(MESSAGING_OPERATION, operationType.defaultOperationName())); } if (emitStableMessagingSemconv()) { expectedEntries.add(entry(stringKey("messaging.client.id"), "43")); expectedEntries.add(entry(MESSAGING_OPERATION_NAME, operationName)); - expectedEntries.add(entry(MESSAGING_OPERATION_TYPE, operation.operationType())); + expectedEntries.add(entry(MESSAGING_OPERATION_TYPE, operationType.value())); } @SuppressWarnings({"unchecked", "rawtypes"}) @@ -138,21 +142,118 @@ void shouldExtractAllAvailableAttributes( static Stream destinations() { return Stream.of( argumentSet( - "regular destination", false, false, "destination", MessageOperation.RECEIVE, "poll"), + "create operation", + false, + false, + "destination", + MessagingOperationType.CREATE, + "create"), + argumentSet( + "regular destination", + false, + false, + "destination", + MessagingOperationType.RECEIVE, + "poll"), argumentSet( "temporary anonymous destination", true, true, "generated-destination", - MessageOperation.PROCESS, - "process")); + MessagingOperationType.PROCESS, + "process"), + argumentSet( + "settle operation", + false, + false, + "destination", + MessagingOperationType.SETTLE, + "settle")); + } + + @ParameterizedTest + @MethodSource("spanKeys") + void shouldReturnSpanKey(MessagingOperationType operationType, SpanKey spanKey) { + MessagingAttributesExtractor, String> underTest = + new MessagingAttributesExtractor<>( + TestGetter.INSTANCE, + operationType, + operationType.defaultOperationName(), + new ArrayList<>()); + + assertThat(underTest.internalGetSpanKey()).isSameAs(spanKey); + } + + static Stream spanKeys() { + return Stream.of( + argumentSet("create", MessagingOperationType.CREATE, SpanKey.PRODUCER_CREATE), + argumentSet("send", MessagingOperationType.SEND, SpanKey.PRODUCER), + argumentSet("receive", MessagingOperationType.RECEIVE, SpanKey.CONSUMER_RECEIVE), + argumentSet("process", MessagingOperationType.PROCESS, SpanKey.CONSUMER_PROCESS), + argumentSet("settle", MessagingOperationType.SETTLE, SpanKey.CONSUMER_SETTLE)); + } + + @SuppressWarnings("deprecation") // testing deprecated API + @Test + void shouldSupportDeprecatedMessageOperation() { + AttributesExtractor, String> underTest = + MessagingAttributesExtractor.create(TestGetter.INSTANCE, MessageOperation.PUBLISH); + + AttributesBuilder attributes = Attributes.builder(); + underTest.onStart(attributes, Context.root(), emptyMap()); + + List, Object>> expectedEntries = new ArrayList<>(); + if (emitOldMessagingSemconv()) { + expectedEntries.add(entry(MESSAGING_OPERATION, "publish")); + } + if (emitStableMessagingSemconv()) { + expectedEntries.add(entry(MESSAGING_OPERATION_NAME, "publish")); + expectedEntries.add(entry(MESSAGING_OPERATION_TYPE, "send")); + } + @SuppressWarnings({"unchecked", "rawtypes"}) + MapEntry, ?>[] expectedEntriesArr = + expectedEntries.toArray(new MapEntry[0]); + assertThat(attributes.build()).containsOnly(expectedEntriesArr); + } + + @Test + void shouldExtractOperationNameWithoutOperationType() { + MessagingOperationType operationType = null; + AttributesExtractor, String> underTest = + MessagingAttributesExtractor.builder(TestGetter.INSTANCE, operationType) + .setOperationName("ack") + .build(); + + AttributesBuilder attributes = Attributes.builder(); + underTest.onStart(attributes, Context.root(), emptyMap()); + + Attributes expected = + emitStableMessagingSemconv() + ? Attributes.of(MESSAGING_OPERATION_NAME, "ack") + : Attributes.empty(); + assertThat(attributes.build()).isEqualTo(expected); + } + + @Test + void shouldExtractErrorTypeFromResponse() { + MessagingOperationType operationType = null; + AttributesExtractor, String> underTest = + MessagingAttributesExtractor.create(TestGetter.INSTANCE, operationType); + + AttributesBuilder attributes = Attributes.builder(); + underTest.onEnd(attributes, Context.root(), emptyMap(), "failure", null); + + Attributes expected = + emitStableMessagingSemconv() ? Attributes.of(ERROR_TYPE, "failure") : Attributes.empty(); + assertThat(attributes.build()).isEqualTo(expected); } @Test void shouldExtractNoAttributesIfNoneAreAvailable() { // given + MessagingOperationType operationType = null; AttributesExtractor, String> underTest = - MessagingAttributesExtractor.create(TestGetter.INSTANCE, null); + MessagingAttributesExtractor.create(TestGetter.INSTANCE, operationType); Context context = Context.root(); @@ -219,7 +320,7 @@ public Long getMessageEnvelopeSize(Map request) { @Override public String getMessageId(Map request, String response) { - return response; + return request.get("messageId"); } @Nullable @@ -234,5 +335,10 @@ public Long getBatchMessageCount(Map request, @Nullable String r String payloadSize = request.get("batchMessageCount"); return payloadSize == null ? null : Long.valueOf(payloadSize); } + + @Override + public String getErrorType(Map request, String response, Throwable error) { + return "failure".equals(response) ? response : null; + } } } diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractorTest.java index 458a832a2f70..5ef04cd89bd9 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanKindExtractorTest.java @@ -11,6 +11,7 @@ import io.opentelemetry.api.trace.SpanKind; import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -19,16 +20,47 @@ class MessagingSpanKindExtractorTest { @ParameterizedTest @MethodSource("spanKinds") - void extractsSpanKind(MessageOperation operation, SpanKind oldKind, SpanKind kind) { - SpanKind actualKind = MessagingSpanKindExtractor.create(operation).extract(new Object()); + void extractsSpanKind( + MessagingOperationType operationType, + boolean isSpanContextPropagated, + SpanKind oldKind, + SpanKind kind) { + SpanKind actualKind = + MessagingSpanKindExtractor.create(operationType, isSpanContextPropagated) + .extract(new Object()); assertThat(actualKind).isEqualTo(emitStableMessagingSemconv() ? kind : oldKind); } + @Test + void sendDefaultsToPropagatedSpanContext() { + SpanKind spanKind = + MessagingSpanKindExtractor.create(MessagingOperationType.SEND).extract(new Object()); + + assertThat(spanKind).isEqualTo(SpanKind.PRODUCER); + } + private static Stream spanKinds() { return Stream.of( - argumentSet("publish", MessageOperation.PUBLISH, SpanKind.PRODUCER, SpanKind.PRODUCER), - argumentSet("receive", MessageOperation.RECEIVE, SpanKind.CONSUMER, SpanKind.CLIENT), - argumentSet("process", MessageOperation.PROCESS, SpanKind.CONSUMER, SpanKind.CONSUMER)); + argumentSet( + "create", MessagingOperationType.CREATE, true, SpanKind.PRODUCER, SpanKind.PRODUCER), + argumentSet( + "send with propagated context", + MessagingOperationType.SEND, + true, + SpanKind.PRODUCER, + SpanKind.PRODUCER), + argumentSet( + "send without propagated context", + MessagingOperationType.SEND, + false, + SpanKind.PRODUCER, + SpanKind.CLIENT), + argumentSet( + "receive", MessagingOperationType.RECEIVE, true, SpanKind.CONSUMER, SpanKind.CLIENT), + argumentSet( + "process", MessagingOperationType.PROCESS, true, SpanKind.CONSUMER, SpanKind.CONSUMER), + argumentSet( + "settle", MessagingOperationType.SETTLE, true, SpanKind.CLIENT, SpanKind.CLIENT)); } } diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractorTest.java index a98517b68f3c..088901a04894 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractorTest.java @@ -31,7 +31,7 @@ void shouldExtractSpanName( boolean isAnonymousQueue, String destinationName, String destinationTemplate, - MessageOperation operation, + MessagingOperationType operationType, String operationName, String oldSpanName, String spanName) { @@ -57,7 +57,9 @@ void shouldExtractSpanName( } SpanNameExtractor underTest = - MessagingSpanNameExtractor.create(getter, operation, operationName); + MessagingSpanNameExtractor.builder(getter, operationType) + .setOperationName(operationName) + .build(); // when String actualSpanName = underTest.extract(message); @@ -74,7 +76,7 @@ static Stream spanNameParams() { false, "destination", null, - MessageOperation.PUBLISH, + MessagingOperationType.SEND, "send", "destination publish", "send destination"), @@ -84,7 +86,7 @@ static Stream spanNameParams() { false, "generated", null, - MessageOperation.PROCESS, + MessagingOperationType.PROCESS, "process", "(temporary) process", "process"), @@ -94,7 +96,7 @@ static Stream spanNameParams() { false, null, null, - MessageOperation.RECEIVE, + MessagingOperationType.RECEIVE, "receive", "unknown receive", "receive"), @@ -104,7 +106,7 @@ static Stream spanNameParams() { false, "customer-42", "customer-{id}", - MessageOperation.PUBLISH, + MessagingOperationType.SEND, "send", "customer-42 publish", "send customer-{id}"), @@ -114,7 +116,7 @@ static Stream spanNameParams() { true, "generated", null, - MessageOperation.PROCESS, + MessagingOperationType.PROCESS, "process", "generated process", "process")); diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SpanKey.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SpanKey.java index 0cf43cc6162a..a7e705ad8d30 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SpanKey.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SpanKey.java @@ -43,12 +43,16 @@ public final class SpanKey { private static final ContextKey DB_CLIENT_KEY = ContextKey.named("opentelemetry-traces-span-key-db-client"); + private static final ContextKey PRODUCER_CREATE_KEY = + ContextKey.named("opentelemetry-traces-span-key-producer-create"); private static final ContextKey PRODUCER_KEY = ContextKey.named("opentelemetry-traces-span-key-producer"); private static final ContextKey CONSUMER_RECEIVE_KEY = ContextKey.named("opentelemetry-traces-span-key-consumer-receive"); private static final ContextKey CONSUMER_PROCESS_KEY = ContextKey.named("opentelemetry-traces-span-key-consumer-process"); + private static final ContextKey CONSUMER_SETTLE_KEY = + ContextKey.named("opentelemetry-traces-span-key-consumer-settle"); /* Span keys */ @@ -66,9 +70,11 @@ public final class SpanKey { public static final SpanKey RPC_CLIENT = new SpanKey(RPC_CLIENT_KEY); public static final SpanKey DB_CLIENT = new SpanKey(DB_CLIENT_KEY); + public static final SpanKey PRODUCER_CREATE = new SpanKey(PRODUCER_CREATE_KEY); public static final SpanKey PRODUCER = new SpanKey(PRODUCER_KEY); public static final SpanKey CONSUMER_RECEIVE = new SpanKey(CONSUMER_RECEIVE_KEY); public static final SpanKey CONSUMER_PROCESS = new SpanKey(CONSUMER_PROCESS_KEY); + public static final SpanKey CONSUMER_SETTLE = new SpanKey(CONSUMER_SETTLE_KEY); private final ContextKey key; diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java index 3a96403c8843..80f62466c5e3 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java @@ -20,9 +20,9 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientMetrics; -import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; @@ -96,21 +96,21 @@ private List, Response>> attributesExtractors( } private AttributesExtractor messagingAttributesExtractor( - MessagingAttributesGetter getter, MessageOperation operation) { - return MessagingAttributesExtractor.builder(getter, operation) + MessagingAttributesGetter getter, MessagingOperationType operationType) { + return MessagingAttributesExtractor.builder(getter, operationType) .setCapturedHeaders(capturedHeaders) .build(); } public Instrumenter> consumerReceiveInstrumenter() { - MessageOperation operation = MessageOperation.RECEIVE; + MessagingOperationType operationType = MessagingOperationType.RECEIVE; SqsReceiveRequestAttributesGetter getter = new SqsReceiveRequestAttributesGetter(); AttributesExtractor> messagingAttributeExtractor = - messagingAttributesExtractor(getter, operation); + messagingAttributesExtractor(getter, operationType); return createInstrumenter( openTelemetry, - MessagingSpanNameExtractor.create(getter, operation), + MessagingSpanNameExtractor.create(getter, operationType), SpanKindExtractor.alwaysConsumer(), toSqsRequestExtractors(attributesExtractors()), singletonList(messagingAttributeExtractor), @@ -119,16 +119,16 @@ public Instrumenter> consumerReceiveInstrumenter( } public Instrumenter> consumerProcessInstrumenter() { - MessageOperation operation = MessageOperation.PROCESS; + MessagingOperationType operationType = MessagingOperationType.PROCESS; SqsProcessRequestAttributesGetter getter = new SqsProcessRequestAttributesGetter(); AttributesExtractor> messagingAttributeExtractor = - messagingAttributesExtractor(getter, operation); + messagingAttributesExtractor(getter, operationType); InstrumenterBuilder> builder = Instrumenter.>builder( openTelemetry, INSTRUMENTATION_NAME, - MessagingSpanNameExtractor.create(getter, operation)) + MessagingSpanNameExtractor.create(getter, operationType)) .addAttributesExtractors(toSqsRequestExtractors(attributesExtractors())) .addAttributesExtractor(messagingAttributeExtractor); setMessagingProcessExceptionEventExtractor(builder); @@ -173,14 +173,14 @@ public void onEnd( } public Instrumenter, Response> producerInstrumenter() { - MessageOperation operation = MessageOperation.PUBLISH; + MessagingOperationType operationType = MessagingOperationType.SEND; SqsAttributesGetter getter = new SqsAttributesGetter(); AttributesExtractor, Response> messagingAttributeExtractor = - messagingAttributesExtractor(getter, operation); + messagingAttributesExtractor(getter, operationType); return createInstrumenter( openTelemetry, - MessagingSpanNameExtractor.create(getter, operation), + MessagingSpanNameExtractor.create(getter, operationType), SpanKindExtractor.alwaysProducer(), attributesExtractors(), singletonList(messagingAttributeExtractor), diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java index c3b154e26a4f..ccc56687f632 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java @@ -24,9 +24,9 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiClientMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiSpanNameExtractor; -import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; @@ -125,21 +125,21 @@ private List> consumerAttribu } private AttributesExtractor messagingAttributesExtractor( - MessagingAttributesGetter getter, MessageOperation operation) { - return MessagingAttributesExtractor.builder(getter, operation) + MessagingAttributesGetter getter, MessagingOperationType operationType) { + return MessagingAttributesExtractor.builder(getter, operationType) .setCapturedHeaders(capturedHeaders) .build(); } public Instrumenter consumerReceiveInstrumenter() { - MessageOperation operation = MessageOperation.RECEIVE; + MessagingOperationType operationType = MessagingOperationType.RECEIVE; SqsReceiveRequestAttributesGetter getter = new SqsReceiveRequestAttributesGetter(); AttributesExtractor messagingAttributeExtractor = - messagingAttributesExtractor(getter, operation); + messagingAttributesExtractor(getter, operationType); return createInstrumenter( openTelemetry, - MessagingSpanNameExtractor.create(getter, operation), + MessagingSpanNameExtractor.create(getter, operationType), SpanKindExtractor.alwaysConsumer(), toSqsRequestExtractors(consumerAttributesExtractors()), singletonList(messagingAttributeExtractor), @@ -148,16 +148,16 @@ public Instrumenter consumerReceiveInstrumenter() { } public Instrumenter consumerProcessInstrumenter() { - MessageOperation operation = MessageOperation.PROCESS; + MessagingOperationType operationType = MessagingOperationType.PROCESS; SqsProcessRequestAttributesGetter getter = new SqsProcessRequestAttributesGetter(); InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, - MessagingSpanNameExtractor.create(getter, operation)) + MessagingSpanNameExtractor.create(getter, operationType)) .addAttributesExtractors(toSqsRequestExtractors(consumerAttributesExtractors())) - .addAttributesExtractor(messagingAttributesExtractor(getter, operation)); + .addAttributesExtractor(messagingAttributesExtractor(getter, operationType)); setMessagingProcessExceptionEventExtractor(builder); if (messagingReceiveInstrumentationEnabled) { @@ -201,14 +201,14 @@ public void onEnd( } public Instrumenter producerInstrumenter() { - MessageOperation operation = MessageOperation.PUBLISH; + MessagingOperationType operationType = MessagingOperationType.SEND; SqsAttributesGetter getter = new SqsAttributesGetter(); AttributesExtractor messagingAttributeExtractor = - messagingAttributesExtractor(getter, operation); + messagingAttributesExtractor(getter, operationType); return createInstrumenter( openTelemetry, - MessagingSpanNameExtractor.create(getter, operation), + MessagingSpanNameExtractor.create(getter, operationType), SpanKindExtractor.alwaysProducer(), attributesExtractors(), singletonList(messagingAttributeExtractor), diff --git a/instrumentation/jms/jms-common-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/common/v1_1/JmsInstrumenterFactory.java b/instrumentation/jms/jms-common-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/common/v1_1/JmsInstrumenterFactory.java index 21cd1f4b198b..9a2c9a23ef23 100644 --- a/instrumentation/jms/jms-common-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/common/v1_1/JmsInstrumenterFactory.java +++ b/instrumentation/jms/jms-common-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/common/v1_1/JmsInstrumenterFactory.java @@ -12,8 +12,8 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -51,28 +51,28 @@ public JmsInstrumenterFactory setMessagingReceiveTelemetryEnabled( public Instrumenter createProducerInstrumenter() { JmsMessageAttributesGetter getter = new JmsMessageAttributesGetter(); - MessageOperation operation = MessageOperation.PUBLISH; + MessagingOperationType operationType = MessagingOperationType.SEND; InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, instrumentationName, - MessagingSpanNameExtractor.create(getter, operation)) - .addAttributesExtractor(createMessagingAttributesExtractor(operation)); + MessagingSpanNameExtractor.create(getter, operationType)) + .addAttributesExtractor(createMessagingAttributesExtractor(operationType)); setMessagingSendExceptionEventExtractor(builder); return builder.buildProducerInstrumenter(new MessagePropertySetter()); } public Instrumenter createConsumerReceiveInstrumenter() { JmsMessageAttributesGetter getter = new JmsMessageAttributesGetter(); - MessageOperation operation = MessageOperation.RECEIVE; + MessagingOperationType operationType = MessagingOperationType.RECEIVE; InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, instrumentationName, - MessagingSpanNameExtractor.create(getter, operation)) - .addAttributesExtractor(createMessagingAttributesExtractor(operation)); + MessagingSpanNameExtractor.create(getter, operationType)) + .addAttributesExtractor(createMessagingAttributesExtractor(operationType)); setMessagingReceiveExceptionEventExtractor(builder); if (messagingReceiveInstrumentationEnabled) { builder.addSpanLinksExtractor( @@ -86,14 +86,14 @@ public Instrumenter createConsumerReceiveInstrumen public Instrumenter createConsumerProcessInstrumenter( boolean canHaveReceiveInstrumentation) { JmsMessageAttributesGetter getter = new JmsMessageAttributesGetter(); - MessageOperation operation = MessageOperation.PROCESS; + MessagingOperationType operationType = MessagingOperationType.PROCESS; InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, instrumentationName, - MessagingSpanNameExtractor.create(getter, operation)) - .addAttributesExtractor(createMessagingAttributesExtractor(operation)); + MessagingSpanNameExtractor.create(getter, operationType)) + .addAttributesExtractor(createMessagingAttributesExtractor(operationType)); setMessagingProcessExceptionEventExtractor(builder); if (canHaveReceiveInstrumentation && messagingReceiveInstrumentationEnabled) { builder.addSpanLinksExtractor( @@ -106,8 +106,8 @@ public Instrumenter createConsumerProcessInstrumen } private AttributesExtractor createMessagingAttributesExtractor( - MessageOperation operation) { - return MessagingAttributesExtractor.builder(new JmsMessageAttributesGetter(), operation) + MessagingOperationType operationType) { + return MessagingAttributesExtractor.builder(new JmsMessageAttributesGetter(), operationType) .setCapturedHeaders(capturedHeaders) .build(); } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java index 3a0654f4b34c..977f0ec3140f 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java @@ -12,9 +12,9 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.ErrorCauseExtractor; @@ -79,15 +79,15 @@ public Instrumenter createProducerInstrume Iterable> extractors) { KafkaProducerAttributesGetter getter = new KafkaProducerAttributesGetter(); - MessageOperation operation = MessageOperation.PUBLISH; + MessagingOperationType operationType = MessagingOperationType.SEND; InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, instrumentationName, - MessagingSpanNameExtractor.create(getter, operation)) + MessagingSpanNameExtractor.create(getter, operationType)) .addAttributesExtractor( - buildMessagingAttributesExtractor(getter, operation, capturedHeaders)) + buildMessagingAttributesExtractor(getter, operationType, capturedHeaders)) .addAttributesExtractors(extractors) .addAttributesExtractor(new KafkaProducerAttributesExtractor()) .setErrorCauseExtractor(errorCauseExtractor); @@ -105,15 +105,15 @@ public Instrumenter createConsumerReceiveInstrumenter public Instrumenter createConsumerReceiveInstrumenter( Iterable> extractors) { KafkaReceiveAttributesGetter getter = new KafkaReceiveAttributesGetter(); - MessageOperation operation = MessageOperation.RECEIVE; + MessagingOperationType operationType = MessagingOperationType.RECEIVE; InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, instrumentationName, - MessagingSpanNameExtractor.create(getter, operation)) + MessagingSpanNameExtractor.create(getter, operationType)) .addAttributesExtractor( - buildMessagingAttributesExtractor(getter, operation, capturedHeaders)) + buildMessagingAttributesExtractor(getter, operationType, capturedHeaders)) .addAttributesExtractor(new KafkaReceiveAttributesExtractor()) .addAttributesExtractors(extractors) .setErrorCauseExtractor(errorCauseExtractor) @@ -129,15 +129,15 @@ public Instrumenter createConsumerProcessInstrumenter public Instrumenter createConsumerProcessInstrumenter( Iterable> extractors) { KafkaConsumerAttributesGetter getter = new KafkaConsumerAttributesGetter(); - MessageOperation operation = MessageOperation.PROCESS; + MessagingOperationType operationType = MessagingOperationType.PROCESS; InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, instrumentationName, - MessagingSpanNameExtractor.create(getter, operation)) + MessagingSpanNameExtractor.create(getter, operationType)) .addAttributesExtractor( - buildMessagingAttributesExtractor(getter, operation, capturedHeaders)) + buildMessagingAttributesExtractor(getter, operationType, capturedHeaders)) .addAttributesExtractor(new KafkaConsumerAttributesExtractor()) .addAttributesExtractors(extractors) .setErrorCauseExtractor(errorCauseExtractor); @@ -159,15 +159,15 @@ public Instrumenter createConsumerProcessInstrumenter public Instrumenter createBatchProcessInstrumenter() { KafkaReceiveAttributesGetter getter = new KafkaReceiveAttributesGetter(); - MessageOperation operation = MessageOperation.PROCESS; + MessagingOperationType operationType = MessagingOperationType.PROCESS; InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, instrumentationName, - MessagingSpanNameExtractor.create(getter, operation)) + MessagingSpanNameExtractor.create(getter, operationType)) .addAttributesExtractor( - buildMessagingAttributesExtractor(getter, operation, capturedHeaders)) + buildMessagingAttributesExtractor(getter, operationType, capturedHeaders)) .addAttributesExtractor(new KafkaReceiveAttributesExtractor()) .addSpanLinksExtractor( new KafkaBatchProcessSpanLinksExtractor( @@ -180,9 +180,9 @@ public Instrumenter createBatchProcessInstrumenter() private static AttributesExtractor buildMessagingAttributesExtractor( MessagingAttributesGetter getter, - MessageOperation operation, + MessagingOperationType operationType, List capturedHeaders) { - return MessagingAttributesExtractor.builder(getter, operation) + return MessagingAttributesExtractor.builder(getter, operationType) .setCapturedHeaders(capturedHeaders) .build(); } diff --git a/instrumentation/kafka/kafka-connect-2.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaconnect/v2_6/KafkaConnectSingletons.java b/instrumentation/kafka/kafka-connect-2.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaconnect/v2_6/KafkaConnectSingletons.java index 21d1fa944a28..a594f59bb6bd 100644 --- a/instrumentation/kafka/kafka-connect-2.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaconnect/v2_6/KafkaConnectSingletons.java +++ b/instrumentation/kafka/kafka-connect-2.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaconnect/v2_6/KafkaConnectSingletons.java @@ -9,8 +9,8 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.context.propagation.TextMapPropagator; -import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; @@ -33,10 +33,10 @@ public class KafkaConnectSingletons { GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, MessagingSpanNameExtractor.create( - new KafkaConnectAttributesGetter(), MessageOperation.PROCESS)) + new KafkaConnectAttributesGetter(), MessagingOperationType.PROCESS)) .addAttributesExtractor( MessagingAttributesExtractor.builder( - new KafkaConnectAttributesGetter(), MessageOperation.PROCESS) + new KafkaConnectAttributesGetter(), MessagingOperationType.PROCESS) .build()) .addSpanLinksExtractor(spanLinksExtractor); setMessagingProcessExceptionEventExtractor(builder); diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java index 3faa961994b5..3e27d43de43d 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java @@ -9,8 +9,8 @@ import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingSendExceptionEventExtractor; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; @@ -30,10 +30,10 @@ public static Instrumenter createProducerInstrumenter( openTelemetry, INSTRUMENTATION_NAME, MessagingSpanNameExtractor.create( - new NatsRequestMessagingAttributesGetter(), MessageOperation.PUBLISH)) + new NatsRequestMessagingAttributesGetter(), MessagingOperationType.SEND)) .addAttributesExtractor( MessagingAttributesExtractor.builder( - new NatsRequestMessagingAttributesGetter(), MessageOperation.PUBLISH) + new NatsRequestMessagingAttributesGetter(), MessagingOperationType.SEND) .setCapturedHeaders(capturedHeaders) .build()); setMessagingSendExceptionEventExtractor(builder); @@ -47,10 +47,10 @@ public static Instrumenter createConsumerProcessInstrumenter( openTelemetry, INSTRUMENTATION_NAME, MessagingSpanNameExtractor.create( - new NatsRequestMessagingAttributesGetter(), MessageOperation.PROCESS)) + new NatsRequestMessagingAttributesGetter(), MessagingOperationType.PROCESS)) .addAttributesExtractor( MessagingAttributesExtractor.builder( - new NatsRequestMessagingAttributesGetter(), MessageOperation.PROCESS) + new NatsRequestMessagingAttributesGetter(), MessagingOperationType.PROCESS) .setCapturedHeaders(capturedHeaders) .build()); setMessagingProcessExceptionEventExtractor(builder); diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java index d235b487720d..4f55d13b7ebe 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java @@ -15,10 +15,10 @@ import io.opentelemetry.context.Scope; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.instrumentation.api.incubator.config.internal.DeclarativeConfigUtil; -import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingConsumerMetrics; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingProcessMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingProducerMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; @@ -78,9 +78,9 @@ private static Instrumenter createConsumerReceiveInstrument Instrumenter.builder( telemetry, INSTRUMENTATION_NAME, - MessagingSpanNameExtractor.create(getter, MessageOperation.RECEIVE)) + MessagingSpanNameExtractor.create(getter, MessagingOperationType.RECEIVE)) .addAttributesExtractor( - createMessagingAttributesExtractor(getter, MessageOperation.RECEIVE)) + createMessagingAttributesExtractor(getter, MessagingOperationType.RECEIVE)) .addOperationMetrics(MessagingConsumerMetrics.get()) .addAttributesExtractor( ServerAttributesExtractor.create(new PulsarNetClientAttributesGetter())); @@ -103,9 +103,9 @@ private static Instrumenter createConsumerBatchReceive Instrumenter.builder( telemetry, INSTRUMENTATION_NAME, - MessagingSpanNameExtractor.create(getter, MessageOperation.RECEIVE)) + MessagingSpanNameExtractor.create(getter, MessagingOperationType.RECEIVE)) .addAttributesExtractor( - createMessagingAttributesExtractor(getter, MessageOperation.RECEIVE)) + createMessagingAttributesExtractor(getter, MessagingOperationType.RECEIVE)) .addAttributesExtractor( ServerAttributesExtractor.create(new PulsarNetClientAttributesGetter())) .addSpanLinksExtractor(new PulsarBatchRequestSpanLinksExtractor(propagator)) @@ -121,9 +121,9 @@ private static Instrumenter createConsumerProcessInstrument Instrumenter.builder( telemetry, INSTRUMENTATION_NAME, - MessagingSpanNameExtractor.create(getter, MessageOperation.PROCESS)) + MessagingSpanNameExtractor.create(getter, MessagingOperationType.PROCESS)) .addAttributesExtractor( - createMessagingAttributesExtractor(getter, MessageOperation.PROCESS)) + createMessagingAttributesExtractor(getter, MessagingOperationType.PROCESS)) .addOperationMetrics(MessagingProcessMetrics.get()); setMessagingProcessExceptionEventExtractor(instrumenterBuilder); @@ -143,9 +143,9 @@ private static Instrumenter createProducerInstrumenter() { Instrumenter.builder( telemetry, INSTRUMENTATION_NAME, - MessagingSpanNameExtractor.create(getter, MessageOperation.PUBLISH)) + MessagingSpanNameExtractor.create(getter, MessagingOperationType.SEND)) .addAttributesExtractor( - createMessagingAttributesExtractor(getter, MessageOperation.PUBLISH)) + createMessagingAttributesExtractor(getter, MessagingOperationType.SEND)) .addAttributesExtractor( ServerAttributesExtractor.create(new PulsarNetClientAttributesGetter())) .addOperationMetrics(MessagingProducerMetrics.get()); @@ -160,8 +160,8 @@ private static Instrumenter createProducerInstrumenter() { } private static AttributesExtractor createMessagingAttributesExtractor( - MessagingAttributesGetter getter, MessageOperation operation) { - return MessagingAttributesExtractor.builder(getter, operation) + MessagingAttributesGetter getter, MessagingOperationType operationType) { + return MessagingAttributesExtractor.builder(getter, operationType) .setCapturedHeaders(capturedHeaders) .build(); } diff --git a/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/v2_7/RabbitSingletons.java b/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/v2_7/RabbitSingletons.java index 7664c4a358f4..4821020c72b7 100644 --- a/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/v2_7/RabbitSingletons.java +++ b/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/v2_7/RabbitSingletons.java @@ -14,9 +14,9 @@ import com.rabbitmq.client.GetResponse; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.context.ContextKey; -import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; @@ -63,7 +63,8 @@ private static Instrumenter createChannelInstrumenter(bo GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, ChannelAndMethod::getMethod) .addAttributesExtractor( buildMessagingAttributesExtractor( - new RabbitChannelAttributesGetter(), publish ? MessageOperation.PUBLISH : null)) + new RabbitChannelAttributesGetter(), + publish ? MessagingOperationType.SEND : null)) .addAttributesExtractor( NetworkAttributesExtractor.create(new RabbitChannelNetAttributesGetter())) .addContextCustomizer( @@ -80,7 +81,7 @@ private static Instrumenter createReceiveInstrument List> extractors = new ArrayList<>(); extractors.add( buildMessagingAttributesExtractor( - new RabbitReceiveAttributesGetter(), MessageOperation.RECEIVE)); + new RabbitReceiveAttributesGetter(), MessagingOperationType.RECEIVE)); extractors.add(NetworkAttributesExtractor.create(new RabbitReceiveNetAttributesGetter())); if (RabbitInstrumenterHelper.CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) { extractors.add(new RabbitReceiveExperimentalAttributesExtractor()); @@ -103,7 +104,7 @@ private static Instrumenter createDeliverInstrumenter() { List> extractors = new ArrayList<>(); extractors.add( buildMessagingAttributesExtractor( - new RabbitDeliveryAttributesGetter(), MessageOperation.PROCESS)); + new RabbitDeliveryAttributesGetter(), MessagingOperationType.PROCESS)); extractors.add(NetworkAttributesExtractor.create(new RabbitDeliveryNetAttributesGetter())); extractors.add(new RabbitDeliveryExtraAttributesExtractor()); if (RabbitInstrumenterHelper.CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) { @@ -119,8 +120,8 @@ private static Instrumenter createDeliverInstrumenter() { } private static AttributesExtractor buildMessagingAttributesExtractor( - MessagingAttributesGetter getter, @Nullable MessageOperation operation) { - return MessagingAttributesExtractor.builder(getter, operation) + MessagingAttributesGetter getter, @Nullable MessagingOperationType operationType) { + return MessagingAttributesExtractor.builder(getter, operationType) .setCapturedHeaders(ExperimentalConfig.get().getMessagingHeaders()) .build(); } diff --git a/instrumentation/rocketmq/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqInstrumenterFactory.java b/instrumentation/rocketmq/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqInstrumenterFactory.java index ef611cf66212..ec363a342b62 100644 --- a/instrumentation/rocketmq/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqInstrumenterFactory.java +++ b/instrumentation/rocketmq/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqInstrumenterFactory.java @@ -11,9 +11,9 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -41,15 +41,15 @@ static Instrumenter createProducerInstrumenter( boolean captureExperimentalSpanAttributes) { RocketMqProducerAttributeGetter getter = new RocketMqProducerAttributeGetter(); - MessageOperation operation = MessageOperation.PUBLISH; + MessagingOperationType operationType = MessagingOperationType.SEND; InstrumenterBuilder instrumenterBuilder = Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, - MessagingSpanNameExtractor.create(getter, operation)) + MessagingSpanNameExtractor.create(getter, operationType)) .addAttributesExtractor( - buildMessagingAttributesExtractor(getter, operation, capturedHeaders)); + buildMessagingAttributesExtractor(getter, operationType, capturedHeaders)); if (captureExperimentalSpanAttributes) { instrumenterBuilder.addAttributesExtractor( new RocketMqProducerExperimentalAttributeExtractor()); @@ -85,16 +85,16 @@ private static Instrumenter createProcessInstrumenter( boolean batch) { RocketMqConsumerAttributeGetter getter = new RocketMqConsumerAttributeGetter(); - MessageOperation operation = MessageOperation.PROCESS; + MessagingOperationType operationType = MessagingOperationType.PROCESS; InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, - MessagingSpanNameExtractor.create(getter, operation)); + MessagingSpanNameExtractor.create(getter, operationType)); builder.addAttributesExtractor( - buildMessagingAttributesExtractor(getter, operation, capturedHeaders)); + buildMessagingAttributesExtractor(getter, operationType, capturedHeaders)); if (captureExperimentalSpanAttributes) { builder.addAttributesExtractor(new RocketMqConsumerExperimentalAttributeExtractor()); } @@ -115,9 +115,9 @@ private static Instrumenter createProcessInstrumenter( private static AttributesExtractor buildMessagingAttributesExtractor( MessagingAttributesGetter getter, - MessageOperation operation, + MessagingOperationType operationType, List capturedHeaders) { - return MessagingAttributesExtractor.builder(getter, operation) + return MessagingAttributesExtractor.builder(getter, operationType) .setCapturedHeaders(capturedHeaders) .build(); } diff --git a/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqConsumerProcessAttributeGetter.java b/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqConsumerProcessAttributeGetter.java index 4a4a01bc3087..32ff58ca7a06 100644 --- a/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqConsumerProcessAttributeGetter.java +++ b/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqConsumerProcessAttributeGetter.java @@ -77,6 +77,13 @@ public Long getBatchMessageCount(MessageView messageView, @Nullable ConsumeResul return null; } + @Nullable + @Override + public String getErrorType( + MessageView messageView, @Nullable ConsumeResult consumeResult, @Nullable Throwable error) { + return consumeResult == ConsumeResult.FAILURE ? ConsumeResult.FAILURE.name() : null; + } + @Override public List getMessageHeader(MessageView messageView, String name) { String value = messageView.getProperties().get(name); diff --git a/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java b/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java index bb6ce5d1d276..7d3409f085a8 100644 --- a/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java +++ b/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java @@ -12,9 +12,9 @@ import apache.rocketmq.v2.ReceiveMessageRequest; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -37,16 +37,16 @@ private RocketMqInstrumenterFactory() {} public static Instrumenter createProducerInstrumenter( OpenTelemetry openTelemetry, List capturedHeaders) { RocketMqProducerAttributeGetter getter = new RocketMqProducerAttributeGetter(); - MessageOperation operation = MessageOperation.PUBLISH; + MessagingOperationType operationType = MessagingOperationType.SEND; AttributesExtractor attributesExtractor = - buildMessagingAttributesExtractor(getter, operation, capturedHeaders); + buildMessagingAttributesExtractor(getter, operationType, capturedHeaders); InstrumenterBuilder instrumenterBuilder = Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, - MessagingSpanNameExtractor.create(getter, operation)) + MessagingSpanNameExtractor.create(getter, operationType)) .addAttributesExtractor(attributesExtractor) .addAttributesExtractor(new RocketMqProducerAttributeExtractor()); setMessagingSendExceptionEventExtractor(instrumenterBuilder); @@ -57,16 +57,16 @@ public static Instrumenter createProduce createConsumerReceiveInstrumenter( OpenTelemetry openTelemetry, List capturedHeaders, boolean enabled) { RocketMqConsumerReceiveAttributeGetter getter = new RocketMqConsumerReceiveAttributeGetter(); - MessageOperation operation = MessageOperation.RECEIVE; + MessagingOperationType operationType = MessagingOperationType.RECEIVE; AttributesExtractor> attributesExtractor = - buildMessagingAttributesExtractor(getter, operation, capturedHeaders); + buildMessagingAttributesExtractor(getter, operationType, capturedHeaders); InstrumenterBuilder> instrumenterBuilder = Instrumenter.>builder( openTelemetry, INSTRUMENTATION_NAME, - MessagingSpanNameExtractor.create(getter, operation)) + MessagingSpanNameExtractor.create(getter, operationType)) .setEnabled(enabled) .addAttributesExtractor(attributesExtractor) .addAttributesExtractor(new RocketMqConsumerReceiveAttributeExtractor()); @@ -79,16 +79,16 @@ public static Instrumenter createConsumerProcessInst List capturedHeaders, boolean receiveInstrumentationEnabled) { RocketMqConsumerProcessAttributeGetter getter = new RocketMqConsumerProcessAttributeGetter(); - MessageOperation operation = MessageOperation.PROCESS; + MessagingOperationType operationType = MessagingOperationType.PROCESS; AttributesExtractor attributesExtractor = - buildMessagingAttributesExtractor(getter, operation, capturedHeaders); + buildMessagingAttributesExtractor(getter, operationType, capturedHeaders); InstrumenterBuilder instrumenterBuilder = Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, - MessagingSpanNameExtractor.create(getter, operation)) + MessagingSpanNameExtractor.create(getter, operationType)) .addAttributesExtractor(attributesExtractor) .addAttributesExtractor(new RocketMqConsumerProcessAttributeExtractor()) .setSpanStatusExtractor( @@ -114,9 +114,9 @@ public static Instrumenter createConsumerProcessInst private static AttributesExtractor buildMessagingAttributesExtractor( MessagingAttributesGetter getter, - MessageOperation operation, + MessagingOperationType operationType, List capturedHeaders) { - return MessagingAttributesExtractor.builder(getter, operation) + return MessagingAttributesExtractor.builder(getter, operationType) .setCapturedHeaders(capturedHeaders) .build(); } diff --git a/instrumentation/spring/spring-integration-4.1/library/src/main/java/io/opentelemetry/instrumentation/spring/integration/v4_1/SpringIntegrationTelemetryBuilder.java b/instrumentation/spring/spring-integration-4.1/library/src/main/java/io/opentelemetry/instrumentation/spring/integration/v4_1/SpringIntegrationTelemetryBuilder.java index 20dea3ca31f7..23e0f6186df8 100644 --- a/instrumentation/spring/spring-integration-4.1/library/src/main/java/io/opentelemetry/instrumentation/spring/integration/v4_1/SpringIntegrationTelemetryBuilder.java +++ b/instrumentation/spring/spring-integration-4.1/library/src/main/java/io/opentelemetry/instrumentation/spring/integration/v4_1/SpringIntegrationTelemetryBuilder.java @@ -12,9 +12,9 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; @@ -84,7 +84,7 @@ public SpringIntegrationTelemetry build() { .addAttributesExtractor( buildMessagingAttributesExtractor( new SpringMessagingAttributesGetter(), - MessageOperation.PROCESS, + MessagingOperationType.PROCESS, capturedHeaders)); setMessagingProcessExceptionEventExtractor(consumerBuilder); Instrumenter consumerInstrumenter = @@ -99,7 +99,7 @@ public SpringIntegrationTelemetry build() { .addAttributesExtractor( buildMessagingAttributesExtractor( new SpringMessagingAttributesGetter(), - MessageOperation.PUBLISH, + MessagingOperationType.SEND, capturedHeaders)); setMessagingSendExceptionEventExtractor(producerBuilder); Instrumenter producerInstrumenter = @@ -121,9 +121,9 @@ private static String producerSpanName(MessageWithChannel messageWithChannel) { private static AttributesExtractor buildMessagingAttributesExtractor( MessagingAttributesGetter getter, - MessageOperation operation, + MessagingOperationType operationType, List capturedHeaders) { - return MessagingAttributesExtractor.builder(getter, operation) + return MessagingAttributesExtractor.builder(getter, operationType) .setCapturedHeaders(capturedHeaders) .build(); } diff --git a/instrumentation/spring/spring-pulsar-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/pulsar/v1_0/SpringPulsarSingletons.java b/instrumentation/spring/spring-pulsar-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/pulsar/v1_0/SpringPulsarSingletons.java index 1f9db736d418..7aae59e3f90f 100644 --- a/instrumentation/spring/spring-pulsar-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/pulsar/v1_0/SpringPulsarSingletons.java +++ b/instrumentation/spring/spring-pulsar-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/pulsar/v1_0/SpringPulsarSingletons.java @@ -9,8 +9,8 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; @@ -26,7 +26,7 @@ public class SpringPulsarSingletons { static { OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); SpringPulsarMessageAttributesGetter getter = new SpringPulsarMessageAttributesGetter(); - MessageOperation operation = MessageOperation.PROCESS; + MessagingOperationType operationType = MessagingOperationType.PROCESS; boolean messagingReceiveInstrumentationEnabled = ExperimentalConfig.get().messagingReceiveInstrumentationEnabled(); @@ -34,9 +34,9 @@ public class SpringPulsarSingletons { Instrumenter., Void>builder( openTelemetry, INSTRUMENTATION_NAME, - MessagingSpanNameExtractor.create(getter, operation)) + MessagingSpanNameExtractor.create(getter, operationType)) .addAttributesExtractor( - MessagingAttributesExtractor.builder(getter, operation) + MessagingAttributesExtractor.builder(getter, operationType) .setCapturedHeaders(ExperimentalConfig.get().getMessagingHeaders()) .build()); setMessagingProcessExceptionEventExtractor(builder); diff --git a/instrumentation/spring/spring-rabbit-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/SpringRabbitSingletons.java b/instrumentation/spring/spring-rabbit-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/SpringRabbitSingletons.java index 0b1c73f8a742..11d844ed96ba 100644 --- a/instrumentation/spring/spring-rabbit-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/SpringRabbitSingletons.java +++ b/instrumentation/spring/spring-rabbit-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/SpringRabbitSingletons.java @@ -8,8 +8,8 @@ import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingProcessExceptionEventExtractor; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; @@ -24,15 +24,15 @@ public class SpringRabbitSingletons { static { SpringRabbitMessageAttributesGetter getter = new SpringRabbitMessageAttributesGetter(); - MessageOperation operation = MessageOperation.PROCESS; + MessagingOperationType operationType = MessagingOperationType.PROCESS; InstrumenterBuilder builder = Instrumenter.builder( GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, - MessagingSpanNameExtractor.create(getter, operation)) + MessagingSpanNameExtractor.create(getter, operationType)) .addAttributesExtractor( - MessagingAttributesExtractor.builder(getter, operation) + MessagingAttributesExtractor.builder(getter, operationType) .setCapturedHeaders(ExperimentalConfig.get().getMessagingHeaders()) .build()); setMessagingProcessExceptionEventExtractor(builder); From 305e38706697d3dd4b89d75ce6330d2c05a16f88 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 17 Jul 2026 08:55:46 -0700 Subject: [PATCH 04/18] Address review comment from copilot-pull-request-reviewer: align receive names and kinds --- .../internal/AwsSdkInstrumenterFactory.java | 3 ++- .../internal/AwsSdkInstrumenterFactory.java | 3 ++- .../common/v1_1/JmsInstrumenterFactory.java | 3 ++- .../v0_11/KafkaClientDefaultTest.java | 20 +++++++++++-------- .../internal/KafkaInstrumenterFactory.java | 3 ++- .../v2_8/telemetry/PulsarSingletons.java | 6 ++++-- .../pulsar/v2_8/AbstractPulsarClientTest.java | 20 ++++++++++++++++--- .../pulsar/v2_8/PulsarClientTest.java | 15 ++++++++------ .../v5_0/RocketMqInstrumenterFactory.java | 3 ++- 9 files changed, 52 insertions(+), 24 deletions(-) diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java index 80f62466c5e3..e0184bfe79c5 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java @@ -23,6 +23,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanKindExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; @@ -111,7 +112,7 @@ public Instrumenter> consumerReceiveInstrumenter( return createInstrumenter( openTelemetry, MessagingSpanNameExtractor.create(getter, operationType), - SpanKindExtractor.alwaysConsumer(), + MessagingSpanKindExtractor.create(operationType), toSqsRequestExtractors(attributesExtractors()), singletonList(messagingAttributeExtractor), builder -> setMessagingReceiveExceptionEventExtractor(builder), diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java index ccc56687f632..384ee949643c 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java @@ -27,6 +27,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanKindExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; @@ -140,7 +141,7 @@ public Instrumenter consumerReceiveInstrumenter() { return createInstrumenter( openTelemetry, MessagingSpanNameExtractor.create(getter, operationType), - SpanKindExtractor.alwaysConsumer(), + MessagingSpanKindExtractor.create(operationType), toSqsRequestExtractors(consumerAttributesExtractors()), singletonList(messagingAttributeExtractor), builder -> setMessagingReceiveExceptionEventExtractor(builder), diff --git a/instrumentation/jms/jms-common-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/common/v1_1/JmsInstrumenterFactory.java b/instrumentation/jms/jms-common-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/common/v1_1/JmsInstrumenterFactory.java index 9a2c9a23ef23..9e25453a5351 100644 --- a/instrumentation/jms/jms-common-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/common/v1_1/JmsInstrumenterFactory.java +++ b/instrumentation/jms/jms-common-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/common/v1_1/JmsInstrumenterFactory.java @@ -14,6 +14,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanKindExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -80,7 +81,7 @@ public Instrumenter createConsumerReceiveInstrumen openTelemetry.getPropagators().getTextMapPropagator(), MessagePropertyGetter.INSTANCE)); } - return builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); + return builder.buildInstrumenter(MessagingSpanKindExtractor.create(operationType)); } public Instrumenter createConsumerProcessInstrumenter( diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java index ea8055f306e1..8157487dabc3 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java @@ -78,7 +78,8 @@ void testKafkaProducerAndConsumerSpan(boolean testHeaders) throws Exception { } AtomicReference producerSpan = new AtomicReference<>(); testing.waitAndAssertSortedTraces( - orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), + orderByRootSpanKind( + SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER), trace -> { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), @@ -103,7 +104,7 @@ void testKafkaProducerAndConsumerSpan(boolean testHeaders) throws Exception { emitStableMessagingSemconv() ? "receive " + SHARED_TOPIC : SHARED_TOPIC + " receive") - .hasKind(SpanKind.CONSUMER) + .hasKind(emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER) .hasNoParent() .hasAttributesSatisfyingExactly(receiveAttributes(testHeaders)), span -> @@ -135,7 +136,8 @@ void testPassThroughTombstone() throws Exception { AtomicReference producerSpan = new AtomicReference<>(); testing.waitAndAssertSortedTraces( - orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), + orderByRootSpanKind( + SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER), trace -> { trace.hasSpansSatisfyingExactly( span -> @@ -155,7 +157,7 @@ void testPassThroughTombstone() throws Exception { emitStableMessagingSemconv() ? "receive " + SHARED_TOPIC : SHARED_TOPIC + " receive") - .hasKind(SpanKind.CONSUMER) + .hasKind(emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER) .hasNoParent() .hasAttributesSatisfyingExactly(receiveAttributes(false)), span -> @@ -203,7 +205,8 @@ void testRecordsWithTopicPartitionKafkaConsume(boolean testListIterator) throws AtomicReference producerSpan = new AtomicReference<>(); testing.waitAndAssertSortedTraces( - orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), + orderByRootSpanKind( + SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER), trace -> { trace.hasSpansSatisfyingExactly( span -> @@ -223,7 +226,7 @@ void testRecordsWithTopicPartitionKafkaConsume(boolean testListIterator) throws emitStableMessagingSemconv() ? "receive " + SHARED_TOPIC : SHARED_TOPIC + " receive") - .hasKind(SpanKind.CONSUMER) + .hasKind(emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER) .hasNoParent() .hasAttributesSatisfyingExactly(receiveAttributes(false)), span -> @@ -276,7 +279,8 @@ void testKafkaHeaderNull() throws Exception { } AtomicReference producerSpan = new AtomicReference<>(); testing.waitAndAssertSortedTraces( - orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), + orderByRootSpanKind( + SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER), trace -> { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), @@ -301,7 +305,7 @@ void testKafkaHeaderNull() throws Exception { emitStableMessagingSemconv() ? "receive " + SHARED_TOPIC : SHARED_TOPIC + " receive") - .hasKind(SpanKind.CONSUMER) + .hasKind(emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER) .hasNoParent() .hasAttributesSatisfyingExactly(receiveAttributes(false)), span -> diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java index 977f0ec3140f..489a7ef971a1 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java @@ -15,6 +15,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanKindExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.ErrorCauseExtractor; @@ -119,7 +120,7 @@ public Instrumenter createConsumerReceiveInstrumenter .setErrorCauseExtractor(errorCauseExtractor) .setEnabled(messagingReceiveInstrumentationEnabled); setMessagingReceiveExceptionEventExtractor(builder); - return builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); + return builder.buildInstrumenter(MessagingSpanKindExtractor.create(operationType)); } public Instrumenter createConsumerProcessInstrumenter() { diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java index 4f55d13b7ebe..c86bf8516690 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java @@ -21,6 +21,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingProcessMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingProducerMetrics; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanKindExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -90,7 +91,7 @@ private static Instrumenter createConsumerReceiveInstrument return instrumenterBuilder .addSpanLinksExtractor( new PropagatorBasedSpanLinksExtractor<>(propagator, MessageTextMapGetter.INSTANCE)) - .buildInstrumenter(SpanKindExtractor.alwaysConsumer()); + .buildInstrumenter(MessagingSpanKindExtractor.create(MessagingOperationType.RECEIVE)); } return instrumenterBuilder.buildConsumerInstrumenter(MessageTextMapGetter.INSTANCE); } @@ -111,7 +112,8 @@ private static Instrumenter createConsumerBatchReceive .addSpanLinksExtractor(new PulsarBatchRequestSpanLinksExtractor(propagator)) .addOperationMetrics(MessagingConsumerMetrics.get()); setMessagingReceiveExceptionEventExtractor(instrumenterBuilder); - return instrumenterBuilder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); + return instrumenterBuilder.buildInstrumenter( + MessagingSpanKindExtractor.create(MessagingOperationType.RECEIVE)); } private static Instrumenter createConsumerProcessInstrumenter() { diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/AbstractPulsarClientTest.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/AbstractPulsarClientTest.java index 26ce8f5b6d36..871aa34218d5 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/AbstractPulsarClientTest.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/AbstractPulsarClientTest.java @@ -6,6 +6,7 @@ package io.opentelemetry.javaagent.instrumentation.pulsar.v2_8; import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.instrumentation.testing.junit.message.MessageHeaderUtil.headerAttributeKey; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; @@ -18,6 +19,8 @@ import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_ID; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_TYPE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -386,10 +389,10 @@ static List sendAttributes( equalTo(SERVER_ADDRESS, brokerHost), equalTo(SERVER_PORT, brokerPort), equalTo(MESSAGING_DESTINATION_NAME, destination), - equalTo(MESSAGING_OPERATION, "publish"), equalTo(MESSAGING_MESSAGE_ID, messageId), satisfies(MESSAGING_MESSAGE_BODY_SIZE, AbstractLongAssert::isNotNegative), equalTo(stringKey("messaging.pulsar.message.type"), experimental("normal")))); + addOperationAttributes(assertions, "publish", "send"); if (testHeaders) { assertions.add(equalTo(headerAttributeKey("Test-Message-Header"), singletonList("test"))); @@ -421,9 +424,9 @@ static List receiveAttributes( equalTo(SERVER_ADDRESS, brokerHost), equalTo(SERVER_PORT, brokerPort), equalTo(MESSAGING_DESTINATION_NAME, destination), - equalTo(MESSAGING_OPERATION, "receive"), equalTo(MESSAGING_MESSAGE_ID, messageId), satisfies(MESSAGING_MESSAGE_BODY_SIZE, AbstractLongAssert::isNotNegative))); + addOperationAttributes(assertions, "receive", "receive"); if (testHeaders) { assertions.add(equalTo(headerAttributeKey("Test-Message-Header"), singletonList("test"))); } @@ -445,9 +448,9 @@ static List processAttributes( asList( equalTo(MESSAGING_SYSTEM, "pulsar"), equalTo(MESSAGING_DESTINATION_NAME, destination), - equalTo(MESSAGING_OPERATION, "process"), equalTo(MESSAGING_MESSAGE_ID, messageId), satisfies(MESSAGING_MESSAGE_BODY_SIZE, AbstractLongAssert::isNotNegative))); + addOperationAttributes(assertions, "process", "process"); if (testHeaders) { assertions.add(equalTo(headerAttributeKey("Test-Message-Header"), singletonList("test"))); } @@ -458,6 +461,17 @@ static List processAttributes( return assertions; } + @SuppressWarnings("deprecation") // using deprecated semconv + private static void addOperationAttributes( + List assertions, String operationName, String operationType) { + if (emitStableMessagingSemconv()) { + assertions.add(equalTo(MESSAGING_OPERATION_NAME, operationName)); + assertions.add(equalTo(MESSAGING_OPERATION_TYPE, operationType)); + } else { + assertions.add(equalTo(MESSAGING_OPERATION, operationName)); + } + } + static void acknowledgeMessage(Consumer consumer, Message message) { try { consumer.acknowledge(message); diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.java index 3199f39a9680..67090327a9af 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.java @@ -86,17 +86,18 @@ void testConsumeNonPartitionedTopic() throws Exception { equalTo(MESSAGING_SYSTEM, "pulsar"), equalTo(MESSAGING_DESTINATION_NAME, topic)) .hasBucketBoundaries(DURATION_BUCKETS))))); - return; } AtomicReference producerSpan = new AtomicReference<>(); testing.waitAndAssertSortedTraces( - orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), + orderByRootSpanKind( + SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER), trace -> { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span -> - span.hasName(topic + " publish") + span.hasName( + emitStableMessagingSemconv() ? "publish " + topic : topic + " publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -107,14 +108,16 @@ void testConsumeNonPartitionedTopic() throws Exception { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName(topic + " receive") - .hasKind(SpanKind.CONSUMER) + span.hasName( + emitStableMessagingSemconv() ? "receive " + topic : topic + " receive") + .hasKind(emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER) .hasNoParent() .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) .hasAttributesSatisfyingExactly( receiveAttributes(topic, msgId.toString(), false)), span -> - span.hasName(topic + " process") + span.hasName( + emitStableMessagingSemconv() ? "process " + topic : topic + " process") .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(0)) .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) diff --git a/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java b/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java index 7d3409f085a8..4537bf20d385 100644 --- a/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java +++ b/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java @@ -15,6 +15,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanKindExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -71,7 +72,7 @@ public static Instrumenter createProduce .addAttributesExtractor(attributesExtractor) .addAttributesExtractor(new RocketMqConsumerReceiveAttributeExtractor()); setMessagingReceiveExceptionEventExtractor(instrumenterBuilder); - return instrumenterBuilder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); + return instrumenterBuilder.buildInstrumenter(MessagingSpanKindExtractor.create(operationType)); } public static Instrumenter createConsumerProcessInstrumenter( From 72caecd5cc8868a9766ee4a6726ecb8b36649606 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 17 Jul 2026 09:08:22 -0700 Subject: [PATCH 05/18] Address review comment from copilot-pull-request-reviewer: bridge create and settle span keys --- .../instrumentationapi/v1_14/SpanKeyBridging.java | 6 ++++++ .../instrumentationapi/v1_14/ContextBridgeTest.java | 4 +++- .../testing/AgentSpanTestingInstrumenter.java | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-api/opentelemetry-instrumentation-api-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationapi/v1_14/SpanKeyBridging.java b/instrumentation/opentelemetry-instrumentation-api/opentelemetry-instrumentation-api-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationapi/v1_14/SpanKeyBridging.java index 24be05a6e8d0..5682b8c418f7 100644 --- a/instrumentation/opentelemetry-instrumentation-api/opentelemetry-instrumentation-api-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationapi/v1_14/SpanKeyBridging.java +++ b/instrumentation/opentelemetry-instrumentation-api/opentelemetry-instrumentation-api-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationapi/v1_14/SpanKeyBridging.java @@ -51,6 +51,9 @@ public class SpanKeyBridging { application.io.opentelemetry.instrumentation.api.internal.SpanKey.DB_CLIENT, SpanKey.DB_CLIENT); + map.put( + application.io.opentelemetry.instrumentation.api.internal.SpanKey.PRODUCER_CREATE, + SpanKey.PRODUCER_CREATE); map.put( application.io.opentelemetry.instrumentation.api.internal.SpanKey.PRODUCER, SpanKey.PRODUCER); @@ -60,6 +63,9 @@ public class SpanKeyBridging { map.put( application.io.opentelemetry.instrumentation.api.internal.SpanKey.CONSUMER_PROCESS, SpanKey.CONSUMER_PROCESS); + map.put( + application.io.opentelemetry.instrumentation.api.internal.SpanKey.CONSUMER_SETTLE, + SpanKey.CONSUMER_SETTLE); return map; } diff --git a/instrumentation/opentelemetry-instrumentation-api/opentelemetry-instrumentation-api-1.14/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/instrumentationapi/v1_14/ContextBridgeTest.java b/instrumentation/opentelemetry-instrumentation-api/opentelemetry-instrumentation-api-1.14/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/instrumentationapi/v1_14/ContextBridgeTest.java index 60ecc0f4201b..7322e0d9569c 100644 --- a/instrumentation/opentelemetry-instrumentation-api/opentelemetry-instrumentation-api-1.14/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/instrumentationapi/v1_14/ContextBridgeTest.java +++ b/instrumentation/opentelemetry-instrumentation-api/opentelemetry-instrumentation-api-1.14/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/instrumentationapi/v1_14/ContextBridgeTest.java @@ -68,9 +68,11 @@ void testSpanKeyBridge() { SpanKey.HTTP_CLIENT, SpanKey.RPC_CLIENT, SpanKey.DB_CLIENT, + SpanKey.PRODUCER_CREATE, SpanKey.PRODUCER, SpanKey.CONSUMER_RECEIVE, - SpanKey.CONSUMER_PROCESS); + SpanKey.CONSUMER_PROCESS, + SpanKey.CONSUMER_SETTLE); spanKeys.forEach( spanKey -> assertThat(spanKey.fromContextOrNull(Context.current())).isNotNull()); diff --git a/instrumentation/opentelemetry-instrumentation-api/opentelemetry-instrumentation-api-1.14/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/testing/AgentSpanTestingInstrumenter.java b/instrumentation/opentelemetry-instrumentation-api/opentelemetry-instrumentation-api-1.14/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/testing/AgentSpanTestingInstrumenter.java index e13499836749..74a821731111 100644 --- a/instrumentation/opentelemetry-instrumentation-api/opentelemetry-instrumentation-api-1.14/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/testing/AgentSpanTestingInstrumenter.java +++ b/instrumentation/opentelemetry-instrumentation-api/opentelemetry-instrumentation-api-1.14/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/testing/AgentSpanTestingInstrumenter.java @@ -73,9 +73,11 @@ private static SpanKey[] getAllSpanKeys() { SpanKey.HTTP_CLIENT, SpanKey.RPC_CLIENT, SpanKey.DB_CLIENT, + SpanKey.PRODUCER_CREATE, SpanKey.PRODUCER, SpanKey.CONSUMER_RECEIVE, SpanKey.CONSUMER_PROCESS, + SpanKey.CONSUMER_SETTLE, }; } From f96268163c73261a707876304c3f890406eb1565 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 17 Jul 2026 09:31:30 -0700 Subject: [PATCH 06/18] Use Kafka-specific messaging operation names --- .../v0_11/KafkaClientDefaultTest.java | 16 +++++----- .../v0_11/internal/KafkaClientBaseTest.java | 4 +-- .../internal/KafkaInstrumenterFactory.java | 29 ++++++++++++++++--- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java index 8157487dabc3..7a240656bdf8 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java @@ -86,7 +86,7 @@ SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CON span -> span.hasName( emitStableMessagingSemconv() - ? "publish " + SHARED_TOPIC + ? "send " + SHARED_TOPIC : SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) @@ -102,7 +102,7 @@ SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CON span -> span.hasName( emitStableMessagingSemconv() - ? "receive " + SHARED_TOPIC + ? "poll " + SHARED_TOPIC : SHARED_TOPIC + " receive") .hasKind(emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER) .hasNoParent() @@ -143,7 +143,7 @@ SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CON span -> span.hasName( emitStableMessagingSemconv() - ? "publish " + SHARED_TOPIC + ? "send " + SHARED_TOPIC : SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasNoParent() @@ -155,7 +155,7 @@ SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CON span -> span.hasName( emitStableMessagingSemconv() - ? "receive " + SHARED_TOPIC + ? "poll " + SHARED_TOPIC : SHARED_TOPIC + " receive") .hasKind(emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER) .hasNoParent() @@ -212,7 +212,7 @@ SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CON span -> span.hasName( emitStableMessagingSemconv() - ? "publish " + SHARED_TOPIC + ? "send " + SHARED_TOPIC : SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasNoParent() @@ -224,7 +224,7 @@ SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CON span -> span.hasName( emitStableMessagingSemconv() - ? "receive " + SHARED_TOPIC + ? "poll " + SHARED_TOPIC : SHARED_TOPIC + " receive") .hasKind(emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER) .hasNoParent() @@ -287,7 +287,7 @@ SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CON span -> span.hasName( emitStableMessagingSemconv() - ? "publish " + SHARED_TOPIC + ? "send " + SHARED_TOPIC : SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) @@ -303,7 +303,7 @@ SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CON span -> span.hasName( emitStableMessagingSemconv() - ? "receive " + SHARED_TOPIC + ? "poll " + SHARED_TOPIC : SHARED_TOPIC + " receive") .hasKind(emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER) .hasNoParent() diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/testing/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaClientBaseTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/testing/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaClientBaseTest.java index c6a8301f571e..c0544cac28f1 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/testing/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaClientBaseTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/testing/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaClientBaseTest.java @@ -185,7 +185,7 @@ protected static List sendAttributes( equalTo(MESSAGING_SYSTEM, "kafka"), equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), equalTo(MESSAGING_OPERATION, emitOldMessagingSemconv() ? "publish" : null), - equalTo(MESSAGING_OPERATION_NAME, emitStableMessagingSemconv() ? "publish" : null), + equalTo(MESSAGING_OPERATION_NAME, emitStableMessagingSemconv() ? "send" : null), equalTo(MESSAGING_OPERATION_TYPE, emitStableMessagingSemconv() ? "send" : null), satisfies(effectiveKey(MESSAGING_CLIENT_ID), val -> val.startsWith("producer")), satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty))); @@ -221,7 +221,7 @@ protected static List receiveAttributes(boolean testHeaders) equalTo(MESSAGING_SYSTEM, "kafka"), equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), equalTo(MESSAGING_OPERATION, emitOldMessagingSemconv() ? "receive" : null), - equalTo(MESSAGING_OPERATION_NAME, emitStableMessagingSemconv() ? "receive" : null), + equalTo(MESSAGING_OPERATION_NAME, emitStableMessagingSemconv() ? "poll" : null), equalTo(MESSAGING_OPERATION_TYPE, emitStableMessagingSemconv() ? "receive" : null), satisfies(effectiveKey(MESSAGING_CLIENT_ID), val -> val.startsWith("consumer")), satisfies(MESSAGING_BATCH_MESSAGE_COUNT, AbstractLongAssert::isPositive))); diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java index 489a7ef971a1..2724c0a7c1f8 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java @@ -34,6 +34,9 @@ */ public final class KafkaInstrumenterFactory { + private static final String SEND_OPERATION_NAME = "send"; + private static final String POLL_OPERATION_NAME = "poll"; + private final OpenTelemetry openTelemetry; private final String instrumentationName; private ErrorCauseExtractor errorCauseExtractor = ErrorCauseExtractor.getDefault(); @@ -86,9 +89,12 @@ public Instrumenter createProducerInstrume Instrumenter.builder( openTelemetry, instrumentationName, - MessagingSpanNameExtractor.create(getter, operationType)) + MessagingSpanNameExtractor.builder(getter, operationType) + .setOperationName(SEND_OPERATION_NAME) + .build()) .addAttributesExtractor( - buildMessagingAttributesExtractor(getter, operationType, capturedHeaders)) + buildMessagingAttributesExtractor( + getter, operationType, SEND_OPERATION_NAME, capturedHeaders)) .addAttributesExtractors(extractors) .addAttributesExtractor(new KafkaProducerAttributesExtractor()) .setErrorCauseExtractor(errorCauseExtractor); @@ -112,9 +118,12 @@ public Instrumenter createConsumerReceiveInstrumenter Instrumenter.builder( openTelemetry, instrumentationName, - MessagingSpanNameExtractor.create(getter, operationType)) + MessagingSpanNameExtractor.builder(getter, operationType) + .setOperationName(POLL_OPERATION_NAME) + .build()) .addAttributesExtractor( - buildMessagingAttributesExtractor(getter, operationType, capturedHeaders)) + buildMessagingAttributesExtractor( + getter, operationType, POLL_OPERATION_NAME, capturedHeaders)) .addAttributesExtractor(new KafkaReceiveAttributesExtractor()) .addAttributesExtractors(extractors) .setErrorCauseExtractor(errorCauseExtractor) @@ -187,4 +196,16 @@ AttributesExtractor buildMessagingAttributesExtractor( .setCapturedHeaders(capturedHeaders) .build(); } + + private static + AttributesExtractor buildMessagingAttributesExtractor( + MessagingAttributesGetter getter, + MessagingOperationType operationType, + String operationName, + List capturedHeaders) { + return MessagingAttributesExtractor.builder(getter, operationType) + .setOperationName(operationName) + .setCapturedHeaders(capturedHeaders) + .build(); + } } From 6d74bf4daa426d5b0c7c5933ec206e65f96417e0 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 17 Jul 2026 09:38:43 -0700 Subject: [PATCH 07/18] Address review comment from copilot-pull-request-reviewer: fix Pulsar preview receive spans --- .../pulsar/pulsar-2.8/javaagent/build.gradle.kts | 2 +- .../pulsar/v2_8/telemetry/PulsarSingletons.java | 4 +++- .../pulsar/v2_8/PulsarClientTest.java | 15 +++++++++++---- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts b/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts index 2d027dc7745b..fc2033772806 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts @@ -55,8 +55,8 @@ tasks { classpath = sourceSets.test.get().runtimeClasspath filter { includeTestsMatching("PulsarClientTest.testConsumeNonPartitionedTopic") + includeTestsMatching("PulsarClientTest.testConsumeNonPartitionedTopicUsingReceive") } - jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=true") jvmArgs("-Dotel.semconv-stability.preview=messaging") jvmArgs("-Dotel.instrumentation.common.v3-preview=true") systemProperty("metadataConfig", "otel.semconv-stability.opt-in=messaging") diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java index c86bf8516690..d557b8e54feb 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java @@ -8,6 +8,7 @@ import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingProcessExceptionEventExtractor; import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingReceiveExceptionEventExtractor; import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingSendExceptionEventExtractor; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; @@ -51,7 +52,8 @@ public class PulsarSingletons { private static final List capturedHeaders = ExperimentalConfig.get().getMessagingHeaders(); private static final boolean receiveInstrumentationEnabled = - ExperimentalConfig.get().messagingReceiveInstrumentationEnabled(); + ExperimentalConfig.get().messagingReceiveInstrumentationEnabled() + || emitStableMessagingSemconv(); private static final Instrumenter consumerProcessInstrumenter = createConsumerProcessInstrumenter(); diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.java index 67090327a9af..91c6095959ca 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.java @@ -147,12 +147,14 @@ void testConsumeNonPartitionedTopicUsingReceive() throws Exception { AtomicReference producerSpan = new AtomicReference<>(); testing.waitAndAssertSortedTraces( - orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), + orderByRootSpanKind( + SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER), trace -> { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span -> - span.hasName(topic + " publish") + span.hasName( + emitStableMessagingSemconv() ? "publish " + topic : topic + " publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -163,13 +165,18 @@ void testConsumeNonPartitionedTopicUsingReceive() throws Exception { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName(topic + " receive") - .hasKind(SpanKind.CONSUMER) + span.hasName( + emitStableMessagingSemconv() ? "receive " + topic : topic + " receive") + .hasKind(emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER) .hasNoParent() .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) .hasAttributesSatisfyingExactly( receiveAttributes(topic, msgId.toString(), false)))); + if (emitStableMessagingSemconv()) { + return; + } + assertThat(testing.metrics()) .filteredOn( metric -> From 2a4d610c7d4f0203b87694ad7945676e1a9fc8ad Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 17 Jul 2026 13:13:26 -0700 Subject: [PATCH 08/18] Preserve ambient parent for messaging process spans --- .../MessagingProcessContextCustomizer.java | 47 ++++++++++++ .../MessagingProcessInstrumenterFactory.java | 39 ++++++++++ ...MessagingProcessContextCustomizerTest.java | 73 +++++++++++++++++++ .../internal/AwsSdkInstrumenterFactory.java | 10 ++- .../v1_11/internal/TracingIterator.java | 7 +- .../internal/AwsSdkInstrumenterFactory.java | 11 ++- .../awssdk/v2_2/internal/TracingIterator.java | 7 +- .../common/v1_1/JmsInstrumenterFactory.java | 15 ++-- .../internal/KafkaInstrumenterFactory.java | 16 ++-- .../internal/NatsInstrumenterFactory.java | 7 +- .../v2_8/telemetry/PulsarSingletons.java | 24 +++--- .../pulsar/v2_8/PulsarClientTest.java | 33 ++++++--- .../rabbitmq/v2_7/RabbitSingletons.java | 7 +- .../v4_8/RocketMqInstrumenterFactory.java | 19 ++--- .../v5_0/RocketMqInstrumenterFactory.java | 17 ++--- .../SpringIntegrationTelemetryBuilder.java | 7 +- .../pulsar/v1_0/SpringPulsarSingletons.java | 17 ++--- .../rabbit/v1_0/SpringRabbitSingletons.java | 12 ++- 18 files changed, 284 insertions(+), 84 deletions(-) create mode 100644 instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/internal/MessagingProcessContextCustomizer.java create mode 100644 instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/internal/MessagingProcessInstrumenterFactory.java create mode 100644 instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/internal/MessagingProcessContextCustomizerTest.java diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/internal/MessagingProcessContextCustomizer.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/internal/MessagingProcessContextCustomizer.java new file mode 100644 index 000000000000..7408b102c022 --- /dev/null +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/internal/MessagingProcessContextCustomizer.java @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; +import java.util.function.BiFunction; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class MessagingProcessContextCustomizer + implements ContextCustomizer { + + public static ContextCustomizer create( + TextMapPropagator propagator, TextMapGetter getter) { + return create((parentContext, request) -> propagator.extract(parentContext, request, getter)); + } + + public static ContextCustomizer create( + BiFunction producerContextExtractor) { + return new MessagingProcessContextCustomizer<>(producerContextExtractor); + } + + private final BiFunction producerContextExtractor; + + private MessagingProcessContextCustomizer( + BiFunction producerContextExtractor) { + this.producerContextExtractor = producerContextExtractor; + } + + @Override + public Context onStart(Context parentContext, REQUEST request, Attributes startAttributes) { + if (Span.fromContext(parentContext).getSpanContext().isValid()) { + return parentContext; + } + return producerContextExtractor.apply(parentContext, request); + } +} diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/internal/MessagingProcessInstrumenterFactory.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/internal/MessagingProcessInstrumenterFactory.java new file mode 100644 index 000000000000..0bd7ae424448 --- /dev/null +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/internal/MessagingProcessInstrumenterFactory.java @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal; + +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; + +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; +import io.opentelemetry.instrumentation.api.internal.PropagatorBasedSpanLinksExtractor; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class MessagingProcessInstrumenterFactory { + + public static Instrumenter create( + InstrumenterBuilder builder, + TextMapPropagator propagator, + TextMapGetter getter, + boolean receiveInstrumentationEnabled) { + if (emitStableMessagingSemconv() || receiveInstrumentationEnabled) { + builder.addSpanLinksExtractor(new PropagatorBasedSpanLinksExtractor<>(propagator, getter)); + if (emitStableMessagingSemconv()) { + builder.addContextCustomizer(MessagingProcessContextCustomizer.create(propagator, getter)); + } + return builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); + } + return builder.buildConsumerInstrumenter(getter); + } + + private MessagingProcessInstrumenterFactory() {} +} diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/internal/MessagingProcessContextCustomizerTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/internal/MessagingProcessContextCustomizerTest.java new file mode 100644 index 000000000000..d7507249a818 --- /dev/null +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/internal/MessagingProcessContextCustomizerTest.java @@ -0,0 +1,73 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class MessagingProcessContextCustomizerTest { + + private static final TextMapGetter> getter = + new TextMapGetter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + + @Override + public String get(Map carrier, String key) { + return carrier.get(key); + } + }; + + private static final ContextCustomizer> underTest = + MessagingProcessContextCustomizer.create(W3CTraceContextPropagator.getInstance(), getter); + + @Test + void preservesAmbientParent() { + Context ambient = context("11111111111111111111111111111111", "1111111111111111"); + + Context result = underTest.onStart(ambient, carrier(), Attributes.empty()); + + assertThat(result).isSameAs(ambient); + } + + @Test + void usesProducerWhenAmbientParentIsMissing() { + Context result = underTest.onStart(Context.root(), carrier(), Attributes.empty()); + + assertThat(Span.fromContext(result).getSpanContext()) + .isEqualTo(spanContext("22222222222222222222222222222222", "2222222222222222")); + } + + private static Map carrier() { + Map carrier = new HashMap<>(); + carrier.put("traceparent", "00-22222222222222222222222222222222-2222222222222222-01"); + return Collections.unmodifiableMap(carrier); + } + + private static Context context(String traceId, String spanId) { + return Context.root().with(Span.wrap(spanContext(traceId, spanId))); + } + + private static SpanContext spanContext(String traceId, String spanId) { + return SpanContext.createFromRemoteParent( + traceId, spanId, TraceFlags.getSampled(), TraceState.getDefault()); + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java index e0184bfe79c5..d6719ff75dae 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java @@ -9,6 +9,7 @@ import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingProcessExceptionEventExtractor; import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingReceiveExceptionEventExtractor; import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingSendExceptionEventExtractor; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.internal.RpcExceptionEventExtractors.setRpcClientExceptionEventExtractor; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -25,6 +26,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanKindExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingProcessContextCustomizer; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -134,7 +136,7 @@ public Instrumenter> consumerProcessInstrumenter( .addAttributesExtractor(messagingAttributeExtractor); setMessagingProcessExceptionEventExtractor(builder); - if (messagingReceiveInstrumentationEnabled) { + if (emitStableMessagingSemconv() || messagingReceiveInstrumentationEnabled) { builder.addSpanLinksExtractor( (spanLinks, parentContext, request) -> { Context extracted = @@ -142,6 +144,12 @@ public Instrumenter> consumerProcessInstrumenter( spanLinks.addLink(Span.fromContext(extracted).getSpanContext()); }); } + if (emitStableMessagingSemconv()) { + builder.addContextCustomizer( + MessagingProcessContextCustomizer.create( + (parentContext, request) -> + SqsParentContext.ofSystemAttributes(request.getMessage().getAttributes()))); + } return builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/TracingIterator.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/TracingIterator.java index 54314671e579..24e27ffef2c7 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/TracingIterator.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/TracingIterator.java @@ -5,6 +5,8 @@ package io.opentelemetry.instrumentation.awssdk.v1_11.internal; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; + import com.amazonaws.Request; import com.amazonaws.Response; import com.amazonaws.services.sqs.model.Message; @@ -72,7 +74,10 @@ public Message next() { if (next != null) { Context parentContext = receiveContext; if (parentContext == null) { - parentContext = SqsParentContext.ofSystemAttributes(next.getAttributes()); + parentContext = + emitStableMessagingSemconv() + ? Context.current() + : SqsParentContext.ofSystemAttributes(next.getAttributes()); } currentRequest = SqsProcessRequest.create(request, SqsMessageImpl.wrap(next)); diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java index 384ee949643c..759eeb77316b 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java @@ -11,6 +11,7 @@ import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingReceiveExceptionEventExtractor; import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingSendExceptionEventExtractor; import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.internal.RpcExceptionEventExtractors.setRpcClientExceptionEventExtractor; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -29,6 +30,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanKindExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingProcessContextCustomizer; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -161,7 +163,7 @@ public Instrumenter consumerProcessInstrumenter() { .addAttributesExtractor(messagingAttributesExtractor(getter, operationType)); setMessagingProcessExceptionEventExtractor(builder); - if (messagingReceiveInstrumentationEnabled) { + if (emitStableMessagingSemconv() || messagingReceiveInstrumentationEnabled) { builder.addSpanLinksExtractor( (spanLinks, parentContext, request) -> { Context extracted = @@ -170,6 +172,13 @@ public Instrumenter consumerProcessInstrumenter() { spanLinks.addLink(Span.fromContext(extracted).getSpanContext()); }); } + if (emitStableMessagingSemconv()) { + builder.addContextCustomizer( + MessagingProcessContextCustomizer.create( + (parentContext, request) -> + SqsParentContext.ofMessage( + request.getMessage(), messagingPropagator, useXrayPropagator))); + } return builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingIterator.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingIterator.java index 74bf989ed540..e47dd8f864dd 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingIterator.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingIterator.java @@ -5,6 +5,8 @@ package io.opentelemetry.instrumentation.awssdk.v2_2.internal; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; + import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -77,7 +79,10 @@ public Message next() { SqsMessage sqsMessage = SqsMessageImpl.wrap(next); Context parentContext = receiveContext; if (parentContext == null) { - parentContext = SqsParentContext.ofMessage(sqsMessage, config); + parentContext = + emitStableMessagingSemconv() + ? Context.current() + : SqsParentContext.ofMessage(sqsMessage, config); } currentRequest = SqsProcessRequest.create(request, sqsMessage); diff --git a/instrumentation/jms/jms-common-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/common/v1_1/JmsInstrumenterFactory.java b/instrumentation/jms/jms-common-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/common/v1_1/JmsInstrumenterFactory.java index 9e25453a5351..e180962f59a5 100644 --- a/instrumentation/jms/jms-common-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/common/v1_1/JmsInstrumenterFactory.java +++ b/instrumentation/jms/jms-common-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/common/v1_1/JmsInstrumenterFactory.java @@ -16,10 +16,10 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanKindExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingProcessInstrumenterFactory; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; -import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; import io.opentelemetry.instrumentation.api.internal.PropagatorBasedSpanLinksExtractor; import java.util.ArrayList; import java.util.Collection; @@ -96,14 +96,11 @@ public Instrumenter createConsumerProcessInstrumen MessagingSpanNameExtractor.create(getter, operationType)) .addAttributesExtractor(createMessagingAttributesExtractor(operationType)); setMessagingProcessExceptionEventExtractor(builder); - if (canHaveReceiveInstrumentation && messagingReceiveInstrumentationEnabled) { - builder.addSpanLinksExtractor( - new PropagatorBasedSpanLinksExtractor<>( - openTelemetry.getPropagators().getTextMapPropagator(), - MessagePropertyGetter.INSTANCE)); - return builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); - } - return builder.buildConsumerInstrumenter(MessagePropertyGetter.INSTANCE); + return MessagingProcessInstrumenterFactory.create( + builder, + openTelemetry.getPropagators().getTextMapPropagator(), + MessagePropertyGetter.INSTANCE, + canHaveReceiveInstrumentation && messagingReceiveInstrumentationEnabled); } private AttributesExtractor createMessagingAttributesExtractor( diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java index 2724c0a7c1f8..f5fc4befd693 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java @@ -17,12 +17,12 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanKindExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingProcessInstrumenterFactory; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.ErrorCauseExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; -import io.opentelemetry.instrumentation.api.internal.PropagatorBasedSpanLinksExtractor; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -156,15 +156,11 @@ public Instrumenter createConsumerProcessInstrumenter } setMessagingProcessExceptionEventExtractor(builder); - if (messagingReceiveInstrumentationEnabled) { - builder.addSpanLinksExtractor( - new PropagatorBasedSpanLinksExtractor<>( - openTelemetry.getPropagators().getTextMapPropagator(), - new KafkaConsumerRecordGetter())); - return builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); - } else { - return builder.buildConsumerInstrumenter(new KafkaConsumerRecordGetter()); - } + return MessagingProcessInstrumenterFactory.create( + builder, + openTelemetry.getPropagators().getTextMapPropagator(), + new KafkaConsumerRecordGetter(), + messagingReceiveInstrumentationEnabled); } public Instrumenter createBatchProcessInstrumenter() { diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java index 3e27d43de43d..f7922429d592 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java @@ -12,6 +12,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingProcessInstrumenterFactory; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import java.util.List; @@ -55,7 +56,11 @@ public static Instrumenter createConsumerProcessInstrumenter( .build()); setMessagingProcessExceptionEventExtractor(builder); - return builder.buildConsumerInstrumenter(new NatsRequestTextMapGetter()); + return MessagingProcessInstrumenterFactory.create( + builder, + openTelemetry.getPropagators().getTextMapPropagator(), + new NatsRequestTextMapGetter(), + false); } private NatsInstrumenterFactory() {} diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java index d557b8e54feb..3beac3603e4c 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java @@ -24,11 +24,10 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingProducerMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanKindExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingProcessInstrumenterFactory; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; -import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.SpanLinksExtractor; import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; import io.opentelemetry.instrumentation.api.internal.PropagatorBasedSpanLinksExtractor; import io.opentelemetry.instrumentation.api.internal.Timer; @@ -52,8 +51,7 @@ public class PulsarSingletons { private static final List capturedHeaders = ExperimentalConfig.get().getMessagingHeaders(); private static final boolean receiveInstrumentationEnabled = - ExperimentalConfig.get().messagingReceiveInstrumentationEnabled() - || emitStableMessagingSemconv(); + ExperimentalConfig.get().messagingReceiveInstrumentationEnabled(); private static final Instrumenter consumerProcessInstrumenter = createConsumerProcessInstrumenter(); @@ -89,7 +87,7 @@ private static Instrumenter createConsumerReceiveInstrument ServerAttributesExtractor.create(new PulsarNetClientAttributesGetter())); setMessagingReceiveExceptionEventExtractor(instrumenterBuilder); - if (receiveInstrumentationEnabled) { + if (emitStableMessagingSemconv() || receiveInstrumentationEnabled) { return instrumenterBuilder .addSpanLinksExtractor( new PropagatorBasedSpanLinksExtractor<>(propagator, MessageTextMapGetter.INSTANCE)) @@ -131,13 +129,11 @@ private static Instrumenter createConsumerProcessInstrument .addOperationMetrics(MessagingProcessMetrics.get()); setMessagingProcessExceptionEventExtractor(instrumenterBuilder); - if (receiveInstrumentationEnabled) { - SpanLinksExtractor spanLinksExtractor = - new PropagatorBasedSpanLinksExtractor<>(propagator, MessageTextMapGetter.INSTANCE); - instrumenterBuilder.addSpanLinksExtractor(spanLinksExtractor); - return instrumenterBuilder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); - } - return instrumenterBuilder.buildConsumerInstrumenter(MessageTextMapGetter.INSTANCE); + return MessagingProcessInstrumenterFactory.create( + instrumenterBuilder, + propagator, + MessageTextMapGetter.INSTANCE, + receiveInstrumentationEnabled); } private static Instrumenter createProducerInstrumenter() { @@ -191,7 +187,9 @@ public static Context startAndEndConsumerReceive( if (MessageListenerContext.isProcessing()) { return null; } - parent = propagator.extract(parent, request, MessageTextMapGetter.INSTANCE); + if (!emitStableMessagingSemconv()) { + parent = propagator.extract(parent, request, MessageTextMapGetter.INSTANCE); + } } Context receiveContext = InstrumenterUtil.startAndEnd( diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.java index 91c6095959ca..12ec920b9075 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.java @@ -86,18 +86,35 @@ void testConsumeNonPartitionedTopic() throws Exception { equalTo(MESSAGING_SYSTEM, "pulsar"), equalTo(MESSAGING_DESTINATION_NAME, topic)) .hasBucketBoundaries(DURATION_BUCKETS))))); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("publish " + topic) + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + sendAttributes(topic, msgId.toString(), false)), + span -> + span.hasName("process " + topic) + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasLinks(LinkData.create(trace.getSpan(1).getSpanContext())) + .hasAttributesSatisfyingExactly( + processAttributes(topic, msgId.toString(), false)))); + return; } AtomicReference producerSpan = new AtomicReference<>(); testing.waitAndAssertSortedTraces( - orderByRootSpanKind( - SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER), + orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), trace -> { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span -> - span.hasName( - emitStableMessagingSemconv() ? "publish " + topic : topic + " publish") + span.hasName(topic + " publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -108,16 +125,14 @@ SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CON trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName( - emitStableMessagingSemconv() ? "receive " + topic : topic + " receive") - .hasKind(emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER) + span.hasName(topic + " receive") + .hasKind(SpanKind.CONSUMER) .hasNoParent() .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) .hasAttributesSatisfyingExactly( receiveAttributes(topic, msgId.toString(), false)), span -> - span.hasName( - emitStableMessagingSemconv() ? "process " + topic : topic + " process") + span.hasName(topic + " process") .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(0)) .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) diff --git a/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/v2_7/RabbitSingletons.java b/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/v2_7/RabbitSingletons.java index 4821020c72b7..3907033af555 100644 --- a/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/v2_7/RabbitSingletons.java +++ b/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/v2_7/RabbitSingletons.java @@ -17,6 +17,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingProcessInstrumenterFactory; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; @@ -116,7 +117,11 @@ private static Instrumenter createDeliverInstrumenter() { GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, DeliveryRequest::spanName) .addAttributesExtractors(extractors); setMessagingProcessExceptionEventExtractor(builder); - return builder.buildConsumerInstrumenter(new DeliveryRequestGetter()); + return MessagingProcessInstrumenterFactory.create( + builder, + GlobalOpenTelemetry.getPropagators().getTextMapPropagator(), + new DeliveryRequestGetter(), + false); } private static AttributesExtractor buildMessagingAttributesExtractor( diff --git a/instrumentation/rocketmq/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqInstrumenterFactory.java b/instrumentation/rocketmq/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqInstrumenterFactory.java index ec363a342b62..22cab982fd2a 100644 --- a/instrumentation/rocketmq/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqInstrumenterFactory.java +++ b/instrumentation/rocketmq/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqInstrumenterFactory.java @@ -15,12 +15,11 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingProcessInstrumenterFactory; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.SpanLinksExtractor; -import io.opentelemetry.instrumentation.api.internal.PropagatorBasedSpanLinksExtractor; import java.util.List; import org.apache.rocketmq.client.hook.SendMessageContext; import org.apache.rocketmq.common.message.MessageExt; @@ -100,17 +99,11 @@ private static Instrumenter createProcessInstrumenter( } setMessagingProcessExceptionEventExtractor(builder); - if (batch) { - SpanLinksExtractor spanLinksExtractor = - new PropagatorBasedSpanLinksExtractor<>( - openTelemetry.getPropagators().getTextMapPropagator(), new TextMapExtractAdapter()); - - return builder - .addSpanLinksExtractor(spanLinksExtractor) - .buildInstrumenter(SpanKindExtractor.alwaysConsumer()); - } else { - return builder.buildConsumerInstrumenter(new TextMapExtractAdapter()); - } + return MessagingProcessInstrumenterFactory.create( + builder, + openTelemetry.getPropagators().getTextMapPropagator(), + new TextMapExtractAdapter(), + batch); } private static AttributesExtractor buildMessagingAttributesExtractor( diff --git a/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java b/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java index 4537bf20d385..5351065fd5d9 100644 --- a/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java +++ b/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java @@ -17,13 +17,11 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanKindExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingProcessInstrumenterFactory; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; -import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.SpanLinksExtractor; import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor; -import io.opentelemetry.instrumentation.api.internal.PropagatorBasedSpanLinksExtractor; import java.util.List; import org.apache.rocketmq.client.apis.consumer.ConsumeResult; import org.apache.rocketmq.client.apis.message.MessageView; @@ -103,14 +101,11 @@ public static Instrumenter createConsumerProcessInst }); setMessagingProcessExceptionEventExtractor(instrumenterBuilder); - if (receiveInstrumentationEnabled) { - SpanLinksExtractor spanLinksExtractor = - new PropagatorBasedSpanLinksExtractor<>( - openTelemetry.getPropagators().getTextMapPropagator(), new MessageMapGetter()); - instrumenterBuilder.addSpanLinksExtractor(spanLinksExtractor); - return instrumenterBuilder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); - } - return instrumenterBuilder.buildConsumerInstrumenter(new MessageMapGetter()); + return MessagingProcessInstrumenterFactory.create( + instrumenterBuilder, + openTelemetry.getPropagators().getTextMapPropagator(), + new MessageMapGetter(), + receiveInstrumentationEnabled); } private static AttributesExtractor buildMessagingAttributesExtractor( diff --git a/instrumentation/spring/spring-integration-4.1/library/src/main/java/io/opentelemetry/instrumentation/spring/integration/v4_1/SpringIntegrationTelemetryBuilder.java b/instrumentation/spring/spring-integration-4.1/library/src/main/java/io/opentelemetry/instrumentation/spring/integration/v4_1/SpringIntegrationTelemetryBuilder.java index 23e0f6186df8..e8f1f45f4000 100644 --- a/instrumentation/spring/spring-integration-4.1/library/src/main/java/io/opentelemetry/instrumentation/spring/integration/v4_1/SpringIntegrationTelemetryBuilder.java +++ b/instrumentation/spring/spring-integration-4.1/library/src/main/java/io/opentelemetry/instrumentation/spring/integration/v4_1/SpringIntegrationTelemetryBuilder.java @@ -15,6 +15,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingProcessInstrumenterFactory; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; @@ -88,7 +89,11 @@ public SpringIntegrationTelemetry build() { capturedHeaders)); setMessagingProcessExceptionEventExtractor(consumerBuilder); Instrumenter consumerInstrumenter = - consumerBuilder.buildConsumerInstrumenter(MessageHeadersGetter.INSTANCE); + MessagingProcessInstrumenterFactory.create( + consumerBuilder, + openTelemetry.getPropagators().getTextMapPropagator(), + MessageHeadersGetter.INSTANCE, + false); InstrumenterBuilder producerBuilder = Instrumenter.builder( diff --git a/instrumentation/spring/spring-pulsar-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/pulsar/v1_0/SpringPulsarSingletons.java b/instrumentation/spring/spring-pulsar-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/pulsar/v1_0/SpringPulsarSingletons.java index 7aae59e3f90f..dc22c41b5b38 100644 --- a/instrumentation/spring/spring-pulsar-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/pulsar/v1_0/SpringPulsarSingletons.java +++ b/instrumentation/spring/spring-pulsar-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/pulsar/v1_0/SpringPulsarSingletons.java @@ -12,10 +12,9 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingProcessInstrumenterFactory; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; -import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; -import io.opentelemetry.instrumentation.api.internal.PropagatorBasedSpanLinksExtractor; import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig; import org.apache.pulsar.client.api.Message; @@ -40,14 +39,12 @@ public class SpringPulsarSingletons { .setCapturedHeaders(ExperimentalConfig.get().getMessagingHeaders()) .build()); setMessagingProcessExceptionEventExtractor(builder); - if (messagingReceiveInstrumentationEnabled) { - builder.addSpanLinksExtractor( - new PropagatorBasedSpanLinksExtractor<>( - openTelemetry.getPropagators().getTextMapPropagator(), new MessageHeaderGetter())); - instrumenter = builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); - } else { - instrumenter = builder.buildConsumerInstrumenter(new MessageHeaderGetter()); - } + instrumenter = + MessagingProcessInstrumenterFactory.create( + builder, + openTelemetry.getPropagators().getTextMapPropagator(), + new MessageHeaderGetter(), + messagingReceiveInstrumentationEnabled); } public static Instrumenter, Void> instrumenter() { diff --git a/instrumentation/spring/spring-rabbit-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/SpringRabbitSingletons.java b/instrumentation/spring/spring-rabbit-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/SpringRabbitSingletons.java index 11d844ed96ba..3d77533d9aad 100644 --- a/instrumentation/spring/spring-rabbit-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/SpringRabbitSingletons.java +++ b/instrumentation/spring/spring-rabbit-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/SpringRabbitSingletons.java @@ -8,9 +8,11 @@ import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingProcessExceptionEventExtractor; import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingProcessInstrumenterFactory; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig; @@ -23,12 +25,13 @@ public class SpringRabbitSingletons { private static final Instrumenter instrumenter; static { + OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); SpringRabbitMessageAttributesGetter getter = new SpringRabbitMessageAttributesGetter(); MessagingOperationType operationType = MessagingOperationType.PROCESS; InstrumenterBuilder builder = Instrumenter.builder( - GlobalOpenTelemetry.get(), + openTelemetry, INSTRUMENTATION_NAME, MessagingSpanNameExtractor.create(getter, operationType)) .addAttributesExtractor( @@ -37,7 +40,12 @@ public class SpringRabbitSingletons { .build()); setMessagingProcessExceptionEventExtractor(builder); - instrumenter = builder.buildConsumerInstrumenter(new MessageHeaderGetter()); + instrumenter = + MessagingProcessInstrumenterFactory.create( + builder, + openTelemetry.getPropagators().getTextMapPropagator(), + new MessageHeaderGetter(), + false); } public static Instrumenter instrumenter() { From c2bbe5d0a5285fa345bf0c2a2f8837e339ca8fb3 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 17 Jul 2026 14:08:27 -0700 Subject: [PATCH 09/18] Address review comments from Copilot: preserve span key bridge compatibility --- .../v1_14/SpanKeyBridging.java | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-api/opentelemetry-instrumentation-api-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationapi/v1_14/SpanKeyBridging.java b/instrumentation/opentelemetry-instrumentation-api/opentelemetry-instrumentation-api-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationapi/v1_14/SpanKeyBridging.java index 5682b8c418f7..e588faf6bd38 100644 --- a/instrumentation/opentelemetry-instrumentation-api/opentelemetry-instrumentation-api-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationapi/v1_14/SpanKeyBridging.java +++ b/instrumentation/opentelemetry-instrumentation-api/opentelemetry-instrumentation-api-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationapi/v1_14/SpanKeyBridging.java @@ -51,9 +51,7 @@ public class SpanKeyBridging { application.io.opentelemetry.instrumentation.api.internal.SpanKey.DB_CLIENT, SpanKey.DB_CLIENT); - map.put( - application.io.opentelemetry.instrumentation.api.internal.SpanKey.PRODUCER_CREATE, - SpanKey.PRODUCER_CREATE); + putIfPresent(map, "PRODUCER_CREATE", SpanKey.PRODUCER_CREATE); map.put( application.io.opentelemetry.instrumentation.api.internal.SpanKey.PRODUCER, SpanKey.PRODUCER); @@ -63,12 +61,26 @@ public class SpanKeyBridging { map.put( application.io.opentelemetry.instrumentation.api.internal.SpanKey.CONSUMER_PROCESS, SpanKey.CONSUMER_PROCESS); - map.put( - application.io.opentelemetry.instrumentation.api.internal.SpanKey.CONSUMER_SETTLE, - SpanKey.CONSUMER_SETTLE); + putIfPresent(map, "CONSUMER_SETTLE", SpanKey.CONSUMER_SETTLE); return map; } + private static void putIfPresent( + Map map, + String fieldName, + SpanKey agentSpanKey) { + try { + application.io.opentelemetry.instrumentation.api.internal.SpanKey applicationSpanKey = + (application.io.opentelemetry.instrumentation.api.internal.SpanKey) + application.io.opentelemetry.instrumentation.api.internal.SpanKey.class + .getField(fieldName) + .get(null); + map.put(applicationSpanKey, agentSpanKey); + } catch (ReflectiveOperationException ignored) { + // The application may use an instrumentation-api version from before this key was added. + } + } + @Nullable public static SpanKey toAgentOrNull( application.io.opentelemetry.instrumentation.api.internal.SpanKey applicationSpanKey) { From 1056425b18fa9e4c16f6e6ef99214878308b7d2a Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 17 Jul 2026 14:16:36 -0700 Subject: [PATCH 10/18] Address review comment from Copilot: align RabbitMQ receive span kind --- .../rabbitmq-2.7/javaagent/build.gradle.kts | 15 ++++++++++++++- .../rabbitmq/v2_7/RabbitSingletons.java | 5 +++-- .../rabbitmq/v2_7/RabbitMqTest.java | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/instrumentation/rabbitmq-2.7/javaagent/build.gradle.kts b/instrumentation/rabbitmq-2.7/javaagent/build.gradle.kts index 60e5e6bd09f4..48de77386049 100644 --- a/instrumentation/rabbitmq-2.7/javaagent/build.gradle.kts +++ b/instrumentation/rabbitmq-2.7/javaagent/build.gradle.kts @@ -44,7 +44,20 @@ tasks { systemProperty("metadataConfig", "otel.instrumentation.rabbitmq.experimental-span-attributes=true") } + // The task name follows the existing pattern; messaging conventions are still preview, and this + // task is limited to the receive span-kind path migrated here. + val testStableSemconv = register("testStableSemconv") { + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath + filter { + includeTestsMatching("RabbitMqTest.testReceiveSpanKind") + } + jvmArgs("-Dotel.semconv-stability.preview=messaging") + jvmArgs("-Dotel.instrumentation.common.v3-preview=true") + systemProperty("metadataConfig", "otel.semconv-stability.opt-in=messaging") + } + check { - dependsOn(testExperimental) + dependsOn(testExperimental, testStableSemconv) } } diff --git a/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/v2_7/RabbitSingletons.java b/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/v2_7/RabbitSingletons.java index 3907033af555..51abc4190107 100644 --- a/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/v2_7/RabbitSingletons.java +++ b/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/v2_7/RabbitSingletons.java @@ -17,11 +17,11 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingOperationType; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanKindExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingProcessInstrumenterFactory; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; -import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; import io.opentelemetry.instrumentation.api.internal.PropagatorBasedSpanLinksExtractor; import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesExtractor; import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig; @@ -98,7 +98,8 @@ private static Instrumenter createReceiveInstrument GlobalOpenTelemetry.getPropagators().getTextMapPropagator(), new ReceiveRequestTextMapGetter())); setMessagingReceiveExceptionEventExtractor(builder); - return builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); + return builder.buildInstrumenter( + MessagingSpanKindExtractor.create(MessagingOperationType.RECEIVE)); } private static Instrumenter createDeliverInstrumenter() { diff --git a/instrumentation/rabbitmq-2.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/v2_7/RabbitMqTest.java b/instrumentation/rabbitmq-2.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/v2_7/RabbitMqTest.java index c2f0074d6feb..299f52807baa 100644 --- a/instrumentation/rabbitmq-2.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/v2_7/RabbitMqTest.java +++ b/instrumentation/rabbitmq-2.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/v2_7/RabbitMqTest.java @@ -8,6 +8,7 @@ import static io.opentelemetry.api.common.AttributeKey.longKey; import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; @@ -161,6 +162,23 @@ void testRabbitPublishGet() throws IOException { false))); } + @Test + void testReceiveSpanKind() throws IOException { + String queueName = channel.queueDeclare().getQueue(); + channel.basicPublish("", queueName, null, "test".getBytes(Charset.defaultCharset())); + testing.clearData(); + + testing.runWithSpan("parent", () -> channel.basicGet(queueName, true)); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasKind(emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)))); + } + @Test void testRabbitPublishGetWithDefaultExchange() throws IOException { String queueName = From de3a4b21bd2396c5ebfadaac4bd4fbe60a47a1b7 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 17 Jul 2026 15:04:36 -0700 Subject: [PATCH 11/18] Prevent leaked SQS process span from parenting next message in preview mode In messaging preview mode the SQS TracingIterator passes Context.current() so MessagingProcessContextCustomizer can honor a genuine ambient parent. An abandoned iterator can leave its own process span current, which the customizer would wrongly treat as the parent of another message's process span. Ignore an instrumentation-owned leaked CONSUMER_PROCESS span while preserving genuine ambient spans and other context values, and cover the abandoned-iterator case for both AWS SDK 1.11 and 2.2. Also standardize the messaging-preview test tasks added by this PR as testMessagingPreview (aws-sdk, pulsar, rabbitmq), keeping the incubator testV3Preview task which intentionally exercises broader v3 preview behavior. --- .../aws-sdk-1.11/library/build.gradle.kts | 11 +++++- .../v1_11/internal/TracingIterator.java | 16 +++++++- .../AbstractSqsSuppressReceiveSpansTest.java | 36 ++++++++++++++++++ .../aws-sdk-2.2/library/build.gradle.kts | 17 ++++++++- .../awssdk/v2_2/internal/TracingIterator.java | 16 +++++++- ...stractAws2SqsSuppressReceiveSpansTest.java | 38 +++++++++++++++++++ .../pulsar-2.8/javaagent/build.gradle.kts | 7 +--- .../rabbitmq-2.7/javaagent/build.gradle.kts | 7 +--- 8 files changed, 134 insertions(+), 14 deletions(-) diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts index f506e594ed99..b73f46299e85 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts @@ -58,6 +58,15 @@ tasks { jvmArgs("-Dotel.semconv-stability.opt-in=database") } + val testMessagingPreview = register("testMessagingPreview") { + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath + filter { + includeTestsMatching("SqsSuppressReceiveSpansTest.testAbandonedIteratorDoesNotParentNextProcessSpan") + } + jvmArgs("-Dotel.semconv-stability.preview=messaging") + } + val testExceptionSignalLogs = register("testExceptionSignalLogs") { testClassesDirs = sourceSets.test.get().output.classesDirs classpath = sourceSets.test.get().runtimeClasspath @@ -65,6 +74,6 @@ tasks { } check { - dependsOn(testing.suites, testStableSemconv, testExceptionSignalLogs) + dependsOn(testing.suites, testStableSemconv, testMessagingPreview, testExceptionSignalLogs) } } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/TracingIterator.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/TracingIterator.java index 24e27ffef2c7..d97542cc73b5 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/TracingIterator.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/TracingIterator.java @@ -10,9 +10,11 @@ import com.amazonaws.Request; import com.amazonaws.Response; import com.amazonaws.services.sqs.model.Message; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.internal.SpanKey; import java.util.Iterator; import javax.annotation.Nullable; @@ -76,7 +78,7 @@ public Message next() { if (parentContext == null) { parentContext = emitStableMessagingSemconv() - ? Context.current() + ? currentContextWithoutLeakedProcessSpan() : SqsParentContext.ofSystemAttributes(next.getAttributes()); } @@ -87,6 +89,18 @@ public Message next() { return next; } + private static Context currentContextWithoutLeakedProcessSpan() { + Context context = Context.current(); + Span currentSpan = Span.fromContext(context); + // If the current span is a process span leaked by an abandoned iterator, replace it with an + // invalid span so the customizer falls back to extracting the parent from the message's + // producer context. Otherwise leave the context untouched, so a genuine ambient application + // span (if any) remains the parent. Either way baggage and other context values are preserved. + return currentSpan == SpanKey.CONSUMER_PROCESS.fromContextOrNull(context) + ? context.with(Span.getInvalid()) + : context; + } + private void closeScopeAndEndSpan() { if (currentScope != null) { currentScope.close(); diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsSuppressReceiveSpansTest.java b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsSuppressReceiveSpansTest.java index 8b072338acc7..516a64690621 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsSuppressReceiveSpansTest.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsSuppressReceiveSpansTest.java @@ -32,12 +32,14 @@ import com.amazonaws.services.sqs.AmazonSQSAsync; import com.amazonaws.services.sqs.AmazonSQSAsyncClient; import com.amazonaws.services.sqs.AmazonSQSAsyncClientBuilder; +import com.amazonaws.services.sqs.model.Message; import com.amazonaws.services.sqs.model.ReceiveMessageRequest; import com.amazonaws.services.sqs.model.ReceiveMessageResult; import com.amazonaws.services.sqs.model.SendMessageRequest; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.test.utils.PortUtils; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.util.Iterator; import org.elasticmq.rest.sqs.SQSRestServer; import org.elasticmq.rest.sqs.SQSRestServerBuilder; import org.junit.jupiter.api.AfterEach; @@ -174,6 +176,40 @@ void testSimpleSqsProducerConsumerServices() { .hasTotalAttributeCount(0))); } + @Test + void testAbandonedIteratorDoesNotParentNextProcessSpan() { + String queueUrl = "http://localhost:" + sqsPort + "/000000000000/testSdkSqs"; + sqsClient.createQueue("testSdkSqs"); + + sqsClient.sendMessage(new SendMessageRequest(queueUrl, "first")); + ReceiveMessageResult firstResponse = sqsClient.receiveMessage(queueUrl); + sqsClient.sendMessage(new SendMessageRequest(queueUrl, "second")); + ReceiveMessageResult secondResponse = sqsClient.receiveMessage(queueUrl); + + Iterator firstIterator = firstResponse.getMessages().iterator(); + Iterator secondIterator = secondResponse.getMessages().iterator(); + assertThat(firstIterator.hasNext()).isTrue(); + firstIterator.next(); + assertThat(secondIterator.hasNext()).isTrue(); + secondIterator.next(); + assertThat(secondIterator.hasNext()).isFalse(); + assertThat(firstIterator.hasNext()).isFalse(); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("SQS.CreateQueue").hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("publish testSdkSqs").hasNoParent(), + span -> span.hasName("process testSdkSqs").hasParent(trace.getSpan(0))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("publish testSdkSqs").hasNoParent(), + span -> span.hasName("process testSdkSqs").hasParent(trace.getSpan(0)))); + } + @Test void testSimpleSqsProducerConsumerServicesWithParentSpan() { sqsClient.createQueue("testSdkSqs"); diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts index 032e0946228b..18541c18b4fb 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts @@ -82,6 +82,15 @@ tasks { jvmArgs("-Dotel.semconv-stability.opt-in=database") } + val testMessagingPreview = register("testMessagingPreview") { + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath + filter { + includeTestsMatching("Aws2SqsSuppressReceiveSpansW3cPropagatorTest.testAbandonedIteratorDoesNotParentNextProcessSpan") + } + jvmArgs("-Dotel.semconv-stability.preview=messaging") + } + val testCoreOnlyStableSemconv = register("testCoreOnlyStableSemconv") { val testCoreOnlySourceSet = sourceSets["testCoreOnly"] testClassesDirs = testCoreOnlySourceSet.output.classesDirs @@ -98,6 +107,12 @@ tasks { } check { - dependsOn(testing.suites, testStableSemconv, testCoreOnlyStableSemconv, testExceptionSignalLogs) + dependsOn( + testing.suites, + testStableSemconv, + testMessagingPreview, + testCoreOnlyStableSemconv, + testExceptionSignalLogs, + ) } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingIterator.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingIterator.java index e47dd8f864dd..e9a38526a9e2 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingIterator.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingIterator.java @@ -7,9 +7,11 @@ import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.internal.SpanKey; import java.util.Iterator; import javax.annotation.Nullable; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; @@ -81,7 +83,7 @@ public Message next() { if (parentContext == null) { parentContext = emitStableMessagingSemconv() - ? Context.current() + ? currentContextWithoutLeakedProcessSpan() : SqsParentContext.ofMessage(sqsMessage, config); } @@ -92,6 +94,18 @@ public Message next() { return next; } + private static Context currentContextWithoutLeakedProcessSpan() { + Context context = Context.current(); + Span currentSpan = Span.fromContext(context); + // If the current span is a process span leaked by an abandoned iterator, replace it with an + // invalid span so the customizer falls back to extracting the parent from the message's + // producer context. Otherwise leave the context untouched, so a genuine ambient application + // span (if any) remains the parent. Either way baggage and other context values are preserved. + return currentSpan == SpanKey.CONSUMER_PROCESS.fromContextOrNull(context) + ? context.with(Span.getInvalid()) + : context; + } + private void closeScopeAndEndSpan() { if (currentScope != null) { currentScope.close(); diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsSuppressReceiveSpansTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsSuppressReceiveSpansTest.java index a162d8e67668..92bf5de25394 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsSuppressReceiveSpansTest.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsSuppressReceiveSpansTest.java @@ -31,11 +31,13 @@ import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; import io.opentelemetry.sdk.testing.assertj.TraceAssert; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.function.Consumer; import org.junit.jupiter.api.Test; import software.amazon.awssdk.services.sqs.SqsClient; import software.amazon.awssdk.services.sqs.SqsClientBuilder; +import software.amazon.awssdk.services.sqs.model.Message; import software.amazon.awssdk.services.sqs.model.ReceiveMessageResponse; @SuppressWarnings("deprecation") // using deprecated semconv @@ -93,6 +95,42 @@ protected void assertSqsTraces(boolean withParent, boolean captureHeaders) { getTesting().waitAndAssertTraces(traceAsserts); } + @Test + void testAbandonedIteratorDoesNotParentNextProcessSpan() { + SqsClientBuilder builder = SqsClient.builder(); + configureSdkClient(builder); + SqsClient client = configureSqsClient(builder.build()); + + client.createQueue(createQueueRequest); + client.sendMessage(sendMessageRequest.toBuilder().messageBody("first").build()); + ReceiveMessageResponse firstResponse = client.receiveMessage(receiveMessageRequest); + client.sendMessage(sendMessageRequest.toBuilder().messageBody("second").build()); + ReceiveMessageResponse secondResponse = client.receiveMessage(receiveMessageRequest); + + Iterator firstIterator = firstResponse.messages().iterator(); + Iterator secondIterator = secondResponse.messages().iterator(); + assertThat(firstIterator.hasNext()).isTrue(); + firstIterator.next(); + assertThat(secondIterator.hasNext()).isTrue(); + secondIterator.next(); + assertThat(secondIterator.hasNext()).isFalse(); + assertThat(firstIterator.hasNext()).isFalse(); + + getTesting() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("Sqs.CreateQueue").hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("publish testSdkSqs").hasNoParent(), + span -> span.hasName("process testSdkSqs").hasParent(trace.getSpan(0))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("publish testSdkSqs").hasNoParent(), + span -> span.hasName("process testSdkSqs").hasParent(trace.getSpan(0)))); + } + @Test @SuppressWarnings("deprecation") // using deprecated semconv void testBatchSqsProducerConsumerServicesSync() { diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts b/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts index fc2033772806..7931ccb64091 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts @@ -48,9 +48,7 @@ tasks { systemProperty("metadataConfig", "otel.instrumentation.pulsar.experimental-span-attributes=true") } - // The task name follows the existing pattern; it does not indicate that the messaging - // conventions are stable. - val testStableSemconv = register("testStableSemconv") { + val testMessagingPreview = register("testMessagingPreview") { testClassesDirs = sourceSets.test.get().output.classesDirs classpath = sourceSets.test.get().runtimeClasspath filter { @@ -58,7 +56,6 @@ tasks { includeTestsMatching("PulsarClientTest.testConsumeNonPartitionedTopicUsingReceive") } jvmArgs("-Dotel.semconv-stability.preview=messaging") - jvmArgs("-Dotel.instrumentation.common.v3-preview=true") systemProperty("metadataConfig", "otel.semconv-stability.opt-in=messaging") } @@ -74,7 +71,7 @@ tasks { } check { - dependsOn(testReceiveSpanDisabled, testExperimental, testStableSemconv) + dependsOn(testReceiveSpanDisabled, testExperimental, testMessagingPreview) } if (otelProps.denyUnsafe) { diff --git a/instrumentation/rabbitmq-2.7/javaagent/build.gradle.kts b/instrumentation/rabbitmq-2.7/javaagent/build.gradle.kts index 48de77386049..688672d45485 100644 --- a/instrumentation/rabbitmq-2.7/javaagent/build.gradle.kts +++ b/instrumentation/rabbitmq-2.7/javaagent/build.gradle.kts @@ -44,20 +44,17 @@ tasks { systemProperty("metadataConfig", "otel.instrumentation.rabbitmq.experimental-span-attributes=true") } - // The task name follows the existing pattern; messaging conventions are still preview, and this - // task is limited to the receive span-kind path migrated here. - val testStableSemconv = register("testStableSemconv") { + val testMessagingPreview = register("testMessagingPreview") { testClassesDirs = sourceSets.test.get().output.classesDirs classpath = sourceSets.test.get().runtimeClasspath filter { includeTestsMatching("RabbitMqTest.testReceiveSpanKind") } jvmArgs("-Dotel.semconv-stability.preview=messaging") - jvmArgs("-Dotel.instrumentation.common.v3-preview=true") systemProperty("metadataConfig", "otel.semconv-stability.opt-in=messaging") } check { - dependsOn(testExperimental, testStableSemconv) + dependsOn(testExperimental, testMessagingPreview) } } From 66dccd71311b1d2fb3b2ec40b3e8334438fbe2dd Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 18 Jul 2026 06:14:44 -0700 Subject: [PATCH 12/18] Address review comments from copilot-pull-request-reviewer: preserve SQS parent context --- .../internal/AwsSdkInstrumenterFactory.java | 3 +- .../v1_11/internal/SqsParentContext.java | 6 +- .../v1_11/internal/SqsParentContextTest.java | 33 +++++++++++ .../internal/AwsSdkInstrumenterFactory.java | 5 +- .../v2_2/internal/SqsParentContext.java | 42 ++++++++++--- .../v2_2/internal/SqsParentContextTest.java | 59 +++++++++++++++++++ 6 files changed, 137 insertions(+), 11 deletions(-) create mode 100644 instrumentation/aws-sdk/aws-sdk-1.11/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsParentContextTest.java create mode 100644 instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsParentContextTest.java diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java index d6719ff75dae..0b643bb47b97 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java @@ -148,7 +148,8 @@ public Instrumenter> consumerProcessInstrumenter( builder.addContextCustomizer( MessagingProcessContextCustomizer.create( (parentContext, request) -> - SqsParentContext.ofSystemAttributes(request.getMessage().getAttributes()))); + SqsParentContext.ofSystemAttributes( + parentContext, request.getMessage().getAttributes()))); } return builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsParentContext.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsParentContext.java index 8f624a0cd862..8188ef0b1325 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsParentContext.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsParentContext.java @@ -35,9 +35,13 @@ public String get(@Nullable Map map, String s) { static final String AWS_TRACE_SYSTEM_ATTRIBUTE = "AWSTraceHeader"; static Context ofSystemAttributes(Map systemAttributes) { + return ofSystemAttributes(Context.root(), systemAttributes); + } + + static Context ofSystemAttributes(Context parentContext, Map systemAttributes) { String traceHeader = systemAttributes.get(AWS_TRACE_SYSTEM_ATTRIBUTE); return AwsXrayPropagator.getInstance() - .extract(Context.root(), singletonMap("X-Amzn-Trace-Id", traceHeader), new MapGetter()); + .extract(parentContext, singletonMap("X-Amzn-Trace-Id", traceHeader), new MapGetter()); } private SqsParentContext() {} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsParentContextTest.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsParentContextTest.java new file mode 100644 index 000000000000..6e40ad1187f1 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsParentContextTest.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v1_11.internal; + +import static java.util.Collections.singletonMap; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextKey; +import org.junit.jupiter.api.Test; + +class SqsParentContextTest { + + private static final ContextKey TEST_CONTEXT_KEY = ContextKey.named("test-context-key"); + private static final String TRACE_HEADER = + "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1"; + + @Test + void preservesParentContextValues() { + Context parentContext = Context.root().with(TEST_CONTEXT_KEY, "test-value"); + + Context extractedContext = + SqsParentContext.ofSystemAttributes( + parentContext, singletonMap(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE, TRACE_HEADER)); + + assertThat(extractedContext.get(TEST_CONTEXT_KEY)).isEqualTo("test-value"); + assertThat(Span.fromContext(extractedContext).getSpanContext().isValid()).isTrue(); + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java index 759eeb77316b..c42c8009855b 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java @@ -177,7 +177,10 @@ public Instrumenter consumerProcessInstrumenter() { MessagingProcessContextCustomizer.create( (parentContext, request) -> SqsParentContext.ofMessage( - request.getMessage(), messagingPropagator, useXrayPropagator))); + parentContext, + request.getMessage(), + messagingPropagator, + useXrayPropagator))); } return builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsParentContext.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsParentContext.java index b9d5037a755d..1efbc45dd4ce 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsParentContext.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsParentContext.java @@ -65,34 +65,60 @@ public String get(@Nullable Map map, String s) { static Context ofMessageAttributes( Map messageAttributes, TextMapPropagator propagator) { + return ofMessageAttributes(Context.root(), messageAttributes, propagator); + } + + static Context ofMessageAttributes( + Context parentContext, + Map messageAttributes, + TextMapPropagator propagator) { return propagator.extract( - Context.root(), messageAttributes, MessageAttributeValueMapGetter.INSTANCE); + parentContext, messageAttributes, MessageAttributeValueMapGetter.INSTANCE); } static Context ofSystemAttributes(Map systemAttributes) { + return ofSystemAttributes(Context.root(), systemAttributes); + } + + static Context ofSystemAttributes(Context parentContext, Map systemAttributes) { String traceHeader = systemAttributes.get(AWS_TRACE_SYSTEM_ATTRIBUTE); return AwsXrayPropagator.getInstance() .extract( - Context.root(), singletonMap("X-Amzn-Trace-Id", traceHeader), StringMapGetter.INSTANCE); + parentContext, singletonMap("X-Amzn-Trace-Id", traceHeader), StringMapGetter.INSTANCE); } public static Context ofMessage(SqsMessage message, TracingExecutionInterceptor config) { - return ofMessage(message, config.getMessagingPropagator(), config.shouldUseXrayPropagator()); + return ofMessage(Context.root(), message, config); + } + + static Context ofMessage( + Context parentContext, SqsMessage message, TracingExecutionInterceptor config) { + return ofMessage( + parentContext, message, config.getMessagingPropagator(), config.shouldUseXrayPropagator()); } static Context ofMessage( SqsMessage message, TextMapPropagator messagingPropagator, boolean shouldUseXrayPropagator) { - Context parentContext = Context.root(); + return ofMessage(Context.root(), message, messagingPropagator, shouldUseXrayPropagator); + } + + static Context ofMessage( + Context parentContext, + SqsMessage message, + TextMapPropagator messagingPropagator, + boolean shouldUseXrayPropagator) { + Context extractedContext = parentContext; if (messagingPropagator != null) { - parentContext = ofMessageAttributes(message.messageAttributes(), messagingPropagator); + extractedContext = + ofMessageAttributes(parentContext, message.messageAttributes(), messagingPropagator); } - if (shouldUseXrayPropagator && parentContext == Context.root()) { - parentContext = ofSystemAttributes(message.attributesAsStrings()); + if (shouldUseXrayPropagator && extractedContext == parentContext) { + extractedContext = ofSystemAttributes(parentContext, message.attributesAsStrings()); } - return parentContext; + return extractedContext; } private SqsParentContext() {} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsParentContextTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsParentContextTest.java new file mode 100644 index 000000000000..8ddde584b64c --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsParentContextTest.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2.internal; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextKey; +import java.util.Map; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; + +class SqsParentContextTest { + + private static final ContextKey TEST_CONTEXT_KEY = ContextKey.named("test-context-key"); + private static final String TRACE_HEADER = + "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1"; + + @Test + void preservesParentContextValues() { + Context parentContext = Context.root().with(TEST_CONTEXT_KEY, "test-value"); + + Context extractedContext = + SqsParentContext.ofMessage(parentContext, messageWithTraceHeader(), null, true); + + assertThat(extractedContext.get(TEST_CONTEXT_KEY)).isEqualTo("test-value"); + assertThat(Span.fromContext(extractedContext).getSpanContext().isValid()).isTrue(); + } + + private static SqsMessage messageWithTraceHeader() { + return new SqsMessage() { + @Override + public Map messageAttributes() { + return emptyMap(); + } + + @Override + public Map attributesAsStrings() { + return singletonMap(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE, TRACE_HEADER); + } + + @Override + public String getMessageAttribute(String name) { + return ""; + } + + @Override + public String getMessageId() { + return ""; + } + }; + } +} From 06313d774a6ee42f82e05668f598271816e79232 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 18 Jul 2026 06:22:12 -0700 Subject: [PATCH 13/18] Address review comment from copilot-pull-request-reviewer: prevent leaked Kafka process parenting --- .../javaagent/build.gradle.kts | 17 +++++++++- .../KafkaClientSuppressReceiveSpansTest.java | 32 +++++++++++++++++++ .../v0_11/internal/TracingIterator.java | 20 +++++++++++- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/build.gradle.kts b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/build.gradle.kts index bbae53c120b0..1ae520b678fd 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/build.gradle.kts +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/build.gradle.kts @@ -53,6 +53,16 @@ tasks { include("**/KafkaClientSuppressReceiveSpansTest.*") } + val testMessagingPreview = register("testMessagingPreview") { + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath + filter { + includeTestsMatching("KafkaClientSuppressReceiveSpansTest.testAbandonedIteratorDoesNotParentNextProcessSpan") + } + jvmArgs("-Dotel.semconv-stability.preview=messaging") + systemProperty("metadataConfig", "otel.semconv-stability.opt-in=messaging") + } + val testExperimental = register("testExperimental") { testClassesDirs = sourceSets.test.get().output.classesDirs classpath = sourceSets.test.get().runtimeClasspath @@ -95,7 +105,12 @@ tasks { } check { - dependsOn(testPropagationDisabled, testReceiveSpansDisabled, testExperimental) + dependsOn( + testPropagationDisabled, + testReceiveSpansDisabled, + testMessagingPreview, + testExperimental, + ) } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java index 4f8a584b926a..864c872ef59b 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java @@ -10,11 +10,14 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaClientBaseTest; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaClientPropagationBaseTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import java.time.Duration; +import java.util.Iterator; import java.util.List; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; @@ -150,4 +153,33 @@ void testRecordsWithTopicPartitionKafkaConsume() throws Exception { .hasAttributesSatisfyingExactly( processAttributes(null, greeting, false, false)))); } + + @Test + void testAbandonedIteratorDoesNotParentNextProcessSpan() throws Exception { + producer.send(new ProducerRecord<>(SHARED_TOPIC, "first")).get(5, SECONDS); + awaitUntilConsumerIsReady(); + Iterator> firstIterator = poll(Duration.ofSeconds(5)).iterator(); + assertThat(firstIterator.hasNext()).isTrue(); + firstIterator.next(); + + try (Scope ignored = Context.root().makeCurrent()) { + producer.send(new ProducerRecord<>(SHARED_TOPIC, "second")).get(5, SECONDS); + } + Iterator> secondIterator = + poll(Duration.ofSeconds(5)).iterator(); + assertThat(secondIterator.hasNext()).isTrue(); + secondIterator.next(); + assertThat(secondIterator.hasNext()).isFalse(); + assertThat(firstIterator.hasNext()).isFalse(); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("send " + SHARED_TOPIC).hasNoParent(), + span -> span.hasName("process " + SHARED_TOPIC).hasParent(trace.getSpan(0))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("send " + SHARED_TOPIC).hasNoParent(), + span -> span.hasName("process " + SHARED_TOPIC).hasParent(trace.getSpan(0)))); + } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/TracingIterator.java b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/TracingIterator.java index 33af6502abd7..f7ea85d9c130 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/TracingIterator.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/TracingIterator.java @@ -5,9 +5,13 @@ package io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; + +import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.internal.SpanKey; import java.util.Iterator; import java.util.function.BooleanSupplier; import javax.annotation.Nullable; @@ -44,10 +48,24 @@ private TracingIterator( Context receiveContext = consumerContext.getContext(); // use the receive CONSUMER as parent if it's available - this.parentContext = receiveContext != null ? receiveContext : Context.current(); + this.parentContext = + receiveContext != null + ? receiveContext + : currentContextWithoutLeakedProcessSpan(Context.current()); this.consumerContext = consumerContext; } + private static Context currentContextWithoutLeakedProcessSpan(Context context) { + if (!emitStableMessagingSemconv()) { + return context; + } + + Span currentSpan = Span.fromContext(context); + return currentSpan == SpanKey.CONSUMER_PROCESS.fromContextOrNull(context) + ? context.with(Span.getInvalid()) + : context; + } + public static Iterator> wrap( Iterator> delegateIterator, Instrumenter instrumenter, From 0a1105312d50139e35d2c907c0591a618dfc9bef Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 18 Jul 2026 06:51:27 -0700 Subject: [PATCH 14/18] Skip abandoned-iterator tests unless stable messaging semconv is enabled --- .../AbstractSqsSuppressReceiveSpansTest.java | 3 + ...stractAws2SqsSuppressReceiveSpansTest.java | 73 ++++++++++--------- .../KafkaClientSuppressReceiveSpansTest.java | 3 + 3 files changed, 44 insertions(+), 35 deletions(-) diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsSuppressReceiveSpansTest.java b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsSuppressReceiveSpansTest.java index 516a64690621..c88ac77c95f9 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsSuppressReceiveSpansTest.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsSuppressReceiveSpansTest.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.awssdk.v1_11; import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD; @@ -25,6 +26,7 @@ import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SERVICE; import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; @@ -178,6 +180,7 @@ void testSimpleSqsProducerConsumerServices() { @Test void testAbandonedIteratorDoesNotParentNextProcessSpan() { + assumeTrue(emitStableMessagingSemconv()); String queueUrl = "http://localhost:" + sqsPort + "/000000000000/testSdkSqs"; sqsClient.createQueue("testSdkSqs"); diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsSuppressReceiveSpansTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsSuppressReceiveSpansTest.java index 92bf5de25394..2f23c46e79d6 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsSuppressReceiveSpansTest.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsSuppressReceiveSpansTest.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.awssdk.v2_2; import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD; @@ -26,6 +27,7 @@ import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; @@ -95,41 +97,42 @@ protected void assertSqsTraces(boolean withParent, boolean captureHeaders) { getTesting().waitAndAssertTraces(traceAsserts); } - @Test - void testAbandonedIteratorDoesNotParentNextProcessSpan() { - SqsClientBuilder builder = SqsClient.builder(); - configureSdkClient(builder); - SqsClient client = configureSqsClient(builder.build()); - - client.createQueue(createQueueRequest); - client.sendMessage(sendMessageRequest.toBuilder().messageBody("first").build()); - ReceiveMessageResponse firstResponse = client.receiveMessage(receiveMessageRequest); - client.sendMessage(sendMessageRequest.toBuilder().messageBody("second").build()); - ReceiveMessageResponse secondResponse = client.receiveMessage(receiveMessageRequest); - - Iterator firstIterator = firstResponse.messages().iterator(); - Iterator secondIterator = secondResponse.messages().iterator(); - assertThat(firstIterator.hasNext()).isTrue(); - firstIterator.next(); - assertThat(secondIterator.hasNext()).isTrue(); - secondIterator.next(); - assertThat(secondIterator.hasNext()).isFalse(); - assertThat(firstIterator.hasNext()).isFalse(); - - getTesting() - .waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("Sqs.CreateQueue").hasNoParent()), - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("publish testSdkSqs").hasNoParent(), - span -> span.hasName("process testSdkSqs").hasParent(trace.getSpan(0))), - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("publish testSdkSqs").hasNoParent(), - span -> span.hasName("process testSdkSqs").hasParent(trace.getSpan(0)))); - } + @Test + void testAbandonedIteratorDoesNotParentNextProcessSpan() { + assumeTrue(emitStableMessagingSemconv()); + SqsClientBuilder builder = SqsClient.builder(); + configureSdkClient(builder); + SqsClient client = configureSqsClient(builder.build()); + + client.createQueue(createQueueRequest); + client.sendMessage(sendMessageRequest.toBuilder().messageBody("first").build()); + ReceiveMessageResponse firstResponse = client.receiveMessage(receiveMessageRequest); + client.sendMessage(sendMessageRequest.toBuilder().messageBody("second").build()); + ReceiveMessageResponse secondResponse = client.receiveMessage(receiveMessageRequest); + + Iterator firstIterator = firstResponse.messages().iterator(); + Iterator secondIterator = secondResponse.messages().iterator(); + assertThat(firstIterator.hasNext()).isTrue(); + firstIterator.next(); + assertThat(secondIterator.hasNext()).isTrue(); + secondIterator.next(); + assertThat(secondIterator.hasNext()).isFalse(); + assertThat(firstIterator.hasNext()).isFalse(); + + getTesting() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("Sqs.CreateQueue").hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("publish testSdkSqs").hasNoParent(), + span -> span.hasName("process testSdkSqs").hasParent(trace.getSpan(0))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("publish testSdkSqs").hasNoParent(), + span -> span.hasName("process testSdkSqs").hasParent(trace.getSpan(0)))); + } @Test @SuppressWarnings("deprecation") // using deprecated semconv diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java index 864c872ef59b..71a1f83d100e 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java @@ -5,9 +5,11 @@ package io.opentelemetry.javaagent.instrumentation.kafkaclients.v0_11; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; @@ -156,6 +158,7 @@ void testRecordsWithTopicPartitionKafkaConsume() throws Exception { @Test void testAbandonedIteratorDoesNotParentNextProcessSpan() throws Exception { + assumeTrue(emitStableMessagingSemconv()); producer.send(new ProducerRecord<>(SHARED_TOPIC, "first")).get(5, SECONDS); awaitUntilConsumerIsReady(); Iterator> firstIterator = poll(Duration.ofSeconds(5)).iterator(); From 875fcfbccbd39457f6e8e6bd34f8440673bea841 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 18 Jul 2026 14:57:51 -0700 Subject: [PATCH 15/18] Fix stable messaging consumer span topology --- .../aws-sdk-1.11/library/build.gradle.kts | 1 + .../internal/AwsSdkInstrumenterFactory.java | 23 ++- .../awssdk/v1_11/internal/SqsImpl.java | 9 +- .../awssdk/v1_11/internal/SqsMessage.java | 3 + .../awssdk/v1_11/internal/SqsMessageImpl.java | 6 + .../v1_11/internal/TracingIterator.java | 61 ++----- .../awssdk/v1_11/internal/TracingList.java | 45 +++-- .../awssdk/v1_11/AbstractSqsTracingTest.java | 38 ++++ .../aws-sdk-2.2/library/build.gradle.kts | 1 + .../awssdk/v2_2/AwsSdkTelemetry.java | 4 +- .../internal/AwsSdkInstrumenterFactory.java | 34 ++-- .../awssdk/v2_2/internal/SqsImpl.java | 8 +- .../awssdk/v2_2/internal/SqsMessage.java | 3 + .../awssdk/v2_2/internal/SqsMessageImpl.java | 22 ++- .../awssdk/v2_2/internal/TracingIterator.java | 65 ++----- .../awssdk/v2_2/internal/TracingList.java | 38 ++-- .../v2_2/internal/SqsParentContextTest.java | 5 + .../v2_2/AbstractAws2SqsTracingTest.java | 41 +++++ .../jms/common/v1_1/JmsReceiveSpanUtil.java | 4 +- .../javaagent/build.gradle.kts | 15 +- .../v0_11/KafkaConsumerInstrumentation.java | 15 +- .../v0_11/KafkaClientDefaultTest.java | 162 ++++++++++++++++++ .../v2_6/internal/KafkaConsumerTelemetry.java | 11 +- .../testing/build.gradle.kts | 12 +- .../v2_6/KafkaConnectSinkTaskBaseTest.java | 15 +- .../v2_6/MongoKafkaConnectSinkTaskTest.java | 43 +++-- .../v2_8/telemetry/PulsarSingletons.java | 10 +- .../v4_8/RocketMqConsumerInstrumenter.java | 10 +- .../v5_0/ReceiveSpanFinishingCallback.java | 4 +- .../spring/cloud/aws/v3_0/SpringAwsUtil.java | 18 +- 30 files changed, 510 insertions(+), 216 deletions(-) diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts index b73f46299e85..d75f48909a37 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts @@ -63,6 +63,7 @@ tasks { classpath = sourceSets.test.get().runtimeClasspath filter { includeTestsMatching("SqsSuppressReceiveSpansTest.testAbandonedIteratorDoesNotParentNextProcessSpan") + includeTestsMatching("SqsTracingTest.testReceiveSpanLinksToProducer") } jvmArgs("-Dotel.semconv-stability.preview=messaging") } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java index 0b643bb47b97..b19a05e6c27b 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java @@ -9,8 +9,8 @@ import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingProcessExceptionEventExtractor; import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingReceiveExceptionEventExtractor; import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingSendExceptionEventExtractor; -import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.internal.RpcExceptionEventExtractors.setRpcClientExceptionEventExtractor; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -117,7 +117,18 @@ public Instrumenter> consumerReceiveInstrumenter( MessagingSpanKindExtractor.create(operationType), toSqsRequestExtractors(attributesExtractors()), singletonList(messagingAttributeExtractor), - builder -> setMessagingReceiveExceptionEventExtractor(builder), + builder -> { + setMessagingReceiveExceptionEventExtractor(builder); + if (emitStableMessagingSemconv()) { + builder.addSpanLinksExtractor( + (spanLinks, parentContext, request) -> { + for (SqsMessage message : request.getMessages()) { + spanLinks.addLink( + Span.fromContext(message.getCreationContext()).getSpanContext()); + } + }); + } + }, messagingReceiveInstrumentationEnabled); } @@ -139,17 +150,15 @@ public Instrumenter> consumerProcessInstrumenter( if (emitStableMessagingSemconv() || messagingReceiveInstrumentationEnabled) { builder.addSpanLinksExtractor( (spanLinks, parentContext, request) -> { - Context extracted = - SqsParentContext.ofSystemAttributes(request.getMessage().getAttributes()); - spanLinks.addLink(Span.fromContext(extracted).getSpanContext()); + spanLinks.addLink( + Span.fromContext(request.getMessage().getCreationContext()).getSpanContext()); }); } if (emitStableMessagingSemconv()) { builder.addContextCustomizer( MessagingProcessContextCustomizer.create( (parentContext, request) -> - SqsParentContext.ofSystemAttributes( - parentContext, request.getMessage().getAttributes()))); + parentContext.with(Span.fromContext(request.getMessage().getCreationContext())))); } return builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsImpl.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsImpl.java index eea79d900215..6726727344d2 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsImpl.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsImpl.java @@ -5,6 +5,8 @@ package io.opentelemetry.instrumentation.awssdk.v1_11.internal; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; + import com.amazonaws.AmazonWebServiceRequest; import com.amazonaws.Request; import com.amazonaws.Response; @@ -78,8 +80,9 @@ private static void afterConsumerResponse( timer.now()); } + Context processParentContext = emitStableMessagingSemconv() ? parentContext : receiveContext; addTracing( - receiveMessageResult, request, response, consumerProcessInstrumenter, receiveContext); + receiveMessageResult, request, response, consumerProcessInstrumenter, processParentContext); } @Nullable private static final Field messagesField = getMessagesField(); @@ -100,7 +103,7 @@ private static void addTracing( Request request, Response response, Instrumenter> consumerProcessInstrumenter, - @Nullable Context receiveContext) { + @Nullable Context processParentContext) { if (messagesField == null) { return; } @@ -114,7 +117,7 @@ private static void addTracing( consumerProcessInstrumenter, request, response, - receiveContext)); + processParentContext)); } catch (IllegalAccessException ignored) { // should not happen, we call setAccessible on the field } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsMessage.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsMessage.java index ddab50a32d1d..59760d0ff5c3 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsMessage.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsMessage.java @@ -5,6 +5,7 @@ package io.opentelemetry.instrumentation.awssdk.v1_11.internal; +import io.opentelemetry.context.Context; import java.util.Map; import javax.annotation.Nullable; @@ -14,6 +15,8 @@ */ interface SqsMessage { + Context getCreationContext(); + Map getAttributes(); @Nullable diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsMessageImpl.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsMessageImpl.java index c6df87eb67ce..dbba9f97992c 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsMessageImpl.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/SqsMessageImpl.java @@ -7,6 +7,7 @@ import com.amazonaws.services.sqs.model.Message; import com.amazonaws.services.sqs.model.MessageAttributeValue; +import io.opentelemetry.context.Context; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -32,6 +33,11 @@ static List wrap(List messages) { return result; } + @Override + public Context getCreationContext() { + return SqsParentContext.ofSystemAttributes(message.getAttributes()); + } + @Override public Map getAttributes() { return message.getAttributes(); diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/TracingIterator.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/TracingIterator.java index d97542cc73b5..d120f82f523e 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/TracingIterator.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/TracingIterator.java @@ -5,26 +5,16 @@ package io.opentelemetry.instrumentation.awssdk.v1_11.internal; -import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; - -import com.amazonaws.Request; -import com.amazonaws.Response; import com.amazonaws.services.sqs.model.Message; -import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.api.internal.SpanKey; import java.util.Iterator; import javax.annotation.Nullable; class TracingIterator implements Iterator { private final Iterator delegateIterator; - private final Instrumenter> instrumenter; - private final Request request; - private final Response response; - @Nullable private final Context receiveContext; + private final TracingList tracingList; /* * Note: this may potentially create problems if this iterator is used from different threads. But @@ -34,26 +24,13 @@ class TracingIterator implements Iterator { @Nullable private Context currentContext; @Nullable private Scope currentScope; - private TracingIterator( - Iterator delegateIterator, - Instrumenter> instrumenter, - Request request, - Response response, - @Nullable Context receiveContext) { + private TracingIterator(Iterator delegateIterator, TracingList tracingList) { this.delegateIterator = delegateIterator; - this.instrumenter = instrumenter; - this.request = request; - this.response = response; - this.receiveContext = receiveContext; + this.tracingList = tracingList; } - static Iterator wrap( - Iterator delegateIterator, - Instrumenter> instrumenter, - Request request, - Response response, - @Nullable Context receiveContext) { - return new TracingIterator(delegateIterator, instrumenter, request, response, receiveContext); + static Iterator wrap(Iterator delegateIterator, TracingList tracingList) { + return new TracingIterator(delegateIterator, tracingList); } @Override @@ -74,37 +51,25 @@ public Message next() { // (https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/1947) Message next = delegateIterator.next(); if (next != null) { - Context parentContext = receiveContext; + SqsMessage sqsMessage = SqsMessageImpl.wrap(next); + Context parentContext = tracingList.getProcessParentContext(); if (parentContext == null) { - parentContext = - emitStableMessagingSemconv() - ? currentContextWithoutLeakedProcessSpan() - : SqsParentContext.ofSystemAttributes(next.getAttributes()); + parentContext = sqsMessage.getCreationContext(); } - currentRequest = SqsProcessRequest.create(request, SqsMessageImpl.wrap(next)); - currentContext = instrumenter.start(parentContext, currentRequest); + currentRequest = SqsProcessRequest.create(tracingList.getRequest(), sqsMessage); + currentContext = tracingList.getInstrumenter().start(parentContext, currentRequest); currentScope = currentContext.makeCurrent(); } return next; } - private static Context currentContextWithoutLeakedProcessSpan() { - Context context = Context.current(); - Span currentSpan = Span.fromContext(context); - // If the current span is a process span leaked by an abandoned iterator, replace it with an - // invalid span so the customizer falls back to extracting the parent from the message's - // producer context. Otherwise leave the context untouched, so a genuine ambient application - // span (if any) remains the parent. Either way baggage and other context values are preserved. - return currentSpan == SpanKey.CONSUMER_PROCESS.fromContextOrNull(context) - ? context.with(Span.getInvalid()) - : context; - } - private void closeScopeAndEndSpan() { if (currentScope != null) { currentScope.close(); - instrumenter.end(currentContext, currentRequest, response, null); + tracingList + .getInstrumenter() + .end(currentContext, currentRequest, tracingList.getResponse(), null); currentScope = null; currentRequest = null; currentContext = null; diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/TracingList.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/TracingList.java index b441feea3c7a..777926cab47f 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/TracingList.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/TracingList.java @@ -23,29 +23,29 @@ class TracingList extends SdkInternalList { private final transient Instrumenter> instrumenter; private final transient Request request; private final transient Response response; - @Nullable private final transient Context receiveContext; + @Nullable private final transient Context processParentContext; private boolean firstIterator = true; - private TracingList( - List list, + static SdkInternalList wrap( + List messages, Instrumenter> instrumenter, Request request, Response response, - @Nullable Context receiveContext) { - super(list); - this.instrumenter = instrumenter; - this.request = request; - this.response = response; - this.receiveContext = receiveContext; + @Nullable Context processParentContext) { + return new TracingList(messages, instrumenter, request, response, processParentContext); } - static SdkInternalList wrap( - List list, + private TracingList( + List messages, Instrumenter> instrumenter, Request request, Response response, - @Nullable Context receiveContext) { - return new TracingList(list, instrumenter, request, response, receiveContext); + @Nullable Context processParentContext) { + super(messages); + this.instrumenter = instrumenter; + this.request = request; + this.response = response; + this.processParentContext = processParentContext; } @Override @@ -55,7 +55,7 @@ public Iterator iterator() { // However, this is not thread-safe, but usually the first (hopefully only) traversal of // List is performed in the same thread that called receiveMessage() if (firstIterator && !inAwsClient()) { - it = TracingIterator.wrap(super.iterator(), instrumenter, request, response, receiveContext); + it = TracingIterator.wrap(super.iterator(), this); firstIterator = false; } else { it = super.iterator(); @@ -64,6 +64,23 @@ public Iterator iterator() { return it; } + Instrumenter> getInstrumenter() { + return instrumenter; + } + + Request getRequest() { + return request; + } + + Response getResponse() { + return response; + } + + @Nullable + Context getProcessParentContext() { + return processParentContext; + } + @Override public void forEach(Consumer action) { for (Message message : this) { diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsTracingTest.java b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsTracingTest.java index 0f69fde6024c..15d3727a12b0 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsTracingTest.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractSqsTracingTest.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.awssdk.v1_11; import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.instrumentation.testing.junit.message.MessageHeaderUtil.headerAttributeKey; import static io.opentelemetry.instrumentation.testing.util.TestLatestDeps.testLatestDeps; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; @@ -29,6 +30,7 @@ import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; @@ -274,6 +276,42 @@ void testSimpleSqsProducerConsumerServicesCaptureHeaders(boolean testCaptureHead .hasTotalAttributeCount(0))); } + @Test + void testReceiveSpanLinksToProducer() { + assumeTrue(emitStableMessagingSemconv()); + String queueUrl = "http://localhost:" + sqsPort + "/000000000000/testSdkSqs"; + sqsClient.createQueue("testSdkSqs"); + sqsClient.sendMessage(new SendMessageRequest(queueUrl, "hello")); + sqsClient.receiveMessage(queueUrl); + + AtomicReference publishSpan = new AtomicReference<>(); + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("SQS.CreateQueue").hasKind(SpanKind.CLIENT)), + trace -> + trace.hasSpansSatisfyingExactly( + span -> { + publishSpan.set(trace.getSpan(0)); + span.hasName("publish testSdkSqs").hasKind(SpanKind.PRODUCER); + }), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("receive testSdkSqs") + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasLinksSatisfying( + links -> + assertThat(links) + .singleElement() + .satisfies( + link -> + assertThat(link.getSpanContext().getSpanId()) + .isEqualTo(publishSpan.get().getSpanId()))))); + } + @Test void testSimpleSqsProducerConsumerServicesWithParentSpan() { sqsClient.createQueue("testSdkSqs"); diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts index 18541c18b4fb..0983f90f01fc 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts @@ -87,6 +87,7 @@ tasks { classpath = sourceSets.test.get().runtimeClasspath filter { includeTestsMatching("Aws2SqsSuppressReceiveSpansW3cPropagatorTest.testAbandonedIteratorDoesNotParentNextProcessSpan") + includeTestsMatching("Aws2SqsDefaultPropagatorTest.testReceiveSpanLinksToProducer") } jvmArgs("-Dotel.semconv-stability.preview=messaging") } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkTelemetry.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkTelemetry.java index 05fe678711db..b1769dd15956 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkTelemetry.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkTelemetry.java @@ -89,11 +89,9 @@ public static AwsSdkTelemetryBuilder builder(OpenTelemetry openTelemetry) { AwsSdkInstrumenterFactory instrumenterFactory = new AwsSdkInstrumenterFactory( openTelemetry, - messagingPropagator, capturedHeaders, captureExperimentalSpanAttributes, - messagingReceiveInstrumentationEnabled, - useXrayPropagator); + messagingReceiveInstrumentationEnabled); this.requestInstrumenter = instrumenterFactory.requestInstrumenter(); this.consumerReceiveInstrumenter = instrumenterFactory.consumerReceiveInstrumenter(); diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java index c42c8009855b..f11d2aa74883 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java @@ -20,7 +20,6 @@ import io.opentelemetry.api.logs.Logger; import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; -import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiClientMetrics; @@ -84,25 +83,19 @@ public final class AwsSdkInstrumenterFactory { asList(rpcAttributesExtractor, httpAttributesExtractor, experimentalAttributesExtractor); private final OpenTelemetry openTelemetry; - @Nullable private final TextMapPropagator messagingPropagator; private final List capturedHeaders; private final boolean captureExperimentalSpanAttributes; private final boolean messagingReceiveInstrumentationEnabled; - private final boolean useXrayPropagator; public AwsSdkInstrumenterFactory( OpenTelemetry openTelemetry, - @Nullable TextMapPropagator messagingPropagator, List capturedHeaders, boolean captureExperimentalSpanAttributes, - boolean messagingReceiveInstrumentationEnabled, - boolean useXrayPropagator) { + boolean messagingReceiveInstrumentationEnabled) { this.openTelemetry = openTelemetry; - this.messagingPropagator = messagingPropagator; this.capturedHeaders = capturedHeaders; this.captureExperimentalSpanAttributes = captureExperimentalSpanAttributes; this.messagingReceiveInstrumentationEnabled = messagingReceiveInstrumentationEnabled; - this.useXrayPropagator = useXrayPropagator; } public Instrumenter requestInstrumenter() { @@ -146,7 +139,18 @@ public Instrumenter consumerReceiveInstrumenter() { MessagingSpanKindExtractor.create(operationType), toSqsRequestExtractors(consumerAttributesExtractors()), singletonList(messagingAttributeExtractor), - builder -> setMessagingReceiveExceptionEventExtractor(builder), + builder -> { + setMessagingReceiveExceptionEventExtractor(builder); + if (emitStableMessagingSemconv()) { + builder.addSpanLinksExtractor( + (spanLinks, parentContext, request) -> { + for (SqsMessage message : request.getMessages()) { + spanLinks.addLink( + Span.fromContext(message.getCreationContext()).getSpanContext()); + } + }); + } + }, messagingReceiveInstrumentationEnabled); } @@ -166,21 +170,15 @@ public Instrumenter consumerProcessInstrumenter() { if (emitStableMessagingSemconv() || messagingReceiveInstrumentationEnabled) { builder.addSpanLinksExtractor( (spanLinks, parentContext, request) -> { - Context extracted = - SqsParentContext.ofMessage( - request.getMessage(), messagingPropagator, useXrayPropagator); - spanLinks.addLink(Span.fromContext(extracted).getSpanContext()); + spanLinks.addLink( + Span.fromContext(request.getMessage().getCreationContext()).getSpanContext()); }); } if (emitStableMessagingSemconv()) { builder.addContextCustomizer( MessagingProcessContextCustomizer.create( (parentContext, request) -> - SqsParentContext.ofMessage( - parentContext, - request.getMessage(), - messagingPropagator, - useXrayPropagator))); + parentContext.with(Span.fromContext(request.getMessage().getCreationContext())))); } return builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsImpl.java index 8dc003fc5ade..f1e467d8164f 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsImpl.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsImpl.java @@ -5,6 +5,7 @@ package io.opentelemetry.instrumentation.awssdk.v2_2.internal; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.TracingExecutionInterceptor.SDK_HTTP_REQUEST_ATTRIBUTE; import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.TracingExecutionInterceptor.SDK_REQUEST_ATTRIBUTE; @@ -76,7 +77,8 @@ static boolean afterReceiveMessageExecution( config.getConsumerReceiveInstrumenter(); io.opentelemetry.context.Context receiveContext = null; SqsReceiveRequest receiveRequest = - SqsReceiveRequest.create(executionAttributes, SqsMessageImpl.wrap(response.messages())); + SqsReceiveRequest.create( + executionAttributes, SqsMessageImpl.wrap(response.messages(), config)); if (timer != null && consumerReceiveInstrumenter.shouldStart(parentContext, receiveRequest)) { receiveContext = InstrumenterUtil.startAndEnd( @@ -88,6 +90,8 @@ static boolean afterReceiveMessageExecution( timer.startTime(), timer.now()); } + io.opentelemetry.context.Context processParentContext = + emitStableMessagingSemconv() ? parentContext : receiveContext; // copy ExecutionAttributes as these will get cleared before the process spans are created ExecutionAttributes copy = new ExecutionAttributes(); copy.putAttribute( @@ -108,7 +112,7 @@ static boolean afterReceiveMessageExecution( copy, new Response(context.httpResponse(), response), config, - receiveContext); + processParentContext); // store tracing list in context so that our proxied SqsClient/SqsAsyncClient could pick it up SqsTracingContext.set(parentContext, tracingList); diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsMessage.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsMessage.java index 9e4b71a770e7..1d2db90b6433 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsMessage.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsMessage.java @@ -5,6 +5,7 @@ package io.opentelemetry.instrumentation.awssdk.v2_2.internal; +import io.opentelemetry.context.Context; import java.util.Map; import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; @@ -17,6 +18,8 @@ */ public interface SqsMessage { + Context getCreationContext(); + Map messageAttributes(); Map attributesAsStrings(); diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsMessageImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsMessageImpl.java index 95bf7c388c6e..0b12bf4d5e60 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsMessageImpl.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsMessageImpl.java @@ -5,9 +5,11 @@ package io.opentelemetry.instrumentation.awssdk.v2_2.internal; +import io.opentelemetry.context.Context; import java.util.ArrayList; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; import software.amazon.awssdk.services.sqs.model.Message; import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; @@ -18,23 +20,39 @@ public final class SqsMessageImpl implements SqsMessage { private final Message message; + @Nullable private final TracingExecutionInterceptor config; private SqsMessageImpl(Message message) { this.message = message; + this.config = null; + } + + private SqsMessageImpl(Message message, TracingExecutionInterceptor config) { + this.message = message; + this.config = config; } public static SqsMessage wrap(Message message) { return new SqsMessageImpl(message); } - static List wrap(List messages) { + public static SqsMessage wrap(Message message, TracingExecutionInterceptor config) { + return new SqsMessageImpl(message, config); + } + + static List wrap(List messages, TracingExecutionInterceptor config) { List result = new ArrayList<>(); for (Message message : messages) { - result.add(wrap(message)); + result.add(wrap(message, config)); } return result; } + @Override + public Context getCreationContext() { + return config != null ? SqsParentContext.ofMessage(this, config) : Context.root(); + } + @Override public Map messageAttributes() { return message.messageAttributes(); diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingIterator.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingIterator.java index e9a38526a9e2..7e2a8801287d 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingIterator.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingIterator.java @@ -5,26 +5,16 @@ package io.opentelemetry.instrumentation.awssdk.v2_2.internal; -import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; - -import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.api.internal.SpanKey; import java.util.Iterator; import javax.annotation.Nullable; -import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.services.sqs.model.Message; class TracingIterator implements Iterator { private final Iterator delegateIterator; - private final Instrumenter instrumenter; - private final ExecutionAttributes request; - private final Response response; - private final TracingExecutionInterceptor config; - private final Context receiveContext; + private final TracingList tracingList; /* * Note: this may potentially create problems if this iterator is used from different threads. But @@ -34,30 +24,14 @@ class TracingIterator implements Iterator { @Nullable private Context currentContext; @Nullable private Scope currentScope; - private TracingIterator( - Iterator delegateIterator, - Instrumenter instrumenter, - ExecutionAttributes request, - Response response, - TracingExecutionInterceptor config, - Context receiveContext) { + private TracingIterator(Iterator delegateIterator, TracingList tracingList) { this.delegateIterator = delegateIterator; - this.instrumenter = instrumenter; - this.request = request; - this.response = response; - this.config = config; - this.receiveContext = receiveContext; + this.tracingList = tracingList; } public static Iterator wrap( - Iterator delegateIterator, - Instrumenter instrumenter, - ExecutionAttributes request, - Response response, - TracingExecutionInterceptor config, - Context receiveContext) { - return new TracingIterator( - delegateIterator, instrumenter, request, response, config, receiveContext); + Iterator delegateIterator, TracingList tracingList) { + return new TracingIterator(delegateIterator, tracingList); } @Override @@ -78,38 +52,25 @@ public Message next() { // (https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/1947) Message next = delegateIterator.next(); if (next != null) { - SqsMessage sqsMessage = SqsMessageImpl.wrap(next); - Context parentContext = receiveContext; + SqsMessage sqsMessage = SqsMessageImpl.wrap(next, tracingList.getConfig()); + Context parentContext = tracingList.getProcessParentContext(); if (parentContext == null) { - parentContext = - emitStableMessagingSemconv() - ? currentContextWithoutLeakedProcessSpan() - : SqsParentContext.ofMessage(sqsMessage, config); + parentContext = sqsMessage.getCreationContext(); } - currentRequest = SqsProcessRequest.create(request, sqsMessage); - currentContext = instrumenter.start(parentContext, currentRequest); + currentRequest = SqsProcessRequest.create(tracingList.getRequest(), sqsMessage); + currentContext = tracingList.getInstrumenter().start(parentContext, currentRequest); currentScope = currentContext.makeCurrent(); } return next; } - private static Context currentContextWithoutLeakedProcessSpan() { - Context context = Context.current(); - Span currentSpan = Span.fromContext(context); - // If the current span is a process span leaked by an abandoned iterator, replace it with an - // invalid span so the customizer falls back to extracting the parent from the message's - // producer context. Otherwise leave the context untouched, so a genuine ambient application - // span (if any) remains the parent. Either way baggage and other context values are preserved. - return currentSpan == SpanKey.CONSUMER_PROCESS.fromContextOrNull(context) - ? context.with(Span.getInvalid()) - : context; - } - private void closeScopeAndEndSpan() { if (currentScope != null) { currentScope.close(); - instrumenter.end(currentContext, currentRequest, response, null); + tracingList + .getInstrumenter() + .end(currentContext, currentRequest, tracingList.getResponse(), null); currentScope = null; currentRequest = null; currentContext = null; diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingList.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingList.java index 6243b38fb959..82c7bd3bb1f2 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingList.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingList.java @@ -11,6 +11,7 @@ import java.util.Iterator; import java.util.List; import java.util.function.Consumer; +import javax.annotation.Nullable; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.services.sqs.model.Message; @@ -25,32 +26,32 @@ public final class TracingList extends ArrayList { private final ExecutionAttributes request; private final Response response; private final TracingExecutionInterceptor config; - private final Context receiveContext; + @Nullable private final Context processParentContext; private boolean firstIterator = true; - private TracingList( - List list, + public static TracingList wrap( + List messages, Instrumenter instrumenter, ExecutionAttributes request, Response response, TracingExecutionInterceptor config, - Context receiveContext) { - super(list); - this.instrumenter = instrumenter; - this.request = request; - this.response = response; - this.config = config; - this.receiveContext = receiveContext; + @Nullable Context processParentContext) { + return new TracingList(messages, instrumenter, request, response, config, processParentContext); } - public static TracingList wrap( - List list, + private TracingList( + List messages, Instrumenter instrumenter, ExecutionAttributes request, Response response, TracingExecutionInterceptor config, - Context receiveContext) { - return new TracingList(list, instrumenter, request, response, config, receiveContext); + @Nullable Context processParentContext) { + super(messages); + this.instrumenter = instrumenter; + this.request = request; + this.response = response; + this.config = config; + this.processParentContext = processParentContext; } public void disableTracing() { @@ -65,9 +66,7 @@ public Iterator iterator() { // However, this is not thread-safe, but usually the first (hopefully only) traversal of // List is performed in the same thread that called receiveMessage() if (firstIterator) { - it = - TracingIterator.wrap( - super.iterator(), instrumenter, request, response, config, receiveContext); + it = TracingIterator.wrap(super.iterator(), this); firstIterator = false; } else { it = super.iterator(); @@ -99,7 +98,8 @@ public TracingExecutionInterceptor getConfig() { return config; } - public Context getReceiveContext() { - return receiveContext; + @Nullable + public Context getProcessParentContext() { + return processParentContext; } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsParentContextTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsParentContextTest.java index 8ddde584b64c..74226fca12dc 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsParentContextTest.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/SqsParentContextTest.java @@ -35,6 +35,11 @@ void preservesParentContextValues() { private static SqsMessage messageWithTraceHeader() { return new SqsMessage() { + @Override + public Context getCreationContext() { + return Context.root(); + } + @Override public Map messageAttributes() { return emptyMap(); diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.java index 92a952e69b37..d81605186e96 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.awssdk.v2_2; import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.instrumentation.testing.junit.message.MessageHeaderUtil.headerAttributeKey; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; @@ -28,6 +29,7 @@ import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; @@ -261,6 +263,45 @@ void testCaptureMessageHeaderAsAttributeSpan() { assertSqsTraces(false, true); } + @Test + void testReceiveSpanLinksToProducer() { + assumeTrue(emitStableMessagingSemconv()); + SqsClientBuilder builder = SqsClient.builder(); + configureSdkClient(builder); + SqsClient client = configureSqsClient(builder.build()); + + client.createQueue(createQueueRequest); + client.sendMessage(sendMessageRequest); + client.receiveMessage(receiveMessageRequest); + + AtomicReference publishSpan = new AtomicReference<>(); + getTesting() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("Sqs.CreateQueue").hasKind(SpanKind.CLIENT)), + trace -> + trace.hasSpansSatisfyingExactly( + span -> { + publishSpan.set(trace.getSpan(0)); + span.hasName("publish testSdkSqs").hasKind(SpanKind.PRODUCER); + }), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("receive testSdkSqs") + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasLinksSatisfying( + links -> + assertThat(links) + .singleElement() + .satisfies( + link -> + assertThat(link.getSpanContext().getSpanId()) + .isEqualTo(publishSpan.get().getSpanId()))))); + } + @Test void testBatchSqsProducerConsumerServicesSync() { SqsClientBuilder builder = SqsClient.builder(); diff --git a/instrumentation/jms/jms-common-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/common/v1_1/JmsReceiveSpanUtil.java b/instrumentation/jms/jms-common-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/common/v1_1/JmsReceiveSpanUtil.java index f31fdad5241e..6af26fdee84e 100644 --- a/instrumentation/jms/jms-common-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/common/v1_1/JmsReceiveSpanUtil.java +++ b/instrumentation/jms/jms-common-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/common/v1_1/JmsReceiveSpanUtil.java @@ -5,6 +5,8 @@ package io.opentelemetry.javaagent.instrumentation.jms.common.v1_1; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; + import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.ContextPropagators; @@ -44,7 +46,7 @@ public static void createReceiveSpan( throwable, timer.startTime(), timer.now()); - JmsReceiveContextHolder.set(receiveContext); + JmsReceiveContextHolder.set(emitStableMessagingSemconv() ? parentContext : receiveContext); } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/build.gradle.kts b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/build.gradle.kts index 1ae520b678fd..5bd5f7590b48 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/build.gradle.kts +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/build.gradle.kts @@ -57,12 +57,24 @@ tasks { testClassesDirs = sourceSets.test.get().output.classesDirs classpath = sourceSets.test.get().runtimeClasspath filter { - includeTestsMatching("KafkaClientSuppressReceiveSpansTest.testAbandonedIteratorDoesNotParentNextProcessSpan") + includeTestsMatching("KafkaClientDefaultTest.testKafkaProducerAndConsumerSpan") } + jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=true") jvmArgs("-Dotel.semconv-stability.preview=messaging") systemProperty("metadataConfig", "otel.semconv-stability.opt-in=messaging") } + val testMessagingPreviewDup = register("testMessagingPreviewDup") { + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath + filter { + includeTestsMatching("KafkaClientDefaultTest.testReceiveDoesNotParentProcessSpan") + } + jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=true") + jvmArgs("-Dotel.semconv-stability.preview=messaging/dup") + systemProperty("metadataConfig", "otel.semconv-stability.opt-in=messaging/dup") + } + val testExperimental = register("testExperimental") { testClassesDirs = sourceSets.test.get().output.classesDirs classpath = sourceSets.test.get().runtimeClasspath @@ -109,6 +121,7 @@ tasks { testPropagationDisabled, testReceiveSpansDisabled, testMessagingPreview, + testMessagingPreviewDup, testExperimental, ) } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaConsumerInstrumentation.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaConsumerInstrumentation.java index fd4c5f5de016..801d01f27768 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaConsumerInstrumentation.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaConsumerInstrumentation.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.kafkaclients.v0_11; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; import static io.opentelemetry.javaagent.instrumentation.kafkaclients.v0_11.KafkaSingletons.consumerReceiveInstrumenter; import static net.bytebuddy.matcher.ElementMatchers.isPublic; @@ -73,9 +74,9 @@ public static void onExit( // disable process tracing and store the receive span for each individual record too boolean previousValue = KafkaClientsConsumerProcessTracing.setWrappingEnabled(false); try { - Context context = null; + Context receiveContext = null; if (consumerReceiveInstrumenter().shouldStart(parentContext, request)) { - context = + receiveContext = InstrumenterUtil.startAndEnd( consumerReceiveInstrumenter(), parentContext, @@ -86,16 +87,14 @@ public static void onExit( timer.now()); } - // we're storing the context of the receive span so that process spans can use it as - // parent context even though the span has ended - // this is the suggested behavior according to the spec batch receive scenario: - // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/messaging/messaging-spans.md#batch-receiving + Context processParentContext = + emitStableMessagingSemconv() ? parentContext : receiveContext; // we're attaching the consumer to the records to be able to retrieve things like consumer // group or clientId later - KafkaConsumerContextUtil.set(records, context, consumer); + KafkaConsumerContextUtil.set(records, processParentContext, consumer); for (ConsumerRecord record : records) { - KafkaConsumerContextUtil.set(record, context, consumer); + KafkaConsumerContextUtil.set(record, processParentContext, consumer); } } finally { KafkaClientsConsumerProcessTracing.setWrappingEnabled(previousValue); diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java index 7a240656bdf8..01d65115f0af 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java @@ -77,6 +77,42 @@ void testKafkaProducerAndConsumerSpan(boolean testHeaders) throws Exception { }); } AtomicReference producerSpan = new AtomicReference<>(); + if (emitStableMessagingSemconv()) { + testing.waitAndAssertSortedTraces( + orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CLIENT), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> { + span.hasName("send " + SHARED_TOPIC) + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + sendAttributes("10", greeting, testHeaders)); + producerSpan.set(span.actual()); + }, + span -> + span.hasName("process " + SHARED_TOPIC) + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) + .hasAttributesSatisfyingExactly( + processAttributes("10", greeting, testHeaders, false)), + span -> span.hasName("processing").hasParent(trace.getSpan(2)), + span -> + span.hasName("producer callback") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("poll " + SHARED_TOPIC) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasAttributesSatisfyingExactly(receiveAttributes(testHeaders)))); + return; + } + testing.waitAndAssertSortedTraces( orderByRootSpanKind( SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER), @@ -120,6 +156,39 @@ SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CON span -> span.hasName("processing").hasParent(trace.getSpan(1)))); } + @Test + void testReceiveDoesNotParentProcessSpan() throws Exception { + producer.send(new ProducerRecord<>(SHARED_TOPIC, 10, "Hello Kafka!")).get(5, SECONDS); + + awaitUntilConsumerIsReady(); + ConsumerRecords records = poll(Duration.ofSeconds(5)); + assertThat(records.count()).isEqualTo(1); + + for (ConsumerRecord ignored : records) { + testing.runWithSpan("processing", () -> {}); + } + + AtomicReference producerSpan = new AtomicReference<>(); + testing.waitAndAssertSortedTraces( + orderByRootSpanKind(SpanKind.PRODUCER, SpanKind.CLIENT), + trace -> + trace.hasSpansSatisfyingExactly( + span -> { + span.hasName("send " + SHARED_TOPIC).hasKind(SpanKind.PRODUCER).hasNoParent(); + producerSpan.set(span.actual()); + }, + span -> + span.hasName("process " + SHARED_TOPIC) + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasLinks(LinkData.create(producerSpan.get().getSpanContext())), + span -> span.hasName("processing").hasParent(trace.getSpan(1))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("poll " + SHARED_TOPIC).hasKind(SpanKind.CLIENT).hasNoParent())); + } + @DisplayName("test pass through tombstone") @Test void testPassThroughTombstone() throws Exception { @@ -135,6 +204,35 @@ void testPassThroughTombstone() throws Exception { } AtomicReference producerSpan = new AtomicReference<>(); + if (emitStableMessagingSemconv()) { + testing.waitAndAssertSortedTraces( + orderByRootSpanKind(SpanKind.PRODUCER, SpanKind.CLIENT), + trace -> + trace.hasSpansSatisfyingExactly( + span -> { + span.hasName("send " + SHARED_TOPIC) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasAttributesSatisfyingExactly(sendAttributes(null, null, false)); + producerSpan.set(span.actual()); + }, + span -> + span.hasName("process " + SHARED_TOPIC) + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) + .hasAttributesSatisfyingExactly( + processAttributes(null, null, false, false))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("poll " + SHARED_TOPIC) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasAttributesSatisfyingExactly(receiveAttributes(false)))); + return; + } + testing.waitAndAssertSortedTraces( orderByRootSpanKind( SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER), @@ -204,6 +302,35 @@ void testRecordsWithTopicPartitionKafkaConsume(boolean testListIterator) throws } AtomicReference producerSpan = new AtomicReference<>(); + if (emitStableMessagingSemconv()) { + testing.waitAndAssertSortedTraces( + orderByRootSpanKind(SpanKind.PRODUCER, SpanKind.CLIENT), + trace -> + trace.hasSpansSatisfyingExactly( + span -> { + span.hasName("send " + SHARED_TOPIC) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasAttributesSatisfyingExactly(sendAttributes(null, greeting, false)); + producerSpan.set(span.actual()); + }, + span -> + span.hasName("process " + SHARED_TOPIC) + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) + .hasAttributesSatisfyingExactly( + processAttributes(null, greeting, false, false))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("poll " + SHARED_TOPIC) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasAttributesSatisfyingExactly(receiveAttributes(false)))); + return; + } + testing.waitAndAssertSortedTraces( orderByRootSpanKind( SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER), @@ -278,6 +405,41 @@ void testKafkaHeaderNull() throws Exception { }); } AtomicReference producerSpan = new AtomicReference<>(); + if (emitStableMessagingSemconv()) { + testing.waitAndAssertSortedTraces( + orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CLIENT), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> { + span.hasName("send " + SHARED_TOPIC) + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly(sendAttributes("10", greeting, false)); + producerSpan.set(span.actual()); + }, + span -> + span.hasName("process " + SHARED_TOPIC) + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) + .hasAttributesSatisfyingExactly( + processAttributes("10", greeting, false, false)), + span -> span.hasName("processing").hasParent(trace.getSpan(2)), + span -> + span.hasName("producer callback") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("poll " + SHARED_TOPIC) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasAttributesSatisfyingExactly(receiveAttributes(false)))); + return; + } + testing.waitAndAssertSortedTraces( orderByRootSpanKind( SpanKind.INTERNAL, emitStableMessagingSemconv() ? SpanKind.CLIENT : SpanKind.CONSUMER), diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/internal/KafkaConsumerTelemetry.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/internal/KafkaConsumerTelemetry.java index df5f398488e0..c800e69f61a6 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/internal/KafkaConsumerTelemetry.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/internal/KafkaConsumerTelemetry.java @@ -5,6 +5,7 @@ package io.opentelemetry.instrumentation.kafkaclients.v2_6.internal; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static java.util.Collections.emptyMap; import io.opentelemetry.context.Context; @@ -77,9 +78,9 @@ public Context buildAndFinishSpan( } Context parentContext = Context.current(); KafkaReceiveRequest request = KafkaReceiveRequest.create(records, consumerGroup, clientId); - Context context = null; + Context receiveContext = null; if (consumerReceiveInstrumenter.shouldStart(parentContext, request)) { - context = + receiveContext = InstrumenterUtil.startAndEnd( consumerReceiveInstrumenter, parentContext, @@ -90,11 +91,7 @@ public Context buildAndFinishSpan( timer.now()); } - // we're returning the context of the receive span so that process spans can use it as - // parent context even though the span has ended - // this is the suggested behavior according to the spec batch receive scenario: - // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/messaging/messaging-spans.md#batch-receiving - return context; + return emitStableMessagingSemconv() ? parentContext : receiveContext; } public void buildAndFinishErrorSpan( diff --git a/instrumentation/kafka/kafka-connect-2.6/testing/build.gradle.kts b/instrumentation/kafka/kafka-connect-2.6/testing/build.gradle.kts index bf090179c83a..74056df732b1 100644 --- a/instrumentation/kafka/kafka-connect-2.6/testing/build.gradle.kts +++ b/instrumentation/kafka/kafka-connect-2.6/testing/build.gradle.kts @@ -43,7 +43,17 @@ tasks { systemProperty("metadataConfig", "otel.semconv-stability.opt-in=database") } + val testMessagingPreview = register("testMessagingPreview") { + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath + filter { + includeTestsMatching("MongoKafkaConnectSinkTaskTest") + } + jvmArgs("-Dotel.semconv-stability.preview=messaging") + systemProperty("metadataConfig", "otel.semconv-stability.opt-in=messaging") + } + check { - dependsOn(testStableSemconv) + dependsOn(testStableSemconv, testMessagingPreview) } } diff --git a/instrumentation/kafka/kafka-connect-2.6/testing/src/test/java/io/opentelemetry/instrumentation/kafkaconnect/v2_6/KafkaConnectSinkTaskBaseTest.java b/instrumentation/kafka/kafka-connect-2.6/testing/src/test/java/io/opentelemetry/instrumentation/kafkaconnect/v2_6/KafkaConnectSinkTaskBaseTest.java index d4eab1656cee..92d2d2ef2708 100644 --- a/instrumentation/kafka/kafka-connect-2.6/testing/src/test/java/io/opentelemetry/instrumentation/kafkaconnect/v2_6/KafkaConnectSinkTaskBaseTest.java +++ b/instrumentation/kafka/kafka-connect-2.6/testing/src/test/java/io/opentelemetry/instrumentation/kafkaconnect/v2_6/KafkaConnectSinkTaskBaseTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.instrumentation.kafkaconnect.v2_6; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.restassured.RestAssured.given; import static java.lang.String.format; import static java.time.temporal.ChronoUnit.MINUTES; @@ -130,6 +131,16 @@ protected String getKafkaBootstrapServers() { return kafka.getHost() + ":" + kafkaExposedPort; } + protected static String messagingSpanName( + String destinationName, String oldOperationName, String stableOperationName) { + if (emitStableMessagingSemconv()) { + return destinationName == null + ? stableOperationName + : stableOperationName + " " + destinationName; + } + return (destinationName == null ? "unknown" : destinationName) + " " + oldOperationName; + } + @Override public TelemetryRetriever getTelemetryRetriever() { return telemetryRetriever; @@ -278,7 +289,9 @@ private void setupKafkaConnect() { .withEnv("OTEL_METRIC_EXPORT_INTERVAL", "1000") .withEnv( "OTEL_SEMCONV_STABILITY_OPT_IN", - System.getProperty("otel.semconv-stability.opt-in")) + emitStableMessagingSemconv() + ? "messaging" + : System.getProperty("otel.semconv-stability.opt-in")) .withEnv("CONNECT_BOOTSTRAP_SERVERS", getInternalKafkaBootstrapServers()) .withEnv("CONNECT_REST_ADVERTISED_HOST_NAME", KAFKA_CONNECT_NETWORK_ALIAS) .withEnv("CONNECT_PLUGIN_PATH", PLUGIN_PATH_CONTAINER) diff --git a/instrumentation/kafka/kafka-connect-2.6/testing/src/test/java/io/opentelemetry/instrumentation/kafkaconnect/v2_6/MongoKafkaConnectSinkTaskTest.java b/instrumentation/kafka/kafka-connect-2.6/testing/src/test/java/io/opentelemetry/instrumentation/kafkaconnect/v2_6/MongoKafkaConnectSinkTaskTest.java index b24171389ce3..d23ad094ea0b 100644 --- a/instrumentation/kafka/kafka-connect-2.6/testing/src/test/java/io/opentelemetry/instrumentation/kafkaconnect/v2_6/MongoKafkaConnectSinkTaskTest.java +++ b/instrumentation/kafka/kafka-connect-2.6/testing/src/test/java/io/opentelemetry/instrumentation/kafkaconnect/v2_6/MongoKafkaConnectSinkTaskTest.java @@ -7,11 +7,14 @@ import static io.opentelemetry.api.trace.SpanKind.CONSUMER; import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_BATCH_MESSAGE_COUNT; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION_TYPE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MessagingOperationTypeIncubatingValues.PROCESS; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MessagingSystemIncubatingValues.KAFKA; @@ -128,32 +131,41 @@ void testSingleMessage() throws IOException { // producer is in a separate trace, linked to consumer with a span link trace.hasSpansSatisfyingExactly( span -> { - span.hasName(testTopicName + " publish").hasKind(SpanKind.PRODUCER).hasNoParent(); + span.hasName(messagingSpanName(testTopicName, "publish", "send")) + .hasKind(SpanKind.PRODUCER) + .hasNoParent(); producerSpanContext.set(span.actual().getSpanContext()); }), trace -> // kafka connect sends message to status topic while processing our message trace.hasSpansSatisfyingExactly( span -> - span.hasName("kafka-connect-status publish") + span.hasName(messagingSpanName("kafka-connect-status", "publish", "send")) .hasKind(SpanKind.PRODUCER) .hasNoParent(), span -> - span.hasName("kafka-connect-status process") + span.hasName(messagingSpanName("kafka-connect-status", "process", "process")) .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(0))), trace -> // kafka connect consumer trace, linked to producer span via a span link trace.hasSpansSatisfyingExactly( span -> - span.hasName(testTopicName + " process") + span.hasName(messagingSpanName(testTopicName, "process", "process")) .hasKind(CONSUMER) .hasNoParent() .hasLinks(LinkData.create(producerSpanContext.get())) .hasAttributesSatisfyingExactly( equalTo(MESSAGING_BATCH_MESSAGE_COUNT, 1), equalTo(MESSAGING_DESTINATION_NAME, testTopicName), - equalTo(MESSAGING_OPERATION, PROCESS), + equalTo( + MESSAGING_OPERATION, emitStableMessagingSemconv() ? null : PROCESS), + equalTo( + MESSAGING_OPERATION_NAME, + emitStableMessagingSemconv() ? "process" : null), + equalTo( + MESSAGING_OPERATION_TYPE, + emitStableMessagingSemconv() ? "process" : null), equalTo(MESSAGING_SYSTEM, KAFKA), satisfies(THREAD_ID, val -> val.isNotZero()), satisfies(THREAD_NAME, val -> val.isNotBlank())), @@ -217,11 +229,11 @@ void testMultiTopic() throws IOException { // kafka connect sends message to status topic while processing our message trace.hasSpansSatisfyingExactly( span -> - span.hasName("kafka-connect-status publish") + span.hasName(messagingSpanName("kafka-connect-status", "publish", "send")) .hasKind(SpanKind.PRODUCER) .hasNoParent(), span -> - span.hasName("kafka-connect-status process") + span.hasName(messagingSpanName("kafka-connect-status", "process", "process")) .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(0))); @@ -234,19 +246,19 @@ void testMultiTopic() throws IOException { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasNoParent(), span -> { - span.hasName(topicName1 + " publish") + span.hasName(messagingSpanName(topicName1, "publish", "send")) .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)); producerSpanContext1.set(span.actual().getSpanContext()); }, span -> { - span.hasName(topicName2 + " publish") + span.hasName(messagingSpanName(topicName2, "publish", "send")) .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)); producerSpanContext2.set(span.actual().getSpanContext()); }, span -> { - span.hasName(topicName3 + " publish") + span.hasName(messagingSpanName(topicName3, "publish", "send")) .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)); producerSpanContext3.set(span.actual().getSpanContext()); @@ -258,7 +270,7 @@ void testMultiTopic() throws IOException { // kafka connect consumer trace, linked to producer span via a span link trace.hasSpansSatisfyingExactly( span -> - span.hasName("unknown process") + span.hasName(messagingSpanName(null, "process", "process")) .hasKind(CONSUMER) .hasNoParent() .hasLinks( @@ -267,7 +279,14 @@ void testMultiTopic() throws IOException { LinkData.create(producerSpanContext3.get())) .hasAttributesSatisfyingExactly( equalTo(MESSAGING_BATCH_MESSAGE_COUNT, 3), - equalTo(MESSAGING_OPERATION, PROCESS), + equalTo( + MESSAGING_OPERATION, emitStableMessagingSemconv() ? null : PROCESS), + equalTo( + MESSAGING_OPERATION_NAME, + emitStableMessagingSemconv() ? "process" : null), + equalTo( + MESSAGING_OPERATION_TYPE, + emitStableMessagingSemconv() ? "process" : null), equalTo(MESSAGING_SYSTEM, KAFKA), satisfies(THREAD_ID, val -> val.isNotZero()), satisfies(THREAD_NAME, val -> val.isNotBlank())), diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java index 3beac3603e4c..ccbef59e97bb 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java @@ -200,10 +200,11 @@ public static Context startAndEndConsumerReceive( throwable, timer.startTime(), timer.now()); + Context processParentContext = emitStableMessagingSemconv() ? parent : receiveContext; // injected context is used in MessageListenerInstrumentation and also in the spring-pulsar // instrumentation - VirtualFieldStore.inject(message, receiveContext); - return receiveContext; + VirtualFieldStore.inject(message, processParentContext); + return processParentContext; } @Nullable @@ -230,12 +231,13 @@ private static Context startAndEndConsumerReceive( throwable, timer.startTime(), timer.now()); + Context processParentContext = emitStableMessagingSemconv() ? parent : receiveContext; // injected context is used in MessageListenerInstrumentation and also in the spring-pulsar // instrumentation for (Message message : messages) { - VirtualFieldStore.inject(message, receiveContext); + VirtualFieldStore.inject(message, processParentContext); } - return receiveContext; + return processParentContext; } public static CompletableFuture wrap(CompletableFuture future) { diff --git a/instrumentation/rocketmq/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqConsumerInstrumenter.java b/instrumentation/rocketmq/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqConsumerInstrumenter.java index 218cc5aa53e7..a757dbc4d640 100644 --- a/instrumentation/rocketmq/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqConsumerInstrumenter.java +++ b/instrumentation/rocketmq/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqConsumerInstrumenter.java @@ -5,6 +5,8 @@ package io.opentelemetry.instrumentation.rocketmqclient.v4_8; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; + import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import java.util.List; @@ -32,11 +34,13 @@ Context start(Context parentContext, List msgs) { } } else { if (batchReceiveInstrumenter.shouldStart(parentContext, null)) { - Context rootContext = batchReceiveInstrumenter.start(parentContext, null); + Context receiveContext = batchReceiveInstrumenter.start(parentContext, null); + Context processParentContext = + emitStableMessagingSemconv() ? parentContext : receiveContext; for (MessageExt message : msgs) { - createChildSpan(rootContext, message); + createChildSpan(processParentContext, message); } - return rootContext; + return receiveContext; } } return parentContext; diff --git a/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/ReceiveSpanFinishingCallback.java b/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/ReceiveSpanFinishingCallback.java index 3ea54e810ada..26d2a8654212 100644 --- a/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/ReceiveSpanFinishingCallback.java +++ b/instrumentation/rocketmq/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/ReceiveSpanFinishingCallback.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.rocketmqclient.v5_0; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static io.opentelemetry.javaagent.instrumentation.rocketmqclient.v5_0.RocketMqSingletons.consumerReceiveInstrumenter; import apache.rocketmq.v2.ReceiveMessageRequest; @@ -51,8 +52,9 @@ public void onSuccess(ReceiveMessageResult receiveMessageResult) { null, timer.startTime(), timer.now()); + Context processParentContext = emitStableMessagingSemconv() ? parentContext : context; for (MessageView messageView : messageViews) { - VirtualFieldStore.setContextByMessage(messageView, context); + VirtualFieldStore.setContextByMessage(messageView, processParentContext); } } } diff --git a/instrumentation/spring/spring-cloud-aws-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/cloud/aws/v3_0/SpringAwsUtil.java b/instrumentation/spring/spring-cloud-aws-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/cloud/aws/v3_0/SpringAwsUtil.java index fe02d827b412..499b9d64b92c 100644 --- a/instrumentation/spring/spring-cloud-aws-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/cloud/aws/v3_0/SpringAwsUtil.java +++ b/instrumentation/spring/spring-cloud-aws-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/cloud/aws/v3_0/SpringAwsUtil.java @@ -12,7 +12,6 @@ import io.opentelemetry.instrumentation.awssdk.v2_2.internal.Response; import io.opentelemetry.instrumentation.awssdk.v2_2.internal.SqsMessage; import io.opentelemetry.instrumentation.awssdk.v2_2.internal.SqsMessageImpl; -import io.opentelemetry.instrumentation.awssdk.v2_2.internal.SqsParentContext; import io.opentelemetry.instrumentation.awssdk.v2_2.internal.SqsProcessRequest; import io.opentelemetry.instrumentation.awssdk.v2_2.internal.TracingExecutionInterceptor; import io.opentelemetry.instrumentation.awssdk.v2_2.internal.TracingList; @@ -83,10 +82,11 @@ public static Scope handleBatch(Collection> messages) { if (tracingContext == null) { return null; } - SqsMessage wrappedMessage = SqsMessageImpl.wrap(tracingContext.sqsMessage); - Context parentContext = tracingContext.receiveContext; + SqsMessage wrappedMessage = + SqsMessageImpl.wrap(tracingContext.sqsMessage, tracingContext.config); + Context parentContext = tracingContext.processParentContext; if (parentContext == null) { - parentContext = SqsParentContext.ofMessage(wrappedMessage, tracingContext.config); + parentContext = wrappedMessage.getCreationContext(); } return parentContext.makeCurrent(); } @@ -121,7 +121,7 @@ private static class TracingContext { private final Response response; private final Instrumenter instrumenter; private final TracingExecutionInterceptor config; - @Nullable private final Context receiveContext; + @Nullable private final Context processParentContext; private final software.amazon.awssdk.services.sqs.model.Message sqsMessage; private TracingContext( @@ -130,16 +130,16 @@ private TracingContext( this.response = tracingList.getResponse(); this.instrumenter = tracingList.getInstrumenter(); this.config = tracingList.getConfig(); - this.receiveContext = tracingList.getReceiveContext(); + this.processParentContext = tracingList.getProcessParentContext(); this.sqsMessage = sqsMessage; } @Nullable MessageScope trace() { - SqsMessage wrappedMessage = SqsMessageImpl.wrap(sqsMessage); - Context parentContext = receiveContext; + SqsMessage wrappedMessage = SqsMessageImpl.wrap(sqsMessage, config); + Context parentContext = processParentContext; if (parentContext == null) { - parentContext = SqsParentContext.ofMessage(wrappedMessage, config); + parentContext = wrappedMessage.getCreationContext(); } SqsProcessRequest processRequest = SqsProcessRequest.create(request, wrappedMessage); if (!instrumenter.shouldStart(parentContext, processRequest)) { From 956e35f489b5239363fe8eb8af4fdb89d3dc3b0b Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 18 Jul 2026 19:43:37 -0700 Subject: [PATCH 16/18] Address review comments from copilot-pull-request-reviewer: enable Kafka receive spans in preview --- .../javaagent/build.gradle.kts | 4 +- .../v0_11/KafkaConsumerInstrumentation.java | 2 +- .../v0_11/KafkaClientDefaultTest.java | 41 +++++++++++++++++++ .../KafkaClientSuppressReceiveSpansTest.java | 35 ---------------- .../v2_6/internal/KafkaConsumerTelemetry.java | 5 ++- .../internal/KafkaConsumerContextUtil.java | 15 +++++++ .../internal/KafkaInstrumenterFactory.java | 3 +- .../v0_11/internal/TracingIterator.java | 20 +-------- 8 files changed, 65 insertions(+), 60 deletions(-) diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/build.gradle.kts b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/build.gradle.kts index 5bd5f7590b48..9578c52abef3 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/build.gradle.kts +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/build.gradle.kts @@ -58,8 +58,8 @@ tasks { classpath = sourceSets.test.get().runtimeClasspath filter { includeTestsMatching("KafkaClientDefaultTest.testKafkaProducerAndConsumerSpan") + includeTestsMatching("KafkaClientDefaultTest.testAbandonedIteratorDoesNotParentNextProcessSpan") } - jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=true") jvmArgs("-Dotel.semconv-stability.preview=messaging") systemProperty("metadataConfig", "otel.semconv-stability.opt-in=messaging") } @@ -70,7 +70,6 @@ tasks { filter { includeTestsMatching("KafkaClientDefaultTest.testReceiveDoesNotParentProcessSpan") } - jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=true") jvmArgs("-Dotel.semconv-stability.preview=messaging/dup") systemProperty("metadataConfig", "otel.semconv-stability.opt-in=messaging/dup") } @@ -96,7 +95,6 @@ tasks { excludeTestsMatching("KafkaClientPropagationDisabledTest") excludeTestsMatching("KafkaClientSuppressReceiveSpansTest") } - jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=true") jvmArgs("-Dotel.semconv-stability.preview=messaging") jvmArgs("-Dotel.instrumentation.common.v3-preview=true") // kafka metrics are disabled by default with v3-preview enabled diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaConsumerInstrumentation.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaConsumerInstrumentation.java index 801d01f27768..7d7f2e5e3ef2 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaConsumerInstrumentation.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaConsumerInstrumentation.java @@ -68,7 +68,7 @@ public static void onExit( return; } - Context parentContext = currentContext(); + Context parentContext = KafkaConsumerContextUtil.withoutLeakedProcessSpan(currentContext()); KafkaReceiveRequest request = KafkaReceiveRequest.create(records, consumer); // disable process tracing and store the receive span for each individual record too diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java index 01d65115f0af..d59b70ec7964 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java @@ -10,8 +10,11 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaClientBaseTest; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaClientPropagationBaseTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; @@ -19,6 +22,7 @@ import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.data.SpanData; import java.time.Duration; +import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.concurrent.atomic.AtomicReference; @@ -189,6 +193,43 @@ void testReceiveDoesNotParentProcessSpan() throws Exception { span.hasName("poll " + SHARED_TOPIC).hasKind(SpanKind.CLIENT).hasNoParent())); } + @Test + void testAbandonedIteratorDoesNotParentNextProcessSpan() throws Exception { + assumeTrue(emitStableMessagingSemconv()); + producer.send(new ProducerRecord<>(SHARED_TOPIC, "first")).get(5, SECONDS); + awaitUntilConsumerIsReady(); + Iterator> firstIterator = poll(Duration.ofSeconds(5)).iterator(); + assertThat(firstIterator.hasNext()).isTrue(); + firstIterator.next(); + + try (Scope ignored = Context.root().makeCurrent()) { + producer.send(new ProducerRecord<>(SHARED_TOPIC, "second")).get(5, SECONDS); + } + Iterator> secondIterator = + poll(Duration.ofSeconds(5)).iterator(); + assertThat(secondIterator.hasNext()).isTrue(); + secondIterator.next(); + assertThat(secondIterator.hasNext()).isFalse(); + assertThat(firstIterator.hasNext()).isFalse(); + + testing.waitAndAssertSortedTraces( + orderByRootSpanKind(SpanKind.PRODUCER, SpanKind.CLIENT), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("send " + SHARED_TOPIC).hasNoParent(), + span -> span.hasName("process " + SHARED_TOPIC).hasParent(trace.getSpan(0))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("send " + SHARED_TOPIC).hasNoParent(), + span -> span.hasName("process " + SHARED_TOPIC).hasParent(trace.getSpan(0))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("poll " + SHARED_TOPIC).hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("poll " + SHARED_TOPIC).hasNoParent())); + } + @DisplayName("test pass through tombstone") @Test void testPassThroughTombstone() throws Exception { diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java index 71a1f83d100e..4f8a584b926a 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java @@ -5,21 +5,16 @@ package io.opentelemetry.javaagent.instrumentation.kafkaclients.v0_11; -import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assumptions.assumeTrue; import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaClientBaseTest; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaClientPropagationBaseTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import java.time.Duration; -import java.util.Iterator; import java.util.List; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; @@ -155,34 +150,4 @@ void testRecordsWithTopicPartitionKafkaConsume() throws Exception { .hasAttributesSatisfyingExactly( processAttributes(null, greeting, false, false)))); } - - @Test - void testAbandonedIteratorDoesNotParentNextProcessSpan() throws Exception { - assumeTrue(emitStableMessagingSemconv()); - producer.send(new ProducerRecord<>(SHARED_TOPIC, "first")).get(5, SECONDS); - awaitUntilConsumerIsReady(); - Iterator> firstIterator = poll(Duration.ofSeconds(5)).iterator(); - assertThat(firstIterator.hasNext()).isTrue(); - firstIterator.next(); - - try (Scope ignored = Context.root().makeCurrent()) { - producer.send(new ProducerRecord<>(SHARED_TOPIC, "second")).get(5, SECONDS); - } - Iterator> secondIterator = - poll(Duration.ofSeconds(5)).iterator(); - assertThat(secondIterator.hasNext()).isTrue(); - secondIterator.next(); - assertThat(secondIterator.hasNext()).isFalse(); - assertThat(firstIterator.hasNext()).isFalse(); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("send " + SHARED_TOPIC).hasNoParent(), - span -> span.hasName("process " + SHARED_TOPIC).hasParent(trace.getSpan(0))), - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("send " + SHARED_TOPIC).hasNoParent(), - span -> span.hasName("process " + SHARED_TOPIC).hasParent(trace.getSpan(0)))); - } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/internal/KafkaConsumerTelemetry.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/internal/KafkaConsumerTelemetry.java index c800e69f61a6..49994ad135c0 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/internal/KafkaConsumerTelemetry.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/internal/KafkaConsumerTelemetry.java @@ -13,6 +13,7 @@ import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; import io.opentelemetry.instrumentation.api.internal.Timer; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaConsumerContext; +import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaConsumerContextUtil; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaProcessRequest; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaReceiveRequest; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaUtil; @@ -76,7 +77,7 @@ public Context buildAndFinishSpan( if (records.isEmpty()) { return null; } - Context parentContext = Context.current(); + Context parentContext = KafkaConsumerContextUtil.withoutLeakedProcessSpan(Context.current()); KafkaReceiveRequest request = KafkaReceiveRequest.create(records, consumerGroup, clientId); Context receiveContext = null; if (consumerReceiveInstrumenter.shouldStart(parentContext, request)) { @@ -96,7 +97,7 @@ public Context buildAndFinishSpan( public void buildAndFinishErrorSpan( Consumer consumer, Timer timer, Throwable error) { - Context parentContext = Context.current(); + Context parentContext = KafkaConsumerContextUtil.withoutLeakedProcessSpan(Context.current()); ConsumerRecords records = new ConsumerRecords<>(emptyMap()); KafkaReceiveRequest request = KafkaReceiveRequest.create( diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaConsumerContextUtil.java b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaConsumerContextUtil.java index d11a423d2d4d..749e8b8a1293 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaConsumerContextUtil.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaConsumerContextUtil.java @@ -5,7 +5,11 @@ package io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; + +import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.internal.SpanKey; import io.opentelemetry.instrumentation.api.util.VirtualField; import javax.annotation.Nullable; import org.apache.kafka.clients.consumer.Consumer; @@ -28,6 +32,17 @@ public final class KafkaConsumerContextUtil { private static final VirtualField, String[]> recordsConsumerInfoField = VirtualField.find(ConsumerRecords.class, String[].class); + public static Context withoutLeakedProcessSpan(Context context) { + if (!emitStableMessagingSemconv()) { + return context; + } + + Span currentSpan = Span.fromContext(context); + return currentSpan == SpanKey.CONSUMER_PROCESS.fromContextOrNull(context) + ? context.with(Span.getInvalid()) + : context; + } + public static KafkaConsumerContext get(ConsumerRecord records) { Context receiveContext = recordContextField.get(records); String consumerGroup = null; diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java index f5fc4befd693..f7e144dd569b 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java @@ -8,6 +8,7 @@ import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingProcessExceptionEventExtractor; import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingReceiveExceptionEventExtractor; import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingSendExceptionEventExtractor; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static java.util.Collections.emptyList; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -127,7 +128,7 @@ public Instrumenter createConsumerReceiveInstrumenter .addAttributesExtractor(new KafkaReceiveAttributesExtractor()) .addAttributesExtractors(extractors) .setErrorCauseExtractor(errorCauseExtractor) - .setEnabled(messagingReceiveInstrumentationEnabled); + .setEnabled(emitStableMessagingSemconv() || messagingReceiveInstrumentationEnabled); setMessagingReceiveExceptionEventExtractor(builder); return builder.buildInstrumenter(MessagingSpanKindExtractor.create(operationType)); } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/TracingIterator.java b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/TracingIterator.java index f7ea85d9c130..30c973d1b18b 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/TracingIterator.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/TracingIterator.java @@ -5,13 +5,9 @@ package io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal; -import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; - -import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.api.internal.SpanKey; import java.util.Iterator; import java.util.function.BooleanSupplier; import javax.annotation.Nullable; @@ -49,23 +45,11 @@ private TracingIterator( Context receiveContext = consumerContext.getContext(); // use the receive CONSUMER as parent if it's available this.parentContext = - receiveContext != null - ? receiveContext - : currentContextWithoutLeakedProcessSpan(Context.current()); + KafkaConsumerContextUtil.withoutLeakedProcessSpan( + receiveContext != null ? receiveContext : Context.current()); this.consumerContext = consumerContext; } - private static Context currentContextWithoutLeakedProcessSpan(Context context) { - if (!emitStableMessagingSemconv()) { - return context; - } - - Span currentSpan = Span.fromContext(context); - return currentSpan == SpanKey.CONSUMER_PROCESS.fromContextOrNull(context) - ? context.with(Span.getInvalid()) - : context; - } - public static Iterator> wrap( Iterator> delegateIterator, Instrumenter instrumenter, From 9bd2fec64a498444dcb6de88a736ada8c5c7aefc Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 18 Jul 2026 20:06:34 -0700 Subject: [PATCH 17/18] Address review comment from copilot-pull-request-reviewer: link Kafka poll spans --- .../kafkaclients/v0_11/KafkaClientDefaultTest.java | 1 + .../common/v0_11/internal/KafkaInstrumenterFactory.java | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java index d59b70ec7964..6e81287963b2 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java @@ -113,6 +113,7 @@ void testKafkaProducerAndConsumerSpan(boolean testHeaders) throws Exception { span.hasName("poll " + SHARED_TOPIC) .hasKind(SpanKind.CLIENT) .hasNoParent() + .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) .hasAttributesSatisfyingExactly(receiveAttributes(testHeaders)))); return; } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java index f7e144dd569b..c7d2f0f902ba 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaInstrumenterFactory.java @@ -129,6 +129,11 @@ public Instrumenter createConsumerReceiveInstrumenter .addAttributesExtractors(extractors) .setErrorCauseExtractor(errorCauseExtractor) .setEnabled(emitStableMessagingSemconv() || messagingReceiveInstrumentationEnabled); + if (emitStableMessagingSemconv()) { + builder.addSpanLinksExtractor( + new KafkaBatchProcessSpanLinksExtractor( + openTelemetry.getPropagators().getTextMapPropagator())); + } setMessagingReceiveExceptionEventExtractor(builder); return builder.buildInstrumenter(MessagingSpanKindExtractor.create(operationType)); } From b342f524db416765f221b0828b74a10d5ef8229f Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 18 Jul 2026 20:29:11 -0700 Subject: [PATCH 18/18] Address review comment from copilot-pull-request-reviewer: suppress temporary destination names --- .../messaging/MessagingSpanNameExtractor.java | 11 ++++++----- .../MessagingSpanNameExtractorTest.java | 17 +++++++++++------ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractor.java index 3c56d08d475c..f0b50b768dc6 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractor.java @@ -70,11 +70,12 @@ public static MessagingSpanNameExtractorBuilder builder( @Override public String extract(REQUEST request) { if (emitStableMessagingSemconv()) { - String destinationName = getter.getDestinationTemplate(request); - if (destinationName == null - && !getter.isTemporaryDestination(request) - && !getter.isAnonymousDestination(request)) { - destinationName = getter.getDestination(request); + String destinationName = null; + if (!getter.isTemporaryDestination(request) && !getter.isAnonymousDestination(request)) { + destinationName = getter.getDestinationTemplate(request); + if (destinationName == null) { + destinationName = getter.getDestination(request); + } } return destinationName == null ? operationName : operationName + " " + destinationName; } diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractorTest.java index 088901a04894..c6c1cde6838c 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingSpanNameExtractorTest.java @@ -8,6 +8,8 @@ import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableMessagingSemconv; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.argumentSet; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; @@ -39,12 +41,12 @@ void shouldExtractSpanName( Message message = new Message(); if (emitStableMessagingSemconv()) { - when(getter.getDestinationTemplate(message)).thenReturn(destinationTemplate); - if (destinationTemplate == null) { - when(getter.isTemporaryDestination(message)).thenReturn(isTemporaryQueue); - if (!isTemporaryQueue) { - when(getter.isAnonymousDestination(message)).thenReturn(isAnonymousQueue); - if (!isAnonymousQueue) { + when(getter.isTemporaryDestination(message)).thenReturn(isTemporaryQueue); + if (!isTemporaryQueue) { + when(getter.isAnonymousDestination(message)).thenReturn(isAnonymousQueue); + if (!isAnonymousQueue) { + when(getter.getDestinationTemplate(message)).thenReturn(destinationTemplate); + if (destinationTemplate == null) { when(getter.getDestination(message)).thenReturn(destinationName); } } @@ -66,6 +68,9 @@ void shouldExtractSpanName( // then assertThat(actualSpanName).isEqualTo(emitStableMessagingSemconv() ? spanName : oldSpanName); + if (emitStableMessagingSemconv() && (isTemporaryQueue || isAnonymousQueue)) { + verify(getter, never()).getDestinationTemplate(message); + } } static Stream spanNameParams() {