From 8dc1398ae5bf05e32e7bdb637b37ca2fa81c05d1 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 19 Jan 2026 17:22:39 +0100 Subject: [PATCH 01/15] Add RPC semantic convention stable opt-in support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds support for the stable RPC semantic conventions via the `otel.semconv-stability.opt-in=rpc` configuration option. Key changes: - Add emitOldRpcSemconv()/emitStableRpcSemconv() to SemconvStability - Add stableRpcSystemName() mapping (apache_dubbo→dubbo, connect_rpc→connectrpc) - Add RpcAttributesGetter.getFullMethod() for "service/method" format - Update RpcCommonAttributesExtractor for dual semconv emission - Update RpcClientMetrics/RpcServerMetrics for dual histogram (ms→s unit change) --- .../build.gradle.kts | 4 +- .../semconv/rpc/RpcAttributesGetter.java | 20 + .../semconv/rpc/RpcClientMetrics.java | 86 +++-- .../rpc/RpcCommonAttributesExtractor.java | 35 +- .../semconv/rpc/RpcMetricsAdvice.java | 86 ++++- .../semconv/rpc/RpcServerMetrics.java | 86 +++-- .../rpc/RpcAttributesExtractorTest.java | 51 ++- .../semconv/rpc/RpcClientMetricsTest.java | 357 +++++++++++------ .../semconv/rpc/RpcServerMetricsTest.java | 358 ++++++++++++------ .../api/internal/SemconvStability.java | 63 +++ 10 files changed, 826 insertions(+), 320 deletions(-) diff --git a/instrumentation-api-incubator/build.gradle.kts b/instrumentation-api-incubator/build.gradle.kts index 45924c2b1882..2a0d8dc8bd50 100644 --- a/instrumentation-api-incubator/build.gradle.kts +++ b/instrumentation-api-incubator/build.gradle.kts @@ -91,14 +91,14 @@ tasks { val testStableSemconv by registering(Test::class) { testClassesDirs = sourceSets.test.get().output.classesDirs classpath = sourceSets.test.get().runtimeClasspath - jvmArgs("-Dotel.semconv-stability.opt-in=database,code,service.peer") + jvmArgs("-Dotel.semconv-stability.opt-in=database,code,service.peer,rpc") inputs.dir(jflexOutputDir) } val testBothSemconv by registering(Test::class) { testClassesDirs = sourceSets.test.get().output.classesDirs classpath = sourceSets.test.get().runtimeClasspath - jvmArgs("-Dotel.semconv-stability.opt-in=database/dup,code/dup,service.peer/dup") + jvmArgs("-Dotel.semconv-stability.opt-in=database/dup,code/dup,service.peer/dup,rpc/dup") inputs.dir(jflexOutputDir) } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java index 0da544340f6b..f3ddc224c8c7 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java @@ -34,4 +34,24 @@ default Long getRequestSize(REQUEST request) { default Long getResponseSize(REQUEST request) { return null; } + + /** + * Returns the fully-qualified RPC method name for stable semconv. + * + *

The default implementation concatenates service + "/" + method. Framework implementations + * can override for efficiency if they already have the fully-qualified name available. + * + * @param request the request object + * @return the fully-qualified RPC method name (e.g., "my.Service/Method"), or null if service or + * method is unavailable + */ + @Nullable + default String getFullMethod(REQUEST request) { + String service = getService(request); + String method = getMethod(request); + if (service == null || method == null) { + return null; + } + return service + "/" + method; + } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java index 0135bd20f799..bf686c7ec5a4 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java @@ -19,8 +19,10 @@ import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; +import javax.annotation.Nullable; /** * {@link OperationListener} which keeps track of RPC_CLIENT_REQUEST_METRICS_STATE = ContextKey.named("rpc-client-request-metrics-state"); private static final Logger logger = Logger.getLogger(RpcClientMetrics.class.getName()); - private final DoubleHistogram clientDurationHistogram; - private final LongHistogram clientRequestSize; - private final LongHistogram clientResponseSize; + @Nullable private final DoubleHistogram oldClientDurationHistogram; + @Nullable private final DoubleHistogram stableClientDurationHistogram; + private final LongHistogram oldClientRequestSize; + private final LongHistogram oldClientResponseSize; private RpcClientMetrics(Meter meter) { - DoubleHistogramBuilder durationBuilder = - meter - .histogramBuilder("rpc.client.duration") - .setDescription("The duration of an outbound RPC invocation.") - .setUnit("ms"); - RpcMetricsAdvice.applyClientDurationAdvice(durationBuilder); - clientDurationHistogram = durationBuilder.build(); + // Old metric (milliseconds) + if (SemconvStability.emitOldRpcSemconv()) { + DoubleHistogramBuilder oldDurationBuilder = + meter + .histogramBuilder("rpc.client.duration") + .setDescription("The duration of an outbound RPC invocation.") + .setUnit("ms"); + RpcMetricsAdvice.applyClientDurationAdvice(oldDurationBuilder, false); + oldClientDurationHistogram = oldDurationBuilder.build(); + } else { + oldClientDurationHistogram = null; + } + + // Stable metric (seconds) + if (SemconvStability.emitStableRpcSemconv()) { + DoubleHistogramBuilder stableDurationBuilder = + meter + .histogramBuilder("rpc.client.call.duration") + .setDescription("The duration of an outbound RPC invocation.") + .setUnit("s"); + RpcMetricsAdvice.applyClientDurationAdvice(stableDurationBuilder, true); + stableClientDurationHistogram = stableDurationBuilder.build(); + } else { + stableClientDurationHistogram = null; + } LongHistogramBuilder requestSizeBuilder = meter @@ -55,8 +77,8 @@ private RpcClientMetrics(Meter meter) { .setUnit("By") .setDescription("Measures the size of RPC request messages (uncompressed).") .ofLongs(); - RpcMetricsAdvice.applyClientRequestSizeAdvice(requestSizeBuilder); - clientRequestSize = requestSizeBuilder.build(); + RpcMetricsAdvice.applyDeprecatedClientRequestSizeAdvice(requestSizeBuilder); + oldClientRequestSize = requestSizeBuilder.build(); LongHistogramBuilder responseSizeBuilder = meter @@ -64,8 +86,8 @@ private RpcClientMetrics(Meter meter) { .setUnit("By") .setDescription("Measures the size of RPC response messages (uncompressed).") .ofLongs(); - RpcMetricsAdvice.applyClientRequestSizeAdvice(responseSizeBuilder); - clientResponseSize = responseSizeBuilder.build(); + RpcMetricsAdvice.applyDeprecatedClientRequestSizeAdvice(responseSizeBuilder); + oldClientResponseSize = responseSizeBuilder.build(); } /** @@ -95,17 +117,37 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { return; } Attributes attributes = state.startAttributes().toBuilder().putAll(endAttributes).build(); - clientDurationHistogram.record( - (endNanos - state.startTimeNanos()) / NANOS_PER_MS, attributes, context); + double durationNanos = (endNanos - state.startTimeNanos()); + + // Record to old histogram (milliseconds) + if (oldClientDurationHistogram != null) { + oldClientDurationHistogram.record( + durationNanos / NANOS_PER_MS, + SemconvStability.getOldRpcMetricAttributes(attributes), + context); + } - Long rpcClientRequestBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE); - if (rpcClientRequestBodySize != null) { - clientRequestSize.record(rpcClientRequestBodySize, attributes, context); + // Record to stable histogram (seconds) + if (stableClientDurationHistogram != null) { + stableClientDurationHistogram.record(durationNanos / NANOS_PER_S, attributes, context); } - Long rpcClientResponseBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_RESPONSE_SIZE); - if (rpcClientResponseBodySize != null) { - clientResponseSize.record(rpcClientResponseBodySize, attributes, context); + if (SemconvStability.emitOldRpcSemconv()) { + Long rpcClientRequestBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE); + if (rpcClientRequestBodySize != null) { + oldClientRequestSize.record( + rpcClientRequestBodySize, + SemconvStability.getOldRpcMetricAttributes(attributes), + context); + } + + Long rpcClientResponseBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_RESPONSE_SIZE); + if (rpcClientResponseBodySize != null) { + oldClientResponseSize.record( + rpcClientResponseBodySize, + SemconvStability.getOldRpcMetricAttributes(attributes), + context); + } } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java index f731e703586c..d844d887e1a3 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java @@ -6,19 +6,27 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.rpc; import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet; +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; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; import javax.annotation.Nullable; abstract class RpcCommonAttributesExtractor implements AttributesExtractor { - // copied from RpcIncubatingAttributes static final AttributeKey RPC_METHOD = AttributeKey.stringKey("rpc.method"); + + // Stable semconv keys + static final AttributeKey RPC_SYSTEM_NAME = AttributeKey.stringKey("rpc.system.name"); + + // removed in stable semconv (merged into rpc.method) static final AttributeKey RPC_SERVICE = AttributeKey.stringKey("rpc.service"); + + // use RPC_SYSTEM_NAME for stable semconv static final AttributeKey RPC_SYSTEM = AttributeKey.stringKey("rpc.system"); private final RpcAttributesGetter getter; @@ -29,9 +37,22 @@ abstract class RpcCommonAttributesExtractor @Override public final void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) { - internalSet(attributes, RPC_SYSTEM, getter.getSystem(request)); - internalSet(attributes, RPC_SERVICE, getter.getService(request)); - internalSet(attributes, RPC_METHOD, getter.getMethod(request)); + String system = getter.getSystem(request); + + if (SemconvStability.emitStableRpcSemconv()) { + internalSet( + attributes, + RPC_SYSTEM_NAME, + system == null ? null : SemconvStability.stableRpcSystemName(system)); + internalSet(attributes, RPC_METHOD, getter.getFullMethod(request)); + } + + if (SemconvStability.emitOldRpcSemconv()) { + internalSet(attributes, RPC_SYSTEM, system); + internalSet(attributes, RPC_SERVICE, getter.getService(request)); + internalSet( + attributes, SemconvStability.getOldRpcMethodAttributeKey(), getter.getMethod(request)); + } } @Override @@ -41,6 +62,10 @@ public final void onEnd( REQUEST request, @Nullable RESPONSE response, @Nullable Throwable error) { - // No response attributes + if (SemconvStability.emitStableRpcSemconv()) { + if (error != null) { + internalSet(attributes, ERROR_TYPE, error.getClass().getName()); + } + } } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcMetricsAdvice.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcMetricsAdvice.java index af317d876d62..3f9d361fa3e0 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcMetricsAdvice.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcMetricsAdvice.java @@ -5,67 +5,111 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.rpc; -import static java.util.Arrays.asList; - import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.incubator.metrics.ExtendedDoubleHistogramBuilder; import io.opentelemetry.api.incubator.metrics.ExtendedLongHistogramBuilder; import io.opentelemetry.api.metrics.DoubleHistogramBuilder; import io.opentelemetry.api.metrics.LongHistogramBuilder; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; import io.opentelemetry.semconv.NetworkAttributes; import io.opentelemetry.semconv.ServerAttributes; +import java.util.ArrayList; import java.util.List; final class RpcMetricsAdvice { + // Stable semconv key + private static final AttributeKey RPC_RESPONSE_STATUS_CODE = + AttributeKey.stringKey("rpc.response.status_code"); + // copied from RpcIncubatingAttributes + @Deprecated // use RPC_RESPONSE_STATUS_CODE for stable semconv private static final AttributeKey RPC_GRPC_STATUS_CODE = AttributeKey.longKey("rpc.grpc.status_code"); - private static final List> RPC_METRICS_ATTRIBUTE_KEYS = - asList( - RpcCommonAttributesExtractor.RPC_SYSTEM, - RpcCommonAttributesExtractor.RPC_SERVICE, - RpcCommonAttributesExtractor.RPC_METHOD, - RPC_GRPC_STATUS_CODE, - NetworkAttributes.NETWORK_TYPE, - NetworkAttributes.NETWORK_TRANSPORT, - ServerAttributes.SERVER_ADDRESS, - ServerAttributes.SERVER_PORT); - - static void applyClientDurationAdvice(DoubleHistogramBuilder builder) { + + private static final List> RPC_METRICS_DEPRECATED_ATTRIBUTE_KEYS = + buildAttributeKeysList(false); + private static final List> RPC_METRICS_STABLE_ATTRIBUTE_KEYS = + buildAttributeKeysList(true); + + @SuppressWarnings("deprecation") // until old rpc semconv are dropped + private static List> buildAttributeKeysList(boolean stable) { + List> keys = new ArrayList<>(); + + // Add stable or old RPC system key + if (stable) { + keys.add(RpcCommonAttributesExtractor.RPC_SYSTEM_NAME); + } else { + keys.add(RpcCommonAttributesExtractor.RPC_SYSTEM); + } + + // Add RPC service (old only) + if (!stable) { + keys.add(RpcCommonAttributesExtractor.RPC_SERVICE); + } + + keys.add(RpcCommonAttributesExtractor.RPC_METHOD); + + // Add status code key + if (SemconvStability.emitStableRpcSemconv()) { + keys.add(RPC_RESPONSE_STATUS_CODE); + } else { + keys.add(RPC_GRPC_STATUS_CODE); + } + + // Network type only for old semconv + if (!stable) { + keys.add(NetworkAttributes.NETWORK_TYPE); + } + + // Common attributes + keys.add(NetworkAttributes.NETWORK_TRANSPORT); + keys.add(ServerAttributes.SERVER_ADDRESS); + keys.add(ServerAttributes.SERVER_PORT); + + return keys; + } + + private static List> getAttributeKeys(boolean stable) { + return stable ? RPC_METRICS_STABLE_ATTRIBUTE_KEYS : RPC_METRICS_DEPRECATED_ATTRIBUTE_KEYS; + } + + static void applyClientDurationAdvice(DoubleHistogramBuilder builder, boolean stable) { if (!(builder instanceof ExtendedDoubleHistogramBuilder)) { return; } // the list of recommended metrics attributes is from // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md - ((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_ATTRIBUTE_KEYS); + ((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(getAttributeKeys(stable)); } - static void applyServerDurationAdvice(DoubleHistogramBuilder builder) { + static void applyServerDurationAdvice(DoubleHistogramBuilder builder, boolean stable) { if (!(builder instanceof ExtendedDoubleHistogramBuilder)) { return; } // the list of recommended metrics attributes is from // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md - ((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_ATTRIBUTE_KEYS); + ((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(getAttributeKeys(stable)); } - static void applyClientRequestSizeAdvice(LongHistogramBuilder builder) { + static void applyDeprecatedClientRequestSizeAdvice(LongHistogramBuilder builder) { if (!(builder instanceof ExtendedLongHistogramBuilder)) { return; } // the list of recommended metrics attributes is from // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md - ((ExtendedLongHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_ATTRIBUTE_KEYS); + ((ExtendedLongHistogramBuilder) builder) + .setAttributesAdvice(RPC_METRICS_DEPRECATED_ATTRIBUTE_KEYS); } - static void applyServerRequestSizeAdvice(LongHistogramBuilder builder) { + static void applyDeprecatedServerRequestSizeAdvice(LongHistogramBuilder builder) { if (!(builder instanceof ExtendedLongHistogramBuilder)) { return; } // the list of recommended metrics attributes is from // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md - ((ExtendedLongHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_ATTRIBUTE_KEYS); + ((ExtendedLongHistogramBuilder) builder) + .setAttributesAdvice(RPC_METRICS_DEPRECATED_ATTRIBUTE_KEYS); } private RpcMetricsAdvice() {} diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java index 2ab6755853d2..6c0cb48d6ce2 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java @@ -19,8 +19,10 @@ import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; +import javax.annotation.Nullable; /** * {@link OperationListener} which keeps track of RPC_SERVER_REQUEST_METRICS_STATE = ContextKey.named("rpc-server-request-metrics-state"); private static final Logger logger = Logger.getLogger(RpcServerMetrics.class.getName()); - private final DoubleHistogram serverDurationHistogram; - private final LongHistogram serverRequestSize; - private final LongHistogram serverResponseSize; + @Nullable private final DoubleHistogram oldServerDurationHistogram; + @Nullable private final DoubleHistogram stableServerDurationHistogram; + private final LongHistogram oldServerRequestSize; + private final LongHistogram oldServerResponseSize; private RpcServerMetrics(Meter meter) { - DoubleHistogramBuilder durationBuilder = - meter - .histogramBuilder("rpc.server.duration") - .setDescription("The duration of an inbound RPC invocation.") - .setUnit("ms"); - RpcMetricsAdvice.applyServerDurationAdvice(durationBuilder); - serverDurationHistogram = durationBuilder.build(); + // Old metric (milliseconds) + if (SemconvStability.emitOldRpcSemconv()) { + DoubleHistogramBuilder oldDurationBuilder = + meter + .histogramBuilder("rpc.server.duration") + .setDescription("The duration of an inbound RPC invocation.") + .setUnit("ms"); + RpcMetricsAdvice.applyServerDurationAdvice(oldDurationBuilder, false); + oldServerDurationHistogram = oldDurationBuilder.build(); + } else { + oldServerDurationHistogram = null; + } + + // Stable metric (seconds) + if (SemconvStability.emitStableRpcSemconv()) { + DoubleHistogramBuilder stableDurationBuilder = + meter + .histogramBuilder("rpc.server.call.duration") + .setDescription("The duration of an inbound RPC invocation.") + .setUnit("s"); + RpcMetricsAdvice.applyServerDurationAdvice(stableDurationBuilder, true); + stableServerDurationHistogram = stableDurationBuilder.build(); + } else { + stableServerDurationHistogram = null; + } LongHistogramBuilder requestSizeBuilder = meter @@ -55,8 +77,8 @@ private RpcServerMetrics(Meter meter) { .setUnit("By") .setDescription("Measures the size of RPC request messages (uncompressed).") .ofLongs(); - RpcMetricsAdvice.applyServerRequestSizeAdvice(requestSizeBuilder); - serverRequestSize = requestSizeBuilder.build(); + RpcMetricsAdvice.applyDeprecatedServerRequestSizeAdvice(requestSizeBuilder); + oldServerRequestSize = requestSizeBuilder.build(); LongHistogramBuilder responseSizeBuilder = meter @@ -64,8 +86,8 @@ private RpcServerMetrics(Meter meter) { .setUnit("By") .setDescription("Measures the size of RPC response messages (uncompressed).") .ofLongs(); - RpcMetricsAdvice.applyServerRequestSizeAdvice(responseSizeBuilder); - serverResponseSize = responseSizeBuilder.build(); + RpcMetricsAdvice.applyDeprecatedServerRequestSizeAdvice(responseSizeBuilder); + oldServerResponseSize = responseSizeBuilder.build(); } /** @@ -95,17 +117,37 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { return; } Attributes attributes = state.startAttributes().toBuilder().putAll(endAttributes).build(); - serverDurationHistogram.record( - (endNanos - state.startTimeNanos()) / NANOS_PER_MS, attributes, context); + double durationNanos = (endNanos - state.startTimeNanos()); + + // Record to old histogram (milliseconds) + if (oldServerDurationHistogram != null) { + oldServerDurationHistogram.record( + durationNanos / NANOS_PER_MS, + SemconvStability.getOldRpcMetricAttributes(attributes), + context); + } - Long rpcServerRequestBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE); - if (rpcServerRequestBodySize != null) { - serverRequestSize.record(rpcServerRequestBodySize, attributes, context); + // Record to stable histogram (seconds) + if (stableServerDurationHistogram != null) { + stableServerDurationHistogram.record(durationNanos / NANOS_PER_S, attributes, context); } - Long rpcServerResponseBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_RESPONSE_SIZE); - if (rpcServerResponseBodySize != null) { - serverResponseSize.record(rpcServerResponseBodySize, attributes, context); + if (SemconvStability.emitOldRpcSemconv()) { + Long rpcServerRequestBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE); + if (rpcServerRequestBodySize != null) { + oldServerRequestSize.record( + rpcServerRequestBodySize, + SemconvStability.getOldRpcMetricAttributes(attributes), + context); + } + + Long rpcServerResponseBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_RESPONSE_SIZE); + if (rpcServerResponseBodySize != null) { + oldServerResponseSize.record( + rpcServerResponseBodySize, + SemconvStability.getOldRpcMetricAttributes(attributes), + context); + } } } diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java index 058bfb3485b3..b50eeae544f2 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java @@ -8,12 +8,15 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static org.assertj.core.api.Assertions.entry; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import io.opentelemetry.semconv.incubating.RpcIncubatingAttributes; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; @@ -49,6 +52,20 @@ void client() { testExtractor(RpcClientAttributesExtractor.create(TestGetter.INSTANCE)); } + // Stable semconv keys + private static final AttributeKey RPC_SYSTEM_NAME = + AttributeKey.stringKey("rpc.system.name"); + + // Old semconv keys (from RpcIncubatingAttributes) + private static final AttributeKey RPC_SYSTEM = + io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM; + + private static final AttributeKey RPC_SERVICE = + io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SERVICE; + + private static final AttributeKey RPC_METHOD = + io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_METHOD; + private static void testExtractor(AttributesExtractor, Void> extractor) { Map request = new HashMap<>(); request.put("service", "my.Service"); @@ -58,16 +75,28 @@ private static void testExtractor(AttributesExtractor, Void> AttributesBuilder attributes = Attributes.builder(); extractor.onStart(attributes, context, request); - assertThat(attributes.build()) - .containsOnly( - entry(RpcIncubatingAttributes.RPC_SYSTEM, "test"), - entry(RpcIncubatingAttributes.RPC_SERVICE, "my.Service"), - entry(RpcIncubatingAttributes.RPC_METHOD, "Method")); + + // Build expected entries list based on semconv mode + List, ?>> expectedEntries = new ArrayList<>(); + + if (SemconvStability.emitStableRpcSemconv()) { + expectedEntries.add(entry(RPC_SYSTEM_NAME, "test")); + expectedEntries.add(entry(RPC_METHOD, "my.Service/Method")); + } + + if (SemconvStability.emitOldRpcSemconv()) { + expectedEntries.add(entry(RPC_SYSTEM, "test")); + expectedEntries.add(entry(RPC_SERVICE, "my.Service")); + expectedEntries.add(entry(SemconvStability.getOldRpcMethodAttributeKey(), "Method")); + } + + // safe conversion for test assertions + @SuppressWarnings({"unchecked", "rawtypes"}) + Map.Entry, ?>[] expectedArray = + (Map.Entry, ?>[]) expectedEntries.toArray(new Map.Entry[0]); + assertThat(attributes.build()).containsOnly(expectedArray); + extractor.onEnd(attributes, context, request, null, null); - assertThat(attributes.build()) - .containsOnly( - entry(RpcIncubatingAttributes.RPC_SYSTEM, "test"), - entry(RpcIncubatingAttributes.RPC_SERVICE, "my.Service"), - entry(RpcIncubatingAttributes.RPC_METHOD, "Method")); + assertThat(attributes.build()).containsOnly(expectedArray); } } diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetricsTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetricsTest.java index 8a0dbc5a32c7..d12fdb3f4235 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetricsTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetricsTest.java @@ -5,21 +5,32 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.rpc; +import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcCommonAttributesExtractor.RPC_SYSTEM_NAME; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_METHOD; +import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SERVICE; +import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; 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.instrumentation.api.internal.SemconvStability; import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; import io.opentelemetry.semconv.NetworkAttributes; import io.opentelemetry.semconv.ServerAttributes; -import io.opentelemetry.semconv.incubating.RpcIncubatingAttributes; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; @@ -35,19 +46,10 @@ void collectsMetrics() { OperationListener listener = RpcClientMetrics.get().create(meterProvider.get("test")); Attributes requestAttributes1 = - Attributes.builder() - .put(RpcIncubatingAttributes.RPC_SYSTEM, "grpc") - .put(RpcIncubatingAttributes.RPC_SERVICE, "myservice.EchoService") - .put(RpcIncubatingAttributes.RPC_METHOD, "exampleMethod") - .put(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE, 10) - .build(); + buildRequestAttributes("grpc", "myservice.EchoService", "exampleMethod", true); Attributes requestAttributes2 = - Attributes.builder() - .put(RpcIncubatingAttributes.RPC_SYSTEM, "grpc") - .put(RpcIncubatingAttributes.RPC_SERVICE, "myservice.EchoService") - .put(RpcIncubatingAttributes.RPC_METHOD, "exampleMethod") - .build(); + buildRequestAttributes("grpc", "myservice.EchoService", "exampleMethod", false); Attributes responseAttributes1 = Attributes.builder() @@ -84,119 +86,236 @@ void collectsMetrics() { listener.onEnd(context1, responseAttributes1, nanos(250)); - assertThat(metricReader.collectAllMetrics()) - .satisfiesExactlyInAnyOrder( - metric -> - assertThat(metric) - .hasName("rpc.client.response.size") - .hasUnit("By") - .hasDescription("Measures the size of RPC response messages (uncompressed).") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying( - point -> - point - .hasSum(20 /* bytes */) - .hasAttributesSatisfyingExactly( - equalTo(RpcIncubatingAttributes.RPC_SYSTEM, "grpc"), - equalTo( - RpcIncubatingAttributes.RPC_SERVICE, - "myservice.EchoService"), - equalTo( - RpcIncubatingAttributes.RPC_METHOD, - "exampleMethod"), - equalTo(ServerAttributes.SERVER_ADDRESS, "example.com"), - equalTo(ServerAttributes.SERVER_PORT, 8080), - equalTo(NetworkAttributes.NETWORK_TRANSPORT, "tcp"), - equalTo(NetworkAttributes.NETWORK_TYPE, "ipv4")) - .hasExemplarsSatisfying( - exemplar -> - exemplar - .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") - .hasSpanId("090a0b0c0d0e0f00")))), - metric -> - assertThat(metric) - .hasName("rpc.client.request.size") - .hasUnit("By") - .hasDescription("Measures the size of RPC request messages (uncompressed).") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying( - point -> - point - .hasSum(10 /* bytes */) - .hasAttributesSatisfyingExactly( - equalTo(RpcIncubatingAttributes.RPC_SYSTEM, "grpc"), - equalTo( - RpcIncubatingAttributes.RPC_SERVICE, - "myservice.EchoService"), - equalTo( - RpcIncubatingAttributes.RPC_METHOD, - "exampleMethod"), - equalTo(ServerAttributes.SERVER_ADDRESS, "example.com"), - equalTo(ServerAttributes.SERVER_PORT, 8080), - equalTo(NetworkAttributes.NETWORK_TRANSPORT, "tcp"), - equalTo(NetworkAttributes.NETWORK_TYPE, "ipv4")) - .hasExemplarsSatisfying( - exemplar -> - exemplar - .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") - .hasSpanId("090a0b0c0d0e0f00")))), - metric -> - assertThat(metric) - .hasName("rpc.client.duration") - .hasUnit("ms") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying( - point -> - point - .hasSum(150 /* millis */) - .hasAttributesSatisfyingExactly( - equalTo(RpcIncubatingAttributes.RPC_SYSTEM, "grpc"), - equalTo( - RpcIncubatingAttributes.RPC_SERVICE, - "myservice.EchoService"), - equalTo( - RpcIncubatingAttributes.RPC_METHOD, - "exampleMethod"), - equalTo(ServerAttributes.SERVER_ADDRESS, "example.com"), - equalTo(ServerAttributes.SERVER_PORT, 8080), - equalTo(NetworkAttributes.NETWORK_TRANSPORT, "tcp"), - equalTo(NetworkAttributes.NETWORK_TYPE, "ipv4")) - .hasExemplarsSatisfying( - exemplar -> - exemplar - .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") - .hasSpanId("090a0b0c0d0e0f00"))))); + // Calculate expected metric count: + // - dup mode: old duration + stable duration + request.size + response.size = 4 + // - old-only mode: old duration + request.size + response.size = 3 + // - stable-only mode: stable duration only = 1 + int expectedMetricCount = 1; // At minimum, we always have one duration metric + if (SemconvStability.emitOldRpcSemconv() && SemconvStability.emitStableRpcSemconv()) { + expectedMetricCount = 4; // Both durations + both size metrics + } else if (SemconvStability.emitOldRpcSemconv()) { + expectedMetricCount = 3; // Old duration + both size metrics + } + + // Collect metrics once (delta reader consumes on each call) + Collection metrics1 = metricReader.collectAllMetrics(); + assertThat(metrics1).hasSize(expectedMetricCount); + + // Build expected attributes for OLD metrics (size + old duration) - alphabetically sorted + List oldMetricAttributes1 = new ArrayList<>(); + if (SemconvStability.emitOldRpcSemconv()) { + oldMetricAttributes1.add(equalTo(NetworkAttributes.NETWORK_TRANSPORT, "tcp")); + oldMetricAttributes1.add(equalTo(NetworkAttributes.NETWORK_TYPE, "ipv4")); + oldMetricAttributes1.add(equalTo(RPC_METHOD, "exampleMethod")); + oldMetricAttributes1.add(equalTo(RPC_SERVICE, "myservice.EchoService")); + oldMetricAttributes1.add(equalTo(RPC_SYSTEM, "grpc")); + oldMetricAttributes1.add(equalTo(ServerAttributes.SERVER_ADDRESS, "example.com")); + oldMetricAttributes1.add(equalTo(ServerAttributes.SERVER_PORT, 8080)); + } + + // Size metrics are only recorded in old semconv mode + if (SemconvStability.emitOldRpcSemconv()) { + assertThat(metrics1) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("rpc.client.response.size") + .hasUnit("By") + .hasDescription("Measures the size of RPC response messages (uncompressed).") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(20 /* bytes */) + .hasAttributesSatisfyingExactly( + oldMetricAttributes1.toArray( + new AttributeAssertion[0])) + .hasExemplarsSatisfying( + exemplar -> + exemplar + .hasTraceId( + "ff01020304050600ff0a0b0c0d0e0f00") + .hasSpanId("090a0b0c0d0e0f00"))))) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("rpc.client.request.size") + .hasUnit("By") + .hasDescription("Measures the size of RPC request messages (uncompressed).") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(10 /* bytes */) + .hasAttributesSatisfyingExactly( + oldMetricAttributes1.toArray( + new AttributeAssertion[0])) + .hasExemplarsSatisfying( + exemplar -> + exemplar + .hasTraceId( + "ff01020304050600ff0a0b0c0d0e0f00") + .hasSpanId("090a0b0c0d0e0f00"))))); + } + + // Assert stable duration metric if emitting stable semconv + if (SemconvStability.emitStableRpcSemconv()) { + // Build expected attributes for STABLE metrics - alphabetically sorted + List stableMetricAttributes1 = new ArrayList<>(); + stableMetricAttributes1.add(equalTo(NetworkAttributes.NETWORK_TRANSPORT, "tcp")); + stableMetricAttributes1.add(equalTo(RPC_METHOD, "myservice.EchoService/exampleMethod")); + stableMetricAttributes1.add( + equalTo( + AttributeKey.stringKey("rpc.system.name"), + SemconvStability.stableRpcSystemName("grpc"))); + stableMetricAttributes1.add(equalTo(ServerAttributes.SERVER_ADDRESS, "example.com")); + stableMetricAttributes1.add(equalTo(ServerAttributes.SERVER_PORT, 8080)); + + assertThat(metrics1) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("rpc.client.call.duration") + .hasUnit("s") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(0.15) + .hasAttributesSatisfyingExactly( + stableMetricAttributes1.toArray( + new AttributeAssertion[0])) + .hasExemplarsSatisfying( + exemplar -> + exemplar + .hasTraceId( + "ff01020304050600ff0a0b0c0d0e0f00") + .hasSpanId("090a0b0c0d0e0f00"))))); + } + + // Assert old duration metric if emitting old semconv + if (SemconvStability.emitOldRpcSemconv()) { + assertThat(metrics1) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("rpc.client.duration") + .hasUnit("ms") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(150.0) + .hasAttributesSatisfyingExactly( + oldMetricAttributes1.toArray( + new AttributeAssertion[0])) + .hasExemplarsSatisfying( + exemplar -> + exemplar + .hasTraceId( + "ff01020304050600ff0a0b0c0d0e0f00") + .hasSpanId("090a0b0c0d0e0f00"))))); + } listener.onEnd(context2, responseAttributes2, nanos(300)); - assertThat(metricReader.collectAllMetrics()) - .satisfiesExactlyInAnyOrder( - metric -> - assertThat(metric) - .hasName("rpc.client.duration") - .hasUnit("ms") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying( - point -> - point - .hasSum(150 /* millis */) - .hasAttributesSatisfyingExactly( - equalTo(RpcIncubatingAttributes.RPC_SYSTEM, "grpc"), - equalTo( - RpcIncubatingAttributes.RPC_SERVICE, - "myservice.EchoService"), - equalTo( - RpcIncubatingAttributes.RPC_METHOD, - "exampleMethod"), - equalTo(ServerAttributes.SERVER_PORT, 8080), - equalTo(NetworkAttributes.NETWORK_TRANSPORT, "tcp"))))); + // Collect metrics once (delta reader consumes on each call) + // Note: metrics2 doesn't include size metrics since context2 has no size attributes + int expectedMetricCount2 = + SemconvStability.emitOldRpcSemconv() && SemconvStability.emitStableRpcSemconv() ? 2 : 1; + Collection metrics2 = metricReader.collectAllMetrics(); + assertThat(metrics2).hasSize(expectedMetricCount2); + + // Build expected attributes for OLD metrics (no server.address or network.type for context2) - + // alphabetically sorted + List oldMetricAttributes2 = new ArrayList<>(); + if (SemconvStability.emitOldRpcSemconv()) { + oldMetricAttributes2.add(equalTo(NetworkAttributes.NETWORK_TRANSPORT, "tcp")); + oldMetricAttributes2.add(equalTo(RPC_METHOD, "exampleMethod")); + oldMetricAttributes2.add(equalTo(RPC_SERVICE, "myservice.EchoService")); + oldMetricAttributes2.add(equalTo(RPC_SYSTEM, "grpc")); + oldMetricAttributes2.add(equalTo(ServerAttributes.SERVER_PORT, 8080)); + } + + // Build expected attributes for STABLE metrics (no server.address for context2) - + // alphabetically sorted + List stableMetricAttributes2 = new ArrayList<>(); + if (SemconvStability.emitStableRpcSemconv()) { + stableMetricAttributes2.add(equalTo(NetworkAttributes.NETWORK_TRANSPORT, "tcp")); + stableMetricAttributes2.add(equalTo(RPC_METHOD, "myservice.EchoService/exampleMethod")); + stableMetricAttributes2.add( + equalTo( + AttributeKey.stringKey("rpc.system.name"), + SemconvStability.stableRpcSystemName("grpc"))); + stableMetricAttributes2.add(equalTo(ServerAttributes.SERVER_PORT, 8080)); + } + + // Assert stable duration metric if emitting stable semconv + if (SemconvStability.emitStableRpcSemconv()) { + assertThat(metrics2) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("rpc.client.call.duration") + .hasUnit("s") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(0.15) + .hasAttributesSatisfyingExactly( + stableMetricAttributes2.toArray( + new AttributeAssertion[0]))))); + } + + // Assert old duration metric if emitting old semconv + if (SemconvStability.emitOldRpcSemconv()) { + assertThat(metrics2) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("rpc.client.duration") + .hasUnit("ms") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(150.0) + .hasAttributesSatisfyingExactly( + oldMetricAttributes2.toArray( + new AttributeAssertion[0]))))); + } } private static long nanos(int millis) { return TimeUnit.MILLISECONDS.toNanos(millis); } + + private static Attributes buildRequestAttributes( + String system, String service, String method, boolean withSize) { + AttributesBuilder builder = Attributes.builder(); + + if (SemconvStability.emitOldRpcSemconv()) { + builder.put(RPC_SYSTEM, system); + builder.put(RPC_SERVICE, service); + builder.put(SemconvStability.getOldRpcMethodAttributeKey(), method); + } + + if (SemconvStability.emitStableRpcSemconv()) { + builder.put(RPC_SYSTEM_NAME, SemconvStability.stableRpcSystemName(system)); + builder.put(RPC_METHOD, service + "/" + method); + } + + if (withSize) { + builder.put(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE, 10); + } + + return builder.build(); + } } diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetricsTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetricsTest.java index d7063861f031..ee178370b9cd 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetricsTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetricsTest.java @@ -5,21 +5,33 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.rpc; +import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcCommonAttributesExtractor.RPC_SYSTEM_NAME; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_METHOD; +import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SERVICE; +import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; 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.instrumentation.api.internal.SemconvStability; import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; import io.opentelemetry.semconv.NetworkAttributes; import io.opentelemetry.semconv.ServerAttributes; import io.opentelemetry.semconv.incubating.RpcIncubatingAttributes; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; @@ -35,19 +47,10 @@ void collectsMetrics() { OperationListener listener = RpcServerMetrics.get().create(meterProvider.get("test")); Attributes requestAttributes1 = - Attributes.builder() - .put(RpcIncubatingAttributes.RPC_SYSTEM, "grpc") - .put(RpcIncubatingAttributes.RPC_SERVICE, "myservice.EchoService") - .put(RpcIncubatingAttributes.RPC_METHOD, "exampleMethod") - .put(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE, 10) - .build(); + buildRequestAttributes("grpc", "myservice.EchoService", "exampleMethod", true); Attributes requestAttributes2 = - Attributes.builder() - .put(RpcIncubatingAttributes.RPC_SYSTEM, "grpc") - .put(RpcIncubatingAttributes.RPC_SERVICE, "myservice.EchoService") - .put(RpcIncubatingAttributes.RPC_METHOD, "exampleMethod") - .build(); + buildRequestAttributes("grpc", "myservice.EchoService", "exampleMethod", false); Attributes responseAttributes1 = Attributes.builder() @@ -86,119 +89,238 @@ void collectsMetrics() { listener.onEnd(context1, responseAttributes1, nanos(250)); - assertThat(metricReader.collectAllMetrics()) - .satisfiesExactlyInAnyOrder( - metric -> - assertThat(metric) - .hasName("rpc.server.duration") - .hasUnit("ms") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying( - point -> - point - .hasSum(150 /* millis */) - .hasAttributesSatisfyingExactly( - equalTo(RpcIncubatingAttributes.RPC_SYSTEM, "grpc"), - equalTo( - RpcIncubatingAttributes.RPC_SERVICE, - "myservice.EchoService"), - equalTo( - RpcIncubatingAttributes.RPC_METHOD, - "exampleMethod"), - equalTo(ServerAttributes.SERVER_ADDRESS, "example.com"), - equalTo(ServerAttributes.SERVER_PORT, 8080), - equalTo(NetworkAttributes.NETWORK_TRANSPORT, "tcp"), - equalTo(NetworkAttributes.NETWORK_TYPE, "ipv4")) - .hasExemplarsSatisfying( - exemplar -> - exemplar - .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") - .hasSpanId("090a0b0c0d0e0f00")))), - metric -> - assertThat(metric) - .hasName("rpc.server.response.size") - .hasUnit("By") - .hasDescription("Measures the size of RPC response messages (uncompressed).") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying( - point -> - point - .hasSum(20 /* bytes */) - .hasAttributesSatisfyingExactly( - equalTo(RpcIncubatingAttributes.RPC_SYSTEM, "grpc"), - equalTo( - RpcIncubatingAttributes.RPC_SERVICE, - "myservice.EchoService"), - equalTo( - RpcIncubatingAttributes.RPC_METHOD, - "exampleMethod"), - equalTo(ServerAttributes.SERVER_ADDRESS, "example.com"), - equalTo(ServerAttributes.SERVER_PORT, 8080), - equalTo(NetworkAttributes.NETWORK_TRANSPORT, "tcp"), - equalTo(NetworkAttributes.NETWORK_TYPE, "ipv4")) - .hasExemplarsSatisfying( - exemplar -> - exemplar - .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") - .hasSpanId("090a0b0c0d0e0f00")))), - metric -> - assertThat(metric) - .hasName("rpc.server.request.size") - .hasUnit("By") - .hasDescription("Measures the size of RPC request messages (uncompressed).") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying( - point -> - point - .hasSum(10 /* bytes */) - .hasAttributesSatisfyingExactly( - equalTo(RpcIncubatingAttributes.RPC_SYSTEM, "grpc"), - equalTo( - RpcIncubatingAttributes.RPC_SERVICE, - "myservice.EchoService"), - equalTo( - RpcIncubatingAttributes.RPC_METHOD, - "exampleMethod"), - equalTo(ServerAttributes.SERVER_ADDRESS, "example.com"), - equalTo(ServerAttributes.SERVER_PORT, 8080), - equalTo(NetworkAttributes.NETWORK_TRANSPORT, "tcp"), - equalTo(NetworkAttributes.NETWORK_TYPE, "ipv4")) - .hasExemplarsSatisfying( - exemplar -> - exemplar - .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") - .hasSpanId("090a0b0c0d0e0f00"))))); + // Calculate expected metric count: + // - dup mode: old duration + stable duration + request.size + response.size = 4 + // - old-only mode: old duration + request.size + response.size = 3 + // - stable-only mode: stable duration only = 1 + int expectedMetricCount = 1; // At minimum, we always have one duration metric + if (SemconvStability.emitOldRpcSemconv() && SemconvStability.emitStableRpcSemconv()) { + expectedMetricCount = 4; // Both durations + both size metrics + } else if (SemconvStability.emitOldRpcSemconv()) { + expectedMetricCount = 3; // Old duration + both size metrics + } + + // Collect metrics once (delta reader consumes on each call) + Collection metrics1 = metricReader.collectAllMetrics(); + assertThat(metrics1).hasSize(expectedMetricCount); + + // Build expected attributes for OLD metrics (size + old duration) - alphabetically sorted + List oldMetricAttributes1 = new ArrayList<>(); + if (SemconvStability.emitOldRpcSemconv()) { + oldMetricAttributes1.add(equalTo(NetworkAttributes.NETWORK_TRANSPORT, "tcp")); + oldMetricAttributes1.add(equalTo(NetworkAttributes.NETWORK_TYPE, "ipv4")); + oldMetricAttributes1.add(equalTo(RpcIncubatingAttributes.RPC_METHOD, "exampleMethod")); + oldMetricAttributes1.add(equalTo(RPC_SERVICE, "myservice.EchoService")); + oldMetricAttributes1.add(equalTo(RPC_SYSTEM, "grpc")); + oldMetricAttributes1.add(equalTo(ServerAttributes.SERVER_ADDRESS, "example.com")); + oldMetricAttributes1.add(equalTo(ServerAttributes.SERVER_PORT, 8080)); + } + + // Size metrics are only recorded in old semconv mode + if (SemconvStability.emitOldRpcSemconv()) { + assertThat(metrics1) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("rpc.server.response.size") + .hasUnit("By") + .hasDescription("Measures the size of RPC response messages (uncompressed).") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(20 /* bytes */) + .hasAttributesSatisfyingExactly( + oldMetricAttributes1.toArray( + new AttributeAssertion[0])) + .hasExemplarsSatisfying( + exemplar -> + exemplar + .hasTraceId( + "ff01020304050600ff0a0b0c0d0e0f00") + .hasSpanId("090a0b0c0d0e0f00"))))) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("rpc.server.request.size") + .hasUnit("By") + .hasDescription("Measures the size of RPC request messages (uncompressed).") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(10 /* bytes */) + .hasAttributesSatisfyingExactly( + oldMetricAttributes1.toArray( + new AttributeAssertion[0])) + .hasExemplarsSatisfying( + exemplar -> + exemplar + .hasTraceId( + "ff01020304050600ff0a0b0c0d0e0f00") + .hasSpanId("090a0b0c0d0e0f00"))))); + } + + // Assert stable duration metric if emitting stable semconv + if (SemconvStability.emitStableRpcSemconv()) { + // Build expected attributes for STABLE metrics - alphabetically sorted + List stableMetricAttributes1 = new ArrayList<>(); + stableMetricAttributes1.add(equalTo(NetworkAttributes.NETWORK_TRANSPORT, "tcp")); + stableMetricAttributes1.add( + equalTo(RpcIncubatingAttributes.RPC_METHOD, "myservice.EchoService/exampleMethod")); + stableMetricAttributes1.add( + equalTo( + AttributeKey.stringKey("rpc.system.name"), + SemconvStability.stableRpcSystemName("grpc"))); + stableMetricAttributes1.add(equalTo(ServerAttributes.SERVER_ADDRESS, "example.com")); + stableMetricAttributes1.add(equalTo(ServerAttributes.SERVER_PORT, 8080)); + + assertThat(metrics1) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("rpc.server.call.duration") + .hasUnit("s") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(0.15) + .hasAttributesSatisfyingExactly( + stableMetricAttributes1.toArray( + new AttributeAssertion[0])) + .hasExemplarsSatisfying( + exemplar -> + exemplar + .hasTraceId( + "ff01020304050600ff0a0b0c0d0e0f00") + .hasSpanId("090a0b0c0d0e0f00"))))); + } + + // Assert old duration metric if emitting old semconv + if (SemconvStability.emitOldRpcSemconv()) { + assertThat(metrics1) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("rpc.server.duration") + .hasUnit("ms") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(150.0) + .hasAttributesSatisfyingExactly( + oldMetricAttributes1.toArray( + new AttributeAssertion[0])) + .hasExemplarsSatisfying( + exemplar -> + exemplar + .hasTraceId( + "ff01020304050600ff0a0b0c0d0e0f00") + .hasSpanId("090a0b0c0d0e0f00"))))); + } listener.onEnd(context2, responseAttributes2, nanos(300)); - assertThat(metricReader.collectAllMetrics()) - .satisfiesExactlyInAnyOrder( - metric -> - assertThat(metric) - .hasName("rpc.server.duration") - .hasUnit("ms") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying( - point -> - point - .hasSum(150 /* millis */) - .hasAttributesSatisfyingExactly( - equalTo(RpcIncubatingAttributes.RPC_SYSTEM, "grpc"), - equalTo( - RpcIncubatingAttributes.RPC_SERVICE, - "myservice.EchoService"), - equalTo( - RpcIncubatingAttributes.RPC_METHOD, - "exampleMethod"), - equalTo(ServerAttributes.SERVER_PORT, 8080), - equalTo(NetworkAttributes.NETWORK_TRANSPORT, "tcp"))))); + // Collect metrics once (delta reader consumes on each call) + // Note: metrics2 doesn't include size metrics since context2 has no size attributes + int expectedMetricCount2 = + SemconvStability.emitOldRpcSemconv() && SemconvStability.emitStableRpcSemconv() ? 2 : 1; + Collection metrics2 = metricReader.collectAllMetrics(); + assertThat(metrics2).hasSize(expectedMetricCount2); + + // Build expected attributes for OLD metrics (no server.address or network.type for context2) - + // alphabetically sorted + List oldMetricAttributes2 = new ArrayList<>(); + if (SemconvStability.emitOldRpcSemconv()) { + oldMetricAttributes2.add(equalTo(NetworkAttributes.NETWORK_TRANSPORT, "tcp")); + oldMetricAttributes2.add(equalTo(RpcIncubatingAttributes.RPC_METHOD, "exampleMethod")); + oldMetricAttributes2.add(equalTo(RPC_SERVICE, "myservice.EchoService")); + oldMetricAttributes2.add(equalTo(RPC_SYSTEM, "grpc")); + oldMetricAttributes2.add(equalTo(ServerAttributes.SERVER_PORT, 8080)); + } + + // Build expected attributes for STABLE metrics (no server.address for context2) - + // alphabetically sorted + List stableMetricAttributes2 = new ArrayList<>(); + if (SemconvStability.emitStableRpcSemconv()) { + stableMetricAttributes2.add(equalTo(NetworkAttributes.NETWORK_TRANSPORT, "tcp")); + stableMetricAttributes2.add( + equalTo(RpcIncubatingAttributes.RPC_METHOD, "myservice.EchoService/exampleMethod")); + stableMetricAttributes2.add( + equalTo( + AttributeKey.stringKey("rpc.system.name"), + SemconvStability.stableRpcSystemName("grpc"))); + stableMetricAttributes2.add(equalTo(ServerAttributes.SERVER_PORT, 8080)); + } + + // Assert stable duration metric if emitting stable semconv + if (SemconvStability.emitStableRpcSemconv()) { + assertThat(metrics2) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("rpc.server.call.duration") + .hasUnit("s") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(0.15) + .hasAttributesSatisfyingExactly( + stableMetricAttributes2.toArray( + new AttributeAssertion[0]))))); + } + + // Assert old duration metric if emitting old semconv + if (SemconvStability.emitOldRpcSemconv()) { + assertThat(metrics2) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("rpc.server.duration") + .hasUnit("ms") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(150.0) + .hasAttributesSatisfyingExactly( + oldMetricAttributes2.toArray( + new AttributeAssertion[0]))))); + } } private static long nanos(int millis) { return TimeUnit.MILLISECONDS.toNanos(millis); } + + private static Attributes buildRequestAttributes( + String system, String service, String method, boolean withSize) { + AttributesBuilder builder = Attributes.builder(); + + if (SemconvStability.emitOldRpcSemconv()) { + builder.put(RPC_SYSTEM, system); + builder.put(RPC_SERVICE, service); + builder.put(SemconvStability.getOldRpcMethodAttributeKey(), method); + } + + if (SemconvStability.emitStableRpcSemconv()) { + builder.put(RPC_SYSTEM_NAME, SemconvStability.stableRpcSystemName(system)); + builder.put(RPC_METHOD, service + "/" + method); + } + + if (withSize) { + builder.put(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE, 10); + } + + return builder.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 94963ef35190..c9e15e131245 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 @@ -7,6 +7,8 @@ import static java.util.Arrays.asList; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -18,6 +20,13 @@ */ public final class SemconvStability { + // copied from RpcIncubatingAttributes + private static final AttributeKey RPC_METHOD = AttributeKey.stringKey("rpc.method"); + + // virtual key to avoid clash with stable rpc.method + private static final AttributeKey RPC_METHOD_OLD = + AttributeKey.stringKey("rpc.method.deprecated"); + private static final boolean emitOldDatabaseSemconv; private static final boolean emitStableDatabaseSemconv; @@ -27,6 +36,9 @@ public final class SemconvStability { private static final boolean emitOldServicePeerSemconv; private static final boolean emitStableServicePeerSemconv; + private static final boolean emitOldRpcSemconv; + private static final boolean emitStableRpcSemconv; + static { boolean oldDatabase = true; boolean stableDatabase = false; @@ -37,6 +49,9 @@ public final class SemconvStability { boolean oldServicePeer = true; boolean stableServicePeer = false; + boolean oldRpc = true; + boolean stableRpc = false; + String value = System.getProperty("otel.semconv-stability.opt-in"); if (value == null) { value = System.getenv("OTEL_SEMCONV_STABILITY_OPT_IN"); @@ -73,6 +88,15 @@ public final class SemconvStability { oldServicePeer = true; stableServicePeer = true; } + + if (values.contains("rpc")) { + oldRpc = false; + stableRpc = true; + } + if (values.contains("rpc/dup")) { + oldRpc = true; + stableRpc = true; + } } emitOldDatabaseSemconv = oldDatabase; @@ -83,6 +107,9 @@ public final class SemconvStability { emitOldServicePeerSemconv = oldServicePeer; emitStableServicePeerSemconv = stableServicePeer; + + emitOldRpcSemconv = oldRpc; + emitStableRpcSemconv = stableRpc; } public static boolean emitOldDatabaseSemconv() { @@ -134,5 +161,41 @@ public static boolean isEmitStableCodeSemconv() { return emitStableCodeSemconv; } + public static boolean emitOldRpcSemconv() { + return emitOldRpcSemconv; + } + + public static boolean emitStableRpcSemconv() { + return emitStableRpcSemconv; + } + + private static final Map rpcSystemNameMap = new HashMap<>(); + + static { + rpcSystemNameMap.put("apache_dubbo", "dubbo"); + rpcSystemNameMap.put("connect_rpc", "connectrpc"); + } + + public static String stableRpcSystemName(String oldRpcSystem) { + String rpcSystemName = rpcSystemNameMap.get(oldRpcSystem); + return rpcSystemName != null ? rpcSystemName : oldRpcSystem; + } + + public static AttributeKey getOldRpcMethodAttributeKey() { + if (emitStableRpcSemconv()) { + // to avoid clash when both semconv are emitted + return RPC_METHOD_OLD; + } + return RPC_METHOD; + } + private SemconvStability() {} + + public static Attributes getOldRpcMetricAttributes(Attributes attributes) { + if (emitStableRpcSemconv()) { + // need to copy attributes + return attributes.toBuilder().put(RPC_METHOD, attributes.get(RPC_METHOD_OLD)).build(); + } + return attributes; + } } From e14112086df9e839788e1f4d1c602587334c59cd Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 20 Jan 2026 13:23:25 +0100 Subject: [PATCH 02/15] check for well-known method --- .../semconv/rpc/RpcAttributesGetter.java | 20 +++++--- .../rpc/RpcCommonAttributesExtractor.java | 11 +++- .../semconv/rpc/RpcSpanNameExtractor.java | 5 ++ .../rpc/RpcAttributesExtractorTest.java | 51 ++++++++++++++++--- 4 files changed, 73 insertions(+), 14 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java index f3ddc224c8c7..8f28e031e64e 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java @@ -46,12 +46,18 @@ default Long getResponseSize(REQUEST request) { * method is unavailable */ @Nullable - default String getFullMethod(REQUEST request) { - String service = getService(request); - String method = getMethod(request); - if (service == null || method == null) { - return null; - } - return service + "/" + method; + default String getRpcMethod(REQUEST request) { + return null; + } + + /** + * Returns whether the RPC method is a well-known method. + * + *

This is used to determine whether to set the {@code rpc.method} attribute to the actual + * method name, or to set it to {@code "_OTHER"} and store the actual method name in {@code + * rpc.method_original}. + */ + default boolean isWellKnownMethod(REQUEST request) { + return false; } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java index d844d887e1a3..96f7e0d6f135 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java @@ -29,6 +29,9 @@ abstract class RpcCommonAttributesExtractor // use RPC_SYSTEM_NAME for stable semconv static final AttributeKey RPC_SYSTEM = AttributeKey.stringKey("rpc.system"); + static final AttributeKey RPC_METHOD_ORIGINAL = + AttributeKey.stringKey("rpc.method_original"); + private final RpcAttributesGetter getter; RpcCommonAttributesExtractor(RpcAttributesGetter getter) { @@ -44,7 +47,13 @@ public final void onStart(AttributesBuilder attributes, Context parentContext, R attributes, RPC_SYSTEM_NAME, system == null ? null : SemconvStability.stableRpcSystemName(system)); - internalSet(attributes, RPC_METHOD, getter.getFullMethod(request)); + String method = getter.getRpcMethod(request); + if (getter.isWellKnownMethod(request)) { + internalSet(attributes, RPC_METHOD, method); + } else { + internalSet(attributes, RPC_METHOD_ORIGINAL, method); + internalSet(attributes, RPC_METHOD, "_OTHER"); + } } if (SemconvStability.emitOldRpcSemconv()) { diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java index 921839bd9234..4317f2342959 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.rpc; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; /** A {@link SpanNameExtractor} for RPC requests. */ public final class RpcSpanNameExtractor implements SpanNameExtractor { @@ -27,6 +28,10 @@ private RpcSpanNameExtractor(RpcAttributesGetter getter) { @Override public String extract(REQUEST request) { + if (SemconvStability.emitStableRpcSemconv()) { + return getter.getRpcMethod(request); + } + String service = getter.getService(request); String method = getter.getMethod(request); if (service == null || method == null) { diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java index b50eeae544f2..86411ac51c46 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.rpc; +import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcCommonAttributesExtractor.RPC_METHOD_ORIGINAL; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static org.assertj.core.api.Assertions.entry; @@ -18,13 +19,19 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; import org.junit.jupiter.api.Test; @SuppressWarnings("deprecation") // using deprecated semconv class RpcAttributesExtractorTest { - enum TestGetter implements RpcAttributesGetter> { - INSTANCE; + private static class TestGetter implements RpcAttributesGetter> { + + private final boolean wellKnownMethod; + + private TestGetter(boolean wellKnownMethod) { + this.wellKnownMethod = wellKnownMethod; + } @Override public String getSystem(Map request) { @@ -40,16 +47,42 @@ public String getService(Map request) { public String getMethod(Map request) { return request.get("method"); } + + @Nullable + @Override + public String getRpcMethod(Map request) { + String service = getService(request); + String method = getMethod(request); + if (service == null || method == null) { + return null; + } + return service + "/" + method; + } + + @Override + public boolean isWellKnownMethod(Map stringStringMap) { + return wellKnownMethod; + } } @Test void server() { - testExtractor(RpcServerAttributesExtractor.create(TestGetter.INSTANCE)); + testExtractor(RpcServerAttributesExtractor.create(new TestGetter(false)), "my.Service/Method"); + } + + @Test + void serverWellKnown() { + testExtractor(RpcServerAttributesExtractor.create(new TestGetter(true)), null); } @Test void client() { - testExtractor(RpcClientAttributesExtractor.create(TestGetter.INSTANCE)); + testExtractor(RpcClientAttributesExtractor.create(new TestGetter(false)), "my.Service/Method"); + } + + @Test + void clientWellKnown() { + testExtractor(RpcClientAttributesExtractor.create(new TestGetter(true)), null); } // Stable semconv keys @@ -66,7 +99,8 @@ void client() { private static final AttributeKey RPC_METHOD = io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_METHOD; - private static void testExtractor(AttributesExtractor, Void> extractor) { + private static void testExtractor( + AttributesExtractor, Void> extractor, @Nullable String originalMethod) { Map request = new HashMap<>(); request.put("service", "my.Service"); request.put("method", "Method"); @@ -81,7 +115,12 @@ private static void testExtractor(AttributesExtractor, Void> if (SemconvStability.emitStableRpcSemconv()) { expectedEntries.add(entry(RPC_SYSTEM_NAME, "test")); - expectedEntries.add(entry(RPC_METHOD, "my.Service/Method")); + if (originalMethod != null) { + expectedEntries.add(entry(RPC_METHOD_ORIGINAL, originalMethod)); + expectedEntries.add(entry(RPC_METHOD, "_OTHER")); + } else { + expectedEntries.add(entry(RPC_METHOD, "my.Service/Method")); + } } if (SemconvStability.emitOldRpcSemconv()) { From ca849448a65c1d255d6de973154955fd50128a24 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 20 Jan 2026 13:34:05 +0100 Subject: [PATCH 03/15] deprecate getMethod --- .../api/incubator/semconv/rpc/RpcAttributesGetter.java | 4 ++++ .../semconv/rpc/RpcCommonAttributesExtractor.java | 1 + .../api/incubator/semconv/rpc/RpcSpanNameExtractor.java | 7 ++++++- .../incubator/semconv/rpc/RpcAttributesExtractorTest.java | 1 + .../apachedubbo/v2_7/DubboRpcAttributesGetter.java | 1 + .../awssdk/v1_11/AwsSdkRpcAttributesGetter.java | 1 + .../awssdk/v1_11/AwsSdkSpanNameExtractor.java | 1 + .../awssdk/v2_2/internal/AwsSdkRpcAttributesGetter.java | 1 + .../instrumentation/grpc/v1_6/GrpcRpcAttributesGetter.java | 1 + .../instrumentation/gwt/GwtRpcAttributesGetter.java | 1 + .../rmi/client/RmiClientAttributesGetter.java | 1 + .../rmi/server/RmiServerAttributesGetter.java | 1 + .../spring/rmi/v4_0/client/ClientAttributesGetter.java | 1 + .../spring/rmi/v4_0/server/ServerAttributesGetter.java | 1 + 14 files changed, 22 insertions(+), 1 deletion(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java index 8f28e031e64e..086b51e29df5 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java @@ -22,6 +22,10 @@ public interface RpcAttributesGetter { @Nullable String getService(REQUEST request); + /** + * @deprecated Use {@link #getRpcMethod(REQUEST)} for stable semconv. + */ + @Deprecated @Nullable String getMethod(REQUEST request); diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java index 96f7e0d6f135..10110604404e 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java @@ -38,6 +38,7 @@ abstract class RpcCommonAttributesExtractor this.getter = getter; } + @SuppressWarnings("deprecation") // for getMethod() @Override public final void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) { String system = getter.getSystem(request); diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java index 4317f2342959..dbd524771fce 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java @@ -26,10 +26,15 @@ private RpcSpanNameExtractor(RpcAttributesGetter getter) { this.getter = getter; } + @SuppressWarnings("deprecation") // for getMethod() @Override public String extract(REQUEST request) { if (SemconvStability.emitStableRpcSemconv()) { - return getter.getRpcMethod(request); + String method = getter.getRpcMethod(request); + if (method == null) { + return "RPC request"; + } + return method; } String service = getter.getService(request); diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java index 86411ac51c46..e27feb8e2dc1 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java @@ -43,6 +43,7 @@ public String getService(Map request) { return request.get("service"); } + @Deprecated @Override public String getMethod(Map request) { return request.get("method"); diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRpcAttributesGetter.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRpcAttributesGetter.java index aac66a138c01..7407255cd17a 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRpcAttributesGetter.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRpcAttributesGetter.java @@ -20,6 +20,7 @@ public String getService(DubboRequest request) { return request.invocation().getInvoker().getInterface().getName(); } + @Deprecated @Override public String getMethod(DubboRequest request) { return request.invocation().getMethodName(); diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkRpcAttributesGetter.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkRpcAttributesGetter.java index a3704e88855b..721e6eda4593 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkRpcAttributesGetter.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkRpcAttributesGetter.java @@ -37,6 +37,7 @@ public String getService(Request request) { return request.getServiceName(); } + @Deprecated @Override public String getMethod(Request request) { return OPERATION_NAME.get(request.getOriginalRequest().getClass()); diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkSpanNameExtractor.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkSpanNameExtractor.java index f757c4c026e3..cd5954f80609 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkSpanNameExtractor.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkSpanNameExtractor.java @@ -14,6 +14,7 @@ class AwsSdkSpanNameExtractor implements SpanNameExtractor> { private static final AwsSdkRpcAttributesGetter rpcAttributes = AwsSdkRpcAttributesGetter.INSTANCE; private final NamesCache namesCache = new NamesCache(); + @SuppressWarnings("deprecation") // for getMethod() @Override public String extract(Request request) { return qualifiedOperation( diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkRpcAttributesGetter.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkRpcAttributesGetter.java index f6a6ba078a2b..632e9e233e09 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkRpcAttributesGetter.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkRpcAttributesGetter.java @@ -22,6 +22,7 @@ public String getService(ExecutionAttributes request) { return request.getAttribute(SdkExecutionAttribute.SERVICE_NAME); } + @Deprecated @Override public String getMethod(ExecutionAttributes request) { return request.getAttribute(SdkExecutionAttribute.OPERATION_NAME); diff --git a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRpcAttributesGetter.java b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRpcAttributesGetter.java index e38b95c40f85..81add19bc76a 100644 --- a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRpcAttributesGetter.java +++ b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRpcAttributesGetter.java @@ -32,6 +32,7 @@ public String getService(GrpcRequest request) { return fullMethodName.substring(0, slashIndex); } + @Deprecated @Override @Nullable public String getMethod(GrpcRequest request) { diff --git a/instrumentation/gwt-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/gwt/GwtRpcAttributesGetter.java b/instrumentation/gwt-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/gwt/GwtRpcAttributesGetter.java index 3ebef129f191..95daf1489c1b 100644 --- a/instrumentation/gwt-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/gwt/GwtRpcAttributesGetter.java +++ b/instrumentation/gwt-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/gwt/GwtRpcAttributesGetter.java @@ -21,6 +21,7 @@ public String getService(Method method) { return method.getDeclaringClass().getName(); } + @Deprecated @Override public String getMethod(Method method) { return method.getName(); diff --git a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/client/RmiClientAttributesGetter.java b/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/client/RmiClientAttributesGetter.java index 1b139dacc7aa..bc63641ce7b4 100644 --- a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/client/RmiClientAttributesGetter.java +++ b/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/client/RmiClientAttributesGetter.java @@ -21,6 +21,7 @@ public String getService(Method method) { return method.getDeclaringClass().getName(); } + @Deprecated @Override public String getMethod(Method method) { return method.getName(); diff --git a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/server/RmiServerAttributesGetter.java b/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/server/RmiServerAttributesGetter.java index ad598bb53814..04d9adb08f47 100644 --- a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/server/RmiServerAttributesGetter.java +++ b/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/server/RmiServerAttributesGetter.java @@ -21,6 +21,7 @@ public String getService(ClassAndMethod classAndMethod) { return classAndMethod.declaringClass().getName(); } + @Deprecated @Override public String getMethod(ClassAndMethod classAndMethod) { return classAndMethod.methodName(); diff --git a/instrumentation/spring/spring-rmi-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rmi/v4_0/client/ClientAttributesGetter.java b/instrumentation/spring/spring-rmi-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rmi/v4_0/client/ClientAttributesGetter.java index 19d31b217770..5770bdc2f855 100644 --- a/instrumentation/spring/spring-rmi-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rmi/v4_0/client/ClientAttributesGetter.java +++ b/instrumentation/spring/spring-rmi-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rmi/v4_0/client/ClientAttributesGetter.java @@ -21,6 +21,7 @@ public String getService(Method method) { return method.getDeclaringClass().getName(); } + @Deprecated @Override public String getMethod(Method method) { return method.getName(); diff --git a/instrumentation/spring/spring-rmi-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rmi/v4_0/server/ServerAttributesGetter.java b/instrumentation/spring/spring-rmi-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rmi/v4_0/server/ServerAttributesGetter.java index e7bf12be54cc..82dba30f6f32 100644 --- a/instrumentation/spring/spring-rmi-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rmi/v4_0/server/ServerAttributesGetter.java +++ b/instrumentation/spring/spring-rmi-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rmi/v4_0/server/ServerAttributesGetter.java @@ -21,6 +21,7 @@ public String getService(ClassAndMethod classAndMethod) { return classAndMethod.declaringClass().getName(); } + @Deprecated @Override public String getMethod(ClassAndMethod classAndMethod) { return classAndMethod.methodName(); From 1c2601120ab5861b12df256e9d837deec893b575 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 20 Jan 2026 13:55:05 +0100 Subject: [PATCH 04/15] rename --- .../incubator/semconv/rpc/RpcAttributesGetter.java | 4 ++-- .../semconv/rpc/RpcCommonAttributesExtractor.java | 2 +- .../semconv/rpc/RpcAttributesExtractorTest.java | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java index 086b51e29df5..1a716b97b510 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java @@ -55,13 +55,13 @@ default String getRpcMethod(REQUEST request) { } /** - * Returns whether the RPC method is a well-known method. + * Returns whether the RPC method is a predefined method at the start of the RPC framework. * *

This is used to determine whether to set the {@code rpc.method} attribute to the actual * method name, or to set it to {@code "_OTHER"} and store the actual method name in {@code * rpc.method_original}. */ - default boolean isWellKnownMethod(REQUEST request) { + default boolean isPredefined(REQUEST request) { return false; } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java index 10110604404e..51f16cea6242 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java @@ -49,7 +49,7 @@ public final void onStart(AttributesBuilder attributes, Context parentContext, R RPC_SYSTEM_NAME, system == null ? null : SemconvStability.stableRpcSystemName(system)); String method = getter.getRpcMethod(request); - if (getter.isWellKnownMethod(request)) { + if (getter.isPredefined(request)) { internalSet(attributes, RPC_METHOD, method); } else { internalSet(attributes, RPC_METHOD_ORIGINAL, method); diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java index e27feb8e2dc1..9651146029de 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java @@ -27,10 +27,10 @@ class RpcAttributesExtractorTest { private static class TestGetter implements RpcAttributesGetter> { - private final boolean wellKnownMethod; + private final boolean predefined; - private TestGetter(boolean wellKnownMethod) { - this.wellKnownMethod = wellKnownMethod; + private TestGetter(boolean predefined) { + this.predefined = predefined; } @Override @@ -61,8 +61,8 @@ public String getRpcMethod(Map request) { } @Override - public boolean isWellKnownMethod(Map stringStringMap) { - return wellKnownMethod; + public boolean isPredefined(Map stringStringMap) { + return predefined; } } @@ -72,7 +72,7 @@ void server() { } @Test - void serverWellKnown() { + void serverPredefined() { testExtractor(RpcServerAttributesExtractor.create(new TestGetter(true)), null); } @@ -82,7 +82,7 @@ void client() { } @Test - void clientWellKnown() { + void clientPredefined() { testExtractor(RpcClientAttributesExtractor.create(new TestGetter(true)), null); } From 58ba0560690194beb8e7909a8bed4047bf34884b Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 20 Jan 2026 14:30:32 +0100 Subject: [PATCH 05/15] fix --- .../semconv/rpc/RpcSpanNameExtractorTest.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractorTest.java index e20af5c494a4..3d2e0adca25c 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractorTest.java @@ -9,6 +9,7 @@ import static org.mockito.Mockito.when; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -20,21 +21,29 @@ class RpcSpanNameExtractorTest { @Mock RpcAttributesGetter getter; @Test + @SuppressWarnings("deprecation") // testing deprecated method void normal() { RpcRequest request = new RpcRequest(); - when(getter.getService(request)).thenReturn("my.Service"); - when(getter.getMethod(request)).thenReturn("Method"); + if (SemconvStability.emitStableRpcSemconv()) { + when(getter.getRpcMethod(request)).thenReturn("my.Service/Method"); + } else { + when(getter.getService(request)).thenReturn("my.Service"); + when(getter.getMethod(request)).thenReturn("Method"); + } SpanNameExtractor extractor = RpcSpanNameExtractor.create(getter); assertThat(extractor.extract(request)).isEqualTo("my.Service/Method"); } @Test + @SuppressWarnings("deprecation") // testing deprecated method void serviceNull() { RpcRequest request = new RpcRequest(); - when(getter.getMethod(request)).thenReturn("Method"); + if (!SemconvStability.emitStableRpcSemconv()) { + when(getter.getMethod(request)).thenReturn("Method"); + } SpanNameExtractor extractor = RpcSpanNameExtractor.create(getter); assertThat(extractor.extract(request)).isEqualTo("RPC request"); @@ -44,7 +53,9 @@ void serviceNull() { void methodNull() { RpcRequest request = new RpcRequest(); - when(getter.getService(request)).thenReturn("my.Service"); + if (!SemconvStability.emitStableRpcSemconv()) { + when(getter.getService(request)).thenReturn("my.Service"); + } SpanNameExtractor extractor = RpcSpanNameExtractor.create(getter); assertThat(extractor.extract(request)).isEqualTo("RPC request"); From 68a152269ad90b9d97923694dfb07aa64ec56a7f Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 21 Jan 2026 09:46:53 +0100 Subject: [PATCH 06/15] update descriptions --- .../api/incubator/semconv/rpc/RpcClientMetrics.java | 2 +- .../api/incubator/semconv/rpc/RpcServerMetrics.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java index bf686c7ec5a4..ffc666a7aa50 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java @@ -63,7 +63,7 @@ private RpcClientMetrics(Meter meter) { DoubleHistogramBuilder stableDurationBuilder = meter .histogramBuilder("rpc.client.call.duration") - .setDescription("The duration of an outbound RPC invocation.") + .setDescription("Measures the duration of outbound remote procedure calls (RPC).") .setUnit("s"); RpcMetricsAdvice.applyClientDurationAdvice(stableDurationBuilder, true); stableClientDurationHistogram = stableDurationBuilder.build(); diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java index 6c0cb48d6ce2..6a3e210470fb 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java @@ -63,7 +63,7 @@ private RpcServerMetrics(Meter meter) { DoubleHistogramBuilder stableDurationBuilder = meter .histogramBuilder("rpc.server.call.duration") - .setDescription("The duration of an inbound RPC invocation.") + .setDescription("Measures the duration of inbound remote procedure calls (RPC).") .setUnit("s"); RpcMetricsAdvice.applyServerDurationAdvice(stableDurationBuilder, true); stableServerDurationHistogram = stableDurationBuilder.build(); From 7dff3a2751e6b7a86caefb72124ff4a7c4c62cc9 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 21 Jan 2026 16:30:51 +0100 Subject: [PATCH 07/15] pr review --- .../semconv/rpc/RpcAttributesGetter.java | 27 ++++++++++++++----- .../semconv/rpc/RpcClientMetrics.java | 2 +- .../rpc/RpcCommonAttributesExtractor.java | 6 ++--- .../semconv/rpc/RpcServerMetrics.java | 2 +- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java index 1a716b97b510..10945cfd2959 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java @@ -42,9 +42,6 @@ default Long getResponseSize(REQUEST request) { /** * Returns the fully-qualified RPC method name for stable semconv. * - *

The default implementation concatenates service + "/" + method. Framework implementations - * can override for efficiency if they already have the fully-qualified name available. - * * @param request the request object * @return the fully-qualified RPC method name (e.g., "my.Service/Method"), or null if service or * method is unavailable @@ -55,11 +52,27 @@ default String getRpcMethod(REQUEST request) { } /** - * Returns whether the RPC method is a predefined method at the start of the RPC framework. + * Returns whether the RPC method is recognized as a predefined method by the RPC framework or + * library. + * + *

Some RPC frameworks or libraries provide a fixed set of recognized methods for client stubs + * and server implementations. Instrumentations for such frameworks MUST return {@code true} only + * when the method is recognized by the framework or library. + * + *

When the method is not recognized (for example, when the server receives a request for a + * method that is not predefined on the server), or when instrumentation is not able to reliably + * detect if the method is predefined, this method MUST return {@code false}. * - *

This is used to determine whether to set the {@code rpc.method} attribute to the actual - * method name, or to set it to {@code "_OTHER"} and store the actual method name in {@code - * rpc.method_original}. + *

When this method returns {@code false}, the {@code rpc.method} attribute will be set to + * {@code "_OTHER"} and the {@code rpc.method_original} attribute will be set to the original + * method name. + * + *

Note: If the RPC instrumentation could end up converting valid RPC methods to {@code + * "_OTHER"}, then it SHOULD provide a way to configure the list of recognized RPC methods. + * + * @param request the request object + * @return {@code true} if the method is recognized as predefined by the framework, {@code false} + * otherwise */ default boolean isPredefined(REQUEST request) { return false; diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java index ffc666a7aa50..94735be1eb40 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java @@ -117,7 +117,7 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { return; } Attributes attributes = state.startAttributes().toBuilder().putAll(endAttributes).build(); - double durationNanos = (endNanos - state.startTimeNanos()); + double durationNanos = endNanos - state.startTimeNanos(); // Record to old histogram (milliseconds) if (oldClientDurationHistogram != null) { diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java index 51f16cea6242..e4bddacf7f7a 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java @@ -72,10 +72,8 @@ public final void onEnd( REQUEST request, @Nullable RESPONSE response, @Nullable Throwable error) { - if (SemconvStability.emitStableRpcSemconv()) { - if (error != null) { - internalSet(attributes, ERROR_TYPE, error.getClass().getName()); - } + if (SemconvStability.emitStableRpcSemconv() && error != null) { + internalSet(attributes, ERROR_TYPE, error.getClass().getName()); } } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java index 6a3e210470fb..2028fff71b66 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java @@ -117,7 +117,7 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { return; } Attributes attributes = state.startAttributes().toBuilder().putAll(endAttributes).build(); - double durationNanos = (endNanos - state.startTimeNanos()); + double durationNanos = endNanos - state.startTimeNanos(); // Record to old histogram (milliseconds) if (oldServerDurationHistogram != null) { From d3512c976d65eecaf583a59d1345ba43c064b599 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 21 Jan 2026 16:45:19 +0100 Subject: [PATCH 08/15] rename to old --- .../incubator/semconv/rpc/RpcClientMetrics.java | 4 ++-- .../incubator/semconv/rpc/RpcMetricsAdvice.java | 14 ++++++-------- .../incubator/semconv/rpc/RpcServerMetrics.java | 4 ++-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java index 94735be1eb40..4b32f5fe8bf0 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java @@ -77,7 +77,7 @@ private RpcClientMetrics(Meter meter) { .setUnit("By") .setDescription("Measures the size of RPC request messages (uncompressed).") .ofLongs(); - RpcMetricsAdvice.applyDeprecatedClientRequestSizeAdvice(requestSizeBuilder); + RpcMetricsAdvice.applyOldClientRequestSizeAdvice(requestSizeBuilder); oldClientRequestSize = requestSizeBuilder.build(); LongHistogramBuilder responseSizeBuilder = @@ -86,7 +86,7 @@ private RpcClientMetrics(Meter meter) { .setUnit("By") .setDescription("Measures the size of RPC response messages (uncompressed).") .ofLongs(); - RpcMetricsAdvice.applyDeprecatedClientRequestSizeAdvice(responseSizeBuilder); + RpcMetricsAdvice.applyOldClientRequestSizeAdvice(responseSizeBuilder); oldClientResponseSize = responseSizeBuilder.build(); } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcMetricsAdvice.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcMetricsAdvice.java index 3f9d361fa3e0..048f14c11c07 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcMetricsAdvice.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcMetricsAdvice.java @@ -27,7 +27,7 @@ final class RpcMetricsAdvice { private static final AttributeKey RPC_GRPC_STATUS_CODE = AttributeKey.longKey("rpc.grpc.status_code"); - private static final List> RPC_METRICS_DEPRECATED_ATTRIBUTE_KEYS = + private static final List> RPC_METRICS_OLD_ATTRIBUTE_KEYS = buildAttributeKeysList(false); private static final List> RPC_METRICS_STABLE_ATTRIBUTE_KEYS = buildAttributeKeysList(true); @@ -71,7 +71,7 @@ private static List> buildAttributeKeysList(boolean stable) { } private static List> getAttributeKeys(boolean stable) { - return stable ? RPC_METRICS_STABLE_ATTRIBUTE_KEYS : RPC_METRICS_DEPRECATED_ATTRIBUTE_KEYS; + return stable ? RPC_METRICS_STABLE_ATTRIBUTE_KEYS : RPC_METRICS_OLD_ATTRIBUTE_KEYS; } static void applyClientDurationAdvice(DoubleHistogramBuilder builder, boolean stable) { @@ -92,24 +92,22 @@ static void applyServerDurationAdvice(DoubleHistogramBuilder builder, boolean st ((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(getAttributeKeys(stable)); } - static void applyDeprecatedClientRequestSizeAdvice(LongHistogramBuilder builder) { + static void applyOldClientRequestSizeAdvice(LongHistogramBuilder builder) { if (!(builder instanceof ExtendedLongHistogramBuilder)) { return; } // the list of recommended metrics attributes is from // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md - ((ExtendedLongHistogramBuilder) builder) - .setAttributesAdvice(RPC_METRICS_DEPRECATED_ATTRIBUTE_KEYS); + ((ExtendedLongHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_OLD_ATTRIBUTE_KEYS); } - static void applyDeprecatedServerRequestSizeAdvice(LongHistogramBuilder builder) { + static void applyOldServerRequestSizeAdvice(LongHistogramBuilder builder) { if (!(builder instanceof ExtendedLongHistogramBuilder)) { return; } // the list of recommended metrics attributes is from // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md - ((ExtendedLongHistogramBuilder) builder) - .setAttributesAdvice(RPC_METRICS_DEPRECATED_ATTRIBUTE_KEYS); + ((ExtendedLongHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_OLD_ATTRIBUTE_KEYS); } private RpcMetricsAdvice() {} diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java index 2028fff71b66..9012e4b68705 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java @@ -77,7 +77,7 @@ private RpcServerMetrics(Meter meter) { .setUnit("By") .setDescription("Measures the size of RPC request messages (uncompressed).") .ofLongs(); - RpcMetricsAdvice.applyDeprecatedServerRequestSizeAdvice(requestSizeBuilder); + RpcMetricsAdvice.applyOldServerRequestSizeAdvice(requestSizeBuilder); oldServerRequestSize = requestSizeBuilder.build(); LongHistogramBuilder responseSizeBuilder = @@ -86,7 +86,7 @@ private RpcServerMetrics(Meter meter) { .setUnit("By") .setDescription("Measures the size of RPC response messages (uncompressed).") .ofLongs(); - RpcMetricsAdvice.applyDeprecatedServerRequestSizeAdvice(responseSizeBuilder); + RpcMetricsAdvice.applyOldServerRequestSizeAdvice(responseSizeBuilder); oldServerResponseSize = responseSizeBuilder.build(); } From 1a1cdbad7d8dcfa2a2a35ce118012d40ae2f5cb7 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 21 Jan 2026 16:49:19 +0100 Subject: [PATCH 09/15] pr review --- .../api/incubator/semconv/rpc/RpcMetricsAdvice.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcMetricsAdvice.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcMetricsAdvice.java index 048f14c11c07..773937c128b0 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcMetricsAdvice.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcMetricsAdvice.java @@ -37,11 +37,10 @@ private static List> buildAttributeKeysList(boolean stable) { List> keys = new ArrayList<>(); // Add stable or old RPC system key - if (stable) { - keys.add(RpcCommonAttributesExtractor.RPC_SYSTEM_NAME); - } else { - keys.add(RpcCommonAttributesExtractor.RPC_SYSTEM); - } + keys.add( + stable + ? RpcCommonAttributesExtractor.RPC_SYSTEM_NAME + : RpcCommonAttributesExtractor.RPC_SYSTEM); // Add RPC service (old only) if (!stable) { From ef9440eace9455903391e86bd92d22786af65528 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 21 Jan 2026 17:25:34 +0100 Subject: [PATCH 10/15] add getErrorType --- .../semconv/rpc/RpcAttributesGetter.java | 21 ++++++++++++++++++- .../rpc/RpcClientAttributesExtractor.java | 4 ++-- .../rpc/RpcCommonAttributesExtractor.java | 13 ++++++++---- .../rpc/RpcServerAttributesExtractor.java | 4 ++-- .../rpc/RpcSizeAttributesExtractor.java | 6 +++--- .../semconv/rpc/RpcSpanNameExtractor.java | 6 +++--- .../rpc/RpcAttributesExtractorTest.java | 2 +- .../semconv/rpc/RpcSpanNameExtractorTest.java | 2 +- .../v2_7/DubboRpcAttributesGetter.java | 2 +- .../v1_11/AwsSdkRpcAttributesGetter.java | 2 +- .../internal/AwsSdkRpcAttributesGetter.java | 2 +- .../grpc/v1_6/GrpcRpcAttributesGetter.java | 2 +- .../gwt/GwtRpcAttributesGetter.java | 2 +- .../rmi/client/RmiClientAttributesGetter.java | 2 +- .../rmi/server/RmiServerAttributesGetter.java | 2 +- .../v4_0/client/ClientAttributesGetter.java | 2 +- .../v4_0/server/ServerAttributesGetter.java | 2 +- 17 files changed, 50 insertions(+), 26 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java index 10945cfd2959..748eac2cc160 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java @@ -14,7 +14,7 @@ * library/framework. It will be used by the {@link RpcClientAttributesExtractor} or {@link * RpcServerAttributesExtractor} to obtain the various RPC attributes in a type-generic way. */ -public interface RpcAttributesGetter { +public interface RpcAttributesGetter { @Nullable String getSystem(REQUEST request); @@ -51,6 +51,25 @@ default String getRpcMethod(REQUEST request) { return null; } + /** + * Returns a description of a class of error the operation ended with. + * + *

This method should return {@code null} if there was no error. + * + *

If this method is not implemented, or if it returns {@code null}, the exception class name + * 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. + * + *

Examples: {@code OK}, {@code CANCELLED}, {@code UNKNOWN}, {@code -32602} + */ + @Nullable + default String getErrorType( + REQUEST request, @Nullable RESPONSE response, @Nullable Throwable error) { + return null; + } + /** * Returns whether the RPC method is recognized as a predefined method by the RPC framework or * library. diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientAttributesExtractor.java index c0759f237438..31353ce3ffa6 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientAttributesExtractor.java @@ -22,11 +22,11 @@ public final class RpcClientAttributesExtractor /** Creates the RPC client attributes extractor. */ public static AttributesExtractor create( - RpcAttributesGetter getter) { + RpcAttributesGetter getter) { return new RpcClientAttributesExtractor<>(getter); } - private RpcClientAttributesExtractor(RpcAttributesGetter getter) { + private RpcClientAttributesExtractor(RpcAttributesGetter getter) { super(getter); } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java index e4bddacf7f7a..e5749e27d164 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java @@ -32,9 +32,9 @@ abstract class RpcCommonAttributesExtractor static final AttributeKey RPC_METHOD_ORIGINAL = AttributeKey.stringKey("rpc.method_original"); - private final RpcAttributesGetter getter; + private final RpcAttributesGetter getter; - RpcCommonAttributesExtractor(RpcAttributesGetter getter) { + RpcCommonAttributesExtractor(RpcAttributesGetter getter) { this.getter = getter; } @@ -72,8 +72,13 @@ public final void onEnd( REQUEST request, @Nullable RESPONSE response, @Nullable Throwable error) { - if (SemconvStability.emitStableRpcSemconv() && error != null) { - internalSet(attributes, ERROR_TYPE, error.getClass().getName()); + if (SemconvStability.emitStableRpcSemconv()) { + String errorType = getter.getErrorType(request, response, error); + // fall back to exception class name & _OTHER + if (errorType == null && error != null) { + errorType = error.getClass().getName(); + } + internalSet(attributes, ERROR_TYPE, errorType); } } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerAttributesExtractor.java index 768af20dc7d6..161c78f466dd 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerAttributesExtractor.java @@ -22,11 +22,11 @@ public final class RpcServerAttributesExtractor /** Creates the RPC server attributes extractor. */ public static AttributesExtractor create( - RpcAttributesGetter getter) { + RpcAttributesGetter getter) { return new RpcServerAttributesExtractor<>(getter); } - private RpcServerAttributesExtractor(RpcAttributesGetter getter) { + private RpcServerAttributesExtractor(RpcAttributesGetter getter) { super(getter); } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSizeAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSizeAttributesExtractor.java index dd085e411bdd..2e904f1f8240 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSizeAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSizeAttributesExtractor.java @@ -19,9 +19,9 @@ public final class RpcSizeAttributesExtractor static final AttributeKey RPC_REQUEST_SIZE = AttributeKey.longKey("rpc.request.size"); static final AttributeKey RPC_RESPONSE_SIZE = AttributeKey.longKey("rpc.response.size"); - private final RpcAttributesGetter getter; + private final RpcAttributesGetter getter; - RpcSizeAttributesExtractor(RpcAttributesGetter getter) { + RpcSizeAttributesExtractor(RpcAttributesGetter getter) { this.getter = getter; } @@ -30,7 +30,7 @@ public final class RpcSizeAttributesExtractor * attributesGetter} instance to determine the request and response size. */ public static RpcSizeAttributesExtractor create( - RpcAttributesGetter attributesGetter) { + RpcAttributesGetter attributesGetter) { return new RpcSizeAttributesExtractor<>(attributesGetter); } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java index dbd524771fce..e5b13fb8c729 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java @@ -16,13 +16,13 @@ public final class RpcSpanNameExtractor implements SpanNameExtractor/}. */ public static SpanNameExtractor create( - RpcAttributesGetter attributesExtractor) { + RpcAttributesGetter attributesExtractor) { return new RpcSpanNameExtractor<>(attributesExtractor); } - private final RpcAttributesGetter getter; + private final RpcAttributesGetter getter; - private RpcSpanNameExtractor(RpcAttributesGetter getter) { + private RpcSpanNameExtractor(RpcAttributesGetter getter) { this.getter = getter; } diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java index 9651146029de..78e8e39b1e6e 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java @@ -25,7 +25,7 @@ @SuppressWarnings("deprecation") // using deprecated semconv class RpcAttributesExtractorTest { - private static class TestGetter implements RpcAttributesGetter> { + private static class TestGetter implements RpcAttributesGetter, Void> { private final boolean predefined; diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractorTest.java index 3d2e0adca25c..93e02bc0783d 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractorTest.java @@ -18,7 +18,7 @@ @ExtendWith(MockitoExtension.class) class RpcSpanNameExtractorTest { - @Mock RpcAttributesGetter getter; + @Mock RpcAttributesGetter getter; @Test @SuppressWarnings("deprecation") // testing deprecated method diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRpcAttributesGetter.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRpcAttributesGetter.java index 7407255cd17a..be03eb35cb5b 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRpcAttributesGetter.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRpcAttributesGetter.java @@ -7,7 +7,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter; -enum DubboRpcAttributesGetter implements RpcAttributesGetter { +enum DubboRpcAttributesGetter implements RpcAttributesGetter { INSTANCE; @Override diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkRpcAttributesGetter.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkRpcAttributesGetter.java index 721e6eda4593..f3b2f5cc3cd2 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkRpcAttributesGetter.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkRpcAttributesGetter.java @@ -8,7 +8,7 @@ import com.amazonaws.Request; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter; -enum AwsSdkRpcAttributesGetter implements RpcAttributesGetter> { +enum AwsSdkRpcAttributesGetter implements RpcAttributesGetter, Void> { INSTANCE; private static final ClassValue OPERATION_NAME = diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkRpcAttributesGetter.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkRpcAttributesGetter.java index 632e9e233e09..9919e5cb402c 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkRpcAttributesGetter.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkRpcAttributesGetter.java @@ -9,7 +9,7 @@ import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; -enum AwsSdkRpcAttributesGetter implements RpcAttributesGetter { +enum AwsSdkRpcAttributesGetter implements RpcAttributesGetter { INSTANCE; @Override diff --git a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRpcAttributesGetter.java b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRpcAttributesGetter.java index 81add19bc76a..170345ef8b2f 100644 --- a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRpcAttributesGetter.java +++ b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRpcAttributesGetter.java @@ -13,7 +13,7 @@ import java.util.stream.StreamSupport; import javax.annotation.Nullable; -enum GrpcRpcAttributesGetter implements RpcAttributesGetter { +enum GrpcRpcAttributesGetter implements RpcAttributesGetter { INSTANCE; @Override diff --git a/instrumentation/gwt-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/gwt/GwtRpcAttributesGetter.java b/instrumentation/gwt-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/gwt/GwtRpcAttributesGetter.java index 95daf1489c1b..1dd84824e59c 100644 --- a/instrumentation/gwt-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/gwt/GwtRpcAttributesGetter.java +++ b/instrumentation/gwt-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/gwt/GwtRpcAttributesGetter.java @@ -8,7 +8,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter; import java.lang.reflect.Method; -enum GwtRpcAttributesGetter implements RpcAttributesGetter { +enum GwtRpcAttributesGetter implements RpcAttributesGetter { INSTANCE; @Override diff --git a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/client/RmiClientAttributesGetter.java b/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/client/RmiClientAttributesGetter.java index bc63641ce7b4..95c01d2d7275 100644 --- a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/client/RmiClientAttributesGetter.java +++ b/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/client/RmiClientAttributesGetter.java @@ -8,7 +8,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter; import java.lang.reflect.Method; -enum RmiClientAttributesGetter implements RpcAttributesGetter { +enum RmiClientAttributesGetter implements RpcAttributesGetter { INSTANCE; @Override diff --git a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/server/RmiServerAttributesGetter.java b/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/server/RmiServerAttributesGetter.java index 04d9adb08f47..f8947eba3df5 100644 --- a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/server/RmiServerAttributesGetter.java +++ b/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/server/RmiServerAttributesGetter.java @@ -8,7 +8,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter; import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; -enum RmiServerAttributesGetter implements RpcAttributesGetter { +enum RmiServerAttributesGetter implements RpcAttributesGetter { INSTANCE; @Override diff --git a/instrumentation/spring/spring-rmi-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rmi/v4_0/client/ClientAttributesGetter.java b/instrumentation/spring/spring-rmi-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rmi/v4_0/client/ClientAttributesGetter.java index 5770bdc2f855..9ed8b87924a0 100644 --- a/instrumentation/spring/spring-rmi-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rmi/v4_0/client/ClientAttributesGetter.java +++ b/instrumentation/spring/spring-rmi-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rmi/v4_0/client/ClientAttributesGetter.java @@ -8,7 +8,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter; import java.lang.reflect.Method; -public enum ClientAttributesGetter implements RpcAttributesGetter { +public enum ClientAttributesGetter implements RpcAttributesGetter { INSTANCE; @Override diff --git a/instrumentation/spring/spring-rmi-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rmi/v4_0/server/ServerAttributesGetter.java b/instrumentation/spring/spring-rmi-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rmi/v4_0/server/ServerAttributesGetter.java index 82dba30f6f32..b4c150ca1dfe 100644 --- a/instrumentation/spring/spring-rmi-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rmi/v4_0/server/ServerAttributesGetter.java +++ b/instrumentation/spring/spring-rmi-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/rmi/v4_0/server/ServerAttributesGetter.java @@ -8,7 +8,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter; import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; -public enum ServerAttributesGetter implements RpcAttributesGetter { +public enum ServerAttributesGetter implements RpcAttributesGetter { INSTANCE; @Override From 5d51fbbbdb66be2dc281d169898eed56410fa10a Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 21 Jan 2026 17:29:44 +0100 Subject: [PATCH 11/15] add getErrorType --- .../rpc/RpcAttributesExtractorTest.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java index 78e8e39b1e6e..a2bacc147858 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java @@ -7,6 +7,7 @@ import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcCommonAttributesExtractor.RPC_METHOD_ORIGINAL; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; import static org.assertj.core.api.Assertions.entry; import io.opentelemetry.api.common.AttributeKey; @@ -60,6 +61,13 @@ public String getRpcMethod(Map request) { return service + "/" + method; } + @Nullable + @Override + public String getErrorType( + Map request, @Nullable Void response, @Nullable Throwable error) { + return request.get("errorType"); + } + @Override public boolean isPredefined(Map stringStringMap) { return predefined; @@ -139,4 +147,63 @@ private static void testExtractor( extractor.onEnd(attributes, context, request, null, null); assertThat(attributes.build()).containsOnly(expectedArray); } + + @Test + void shouldExtractErrorType_getter() { + Map request = new HashMap<>(); + request.put("service", "my.Service"); + request.put("method", "Method"); + request.put("errorType", "CANCELLED"); + + AttributesExtractor, Void> extractor = + RpcServerAttributesExtractor.create(new TestGetter(false)); + + Context context = Context.root(); + AttributesBuilder attributes = Attributes.builder(); + extractor.onStart(attributes, context, request); + extractor.onEnd(attributes, context, request, null, null); + + if (SemconvStability.emitStableRpcSemconv()) { + assertThat(attributes.build()).containsEntry(ERROR_TYPE, "CANCELLED"); + } + } + + @Test + void shouldExtractErrorType_exceptionClassName() { + Map request = new HashMap<>(); + request.put("service", "my.Service"); + request.put("method", "Method"); + + AttributesExtractor, Void> extractor = + RpcServerAttributesExtractor.create(new TestGetter(false)); + + Context context = Context.root(); + AttributesBuilder attributes = Attributes.builder(); + extractor.onStart(attributes, context, request); + extractor.onEnd(attributes, context, request, null, new IllegalArgumentException()); + + if (SemconvStability.emitStableRpcSemconv()) { + assertThat(attributes.build()) + .containsEntry(ERROR_TYPE, "java.lang.IllegalArgumentException"); + } + } + + @Test + void shouldNotExtractErrorType_noError() { + Map request = new HashMap<>(); + request.put("service", "my.Service"); + request.put("method", "Method"); + + AttributesExtractor, Void> extractor = + RpcServerAttributesExtractor.create(new TestGetter(false)); + + Context context = Context.root(); + AttributesBuilder attributes = Attributes.builder(); + extractor.onStart(attributes, context, request); + extractor.onEnd(attributes, context, request, null, null); + + if (SemconvStability.emitStableRpcSemconv()) { + assertThat(attributes.build()).doesNotContainKey(ERROR_TYPE); + } + } } From 25fd8e6f434020cc2d4dd3673808da8a8b5f7dad Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 21 Jan 2026 17:44:24 +0100 Subject: [PATCH 12/15] system fallback for span name --- .../semconv/rpc/RpcSpanNameExtractor.java | 10 ++++-- .../semconv/rpc/RpcSpanNameExtractorTest.java | 35 +++++++++++++++---- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java index e5b13fb8c729..f2abbc92ab21 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java @@ -31,10 +31,14 @@ private RpcSpanNameExtractor(RpcAttributesGetter getter) { public String extract(REQUEST request) { if (SemconvStability.emitStableRpcSemconv()) { String method = getter.getRpcMethod(request); - if (method == null) { - return "RPC request"; + if (method != null) { + return method; } - return method; + String system = getter.getSystem(request); + if (system != null) { + return system; + } + return "RPC request"; } String service = getter.getService(request); diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractorTest.java index 93e02bc0783d..503367ed0add 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractorTest.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.rpc; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.mockito.Mockito.when; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; @@ -39,11 +40,11 @@ void normal() { @Test @SuppressWarnings("deprecation") // testing deprecated method void serviceNull() { + assumeTrue(!SemconvStability.emitStableRpcSemconv()); + RpcRequest request = new RpcRequest(); - if (!SemconvStability.emitStableRpcSemconv()) { - when(getter.getMethod(request)).thenReturn("Method"); - } + when(getter.getMethod(request)).thenReturn("Method"); SpanNameExtractor extractor = RpcSpanNameExtractor.create(getter); assertThat(extractor.extract(request)).isEqualTo("RPC request"); @@ -51,11 +52,33 @@ void serviceNull() { @Test void methodNull() { + assumeTrue(!SemconvStability.emitStableRpcSemconv()); + RpcRequest request = new RpcRequest(); - if (!SemconvStability.emitStableRpcSemconv()) { - when(getter.getService(request)).thenReturn("my.Service"); - } + when(getter.getService(request)).thenReturn("my.Service"); + + SpanNameExtractor extractor = RpcSpanNameExtractor.create(getter); + assertThat(extractor.extract(request)).isEqualTo("RPC request"); + } + + @Test + void rpcMethodSystemFallback() { + assumeTrue(SemconvStability.emitStableRpcSemconv()); + + RpcRequest request = new RpcRequest(); + + when(getter.getSystem(request)).thenReturn("system"); + + SpanNameExtractor extractor = RpcSpanNameExtractor.create(getter); + assertThat(extractor.extract(request)).isEqualTo("system"); + } + + @Test + void rpcMethodNull() { + assumeTrue(SemconvStability.emitStableRpcSemconv()); + + RpcRequest request = new RpcRequest(); SpanNameExtractor extractor = RpcSpanNameExtractor.create(getter); assertThat(extractor.extract(request)).isEqualTo("RPC request"); From 0a09fd5ef6a1c2f56e3b8cb4678b6063df41904b Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 21 Jan 2026 18:36:25 +0100 Subject: [PATCH 13/15] system fallback for span name --- .../api/incubator/semconv/rpc/RpcSpanNameExtractor.java | 6 +++--- .../instrumentation/api/internal/SemconvStability.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java index f2abbc92ab21..fcd07fefd017 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java @@ -16,13 +16,13 @@ public final class RpcSpanNameExtractor implements SpanNameExtractor/}. */ public static SpanNameExtractor create( - RpcAttributesGetter attributesExtractor) { + RpcAttributesGetter attributesExtractor) { return new RpcSpanNameExtractor<>(attributesExtractor); } - private final RpcAttributesGetter getter; + private final RpcAttributesGetter getter; - private RpcSpanNameExtractor(RpcAttributesGetter getter) { + private RpcSpanNameExtractor(RpcAttributesGetter getter) { this.getter = getter; } 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 c9e15e131245..5180e905f5e5 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 @@ -189,8 +189,6 @@ public static AttributeKey getOldRpcMethodAttributeKey() { return RPC_METHOD; } - private SemconvStability() {} - public static Attributes getOldRpcMetricAttributes(Attributes attributes) { if (emitStableRpcSemconv()) { // need to copy attributes @@ -198,4 +196,6 @@ public static Attributes getOldRpcMetricAttributes(Attributes attributes) { } return attributes; } + + private SemconvStability() {} } From 7690d2084357324dea5f35d7fcfa7ce14d24d5b8 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 21 Jan 2026 18:52:00 +0100 Subject: [PATCH 14/15] add getErrorType --- .../apachedubbo/v2_7/DubboRpcAttributesGetter.java | 3 ++- .../awssdk/v1_11/AwsSdkRpcAttributesGetter.java | 3 ++- .../awssdk/v2_2/internal/AwsSdkRpcAttributesGetter.java | 2 +- .../instrumentation/grpc/v1_6/GrpcRpcAttributesGetter.java | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRpcAttributesGetter.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRpcAttributesGetter.java index be03eb35cb5b..e2683d206c71 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRpcAttributesGetter.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRpcAttributesGetter.java @@ -6,8 +6,9 @@ package io.opentelemetry.instrumentation.apachedubbo.v2_7; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter; +import org.apache.dubbo.rpc.Result; -enum DubboRpcAttributesGetter implements RpcAttributesGetter { +enum DubboRpcAttributesGetter implements RpcAttributesGetter { INSTANCE; @Override diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkRpcAttributesGetter.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkRpcAttributesGetter.java index f3b2f5cc3cd2..3d014d0e4a48 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkRpcAttributesGetter.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkRpcAttributesGetter.java @@ -6,9 +6,10 @@ package io.opentelemetry.instrumentation.awssdk.v1_11; import com.amazonaws.Request; +import com.amazonaws.Response; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter; -enum AwsSdkRpcAttributesGetter implements RpcAttributesGetter, Void> { +enum AwsSdkRpcAttributesGetter implements RpcAttributesGetter, Response> { INSTANCE; private static final ClassValue OPERATION_NAME = diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkRpcAttributesGetter.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkRpcAttributesGetter.java index 9919e5cb402c..10e5f7fa4115 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkRpcAttributesGetter.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkRpcAttributesGetter.java @@ -9,7 +9,7 @@ import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; -enum AwsSdkRpcAttributesGetter implements RpcAttributesGetter { +enum AwsSdkRpcAttributesGetter implements RpcAttributesGetter { INSTANCE; @Override diff --git a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRpcAttributesGetter.java b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRpcAttributesGetter.java index 170345ef8b2f..a7211fd13fef 100644 --- a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRpcAttributesGetter.java +++ b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRpcAttributesGetter.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.grpc.v1_6; import io.grpc.Metadata; +import io.grpc.Status; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter; import java.util.Collections; import java.util.List; @@ -13,7 +14,7 @@ import java.util.stream.StreamSupport; import javax.annotation.Nullable; -enum GrpcRpcAttributesGetter implements RpcAttributesGetter { +enum GrpcRpcAttributesGetter implements RpcAttributesGetter { INSTANCE; @Override From 8213ba4f56d2be65aef4c410c6fcaeb2362c3984 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 10 Feb 2026 14:47:01 +0100 Subject: [PATCH 15/15] Use ContextCustomizer to pass old rpc.method to metrics in dup mode When both old and stable RPC semconv are enabled, both use the rpc.method attribute key with different values. Previously this was solved with a virtual rpc.method.deprecated key in SemconvStability that leaked onto spans. Instead, register a ContextCustomizer that stores the old method value directly in context via a ContextKey. RpcClientMetrics/RpcServerMetrics read from context in onStart() and use the stored value when recording old metrics. This keeps the old method value off spans entirely. --- .../semconv/rpc/RpcClientMetrics.java | 35 ++++++++++++------- .../rpc/RpcCommonAttributesExtractor.java | 27 ++++++++++++-- .../semconv/rpc/RpcServerMetrics.java | 35 ++++++++++++------- .../rpc/RpcAttributesExtractorTest.java | 4 ++- .../semconv/rpc/RpcClientMetricsTest.java | 14 ++++++-- .../semconv/rpc/RpcServerMetricsTest.java | 14 ++++++-- .../api/internal/SemconvStability.java | 25 ------------- .../v2_7/DubboTelemetryBuilder.java | 9 +++-- .../grpc/v1_6/GrpcTelemetryBuilder.java | 5 +++ 9 files changed, 107 insertions(+), 61 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java index 4b32f5fe8bf0..b04f4753de88 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java @@ -5,6 +5,8 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.rpc; +import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcCommonAttributesExtractor.OLD_RPC_METHOD_CONTEXT_KEY; +import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcCommonAttributesExtractor.RPC_METHOD; import static java.util.logging.Level.FINE; import com.google.auto.value.AutoValue; @@ -103,7 +105,8 @@ public static OperationMetrics get() { public Context onStart(Context context, Attributes startAttributes, long startNanos) { return context.with( RPC_CLIENT_REQUEST_METRICS_STATE, - new AutoValue_RpcClientMetrics_State(startAttributes, startNanos)); + new AutoValue_RpcClientMetrics_State( + startAttributes, startNanos, context.get(OLD_RPC_METHOD_CONTEXT_KEY))); } @Override @@ -121,10 +124,8 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { // Record to old histogram (milliseconds) if (oldClientDurationHistogram != null) { - oldClientDurationHistogram.record( - durationNanos / NANOS_PER_MS, - SemconvStability.getOldRpcMetricAttributes(attributes), - context); + Attributes oldAttributes = getOldAttributes(attributes, state); + oldClientDurationHistogram.record(durationNanos / NANOS_PER_MS, oldAttributes, context); } // Record to stable histogram (seconds) @@ -133,29 +134,37 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { } if (SemconvStability.emitOldRpcSemconv()) { + Attributes oldAttributes = getOldAttributes(attributes, state); + Long rpcClientRequestBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE); if (rpcClientRequestBodySize != null) { - oldClientRequestSize.record( - rpcClientRequestBodySize, - SemconvStability.getOldRpcMetricAttributes(attributes), - context); + oldClientRequestSize.record(rpcClientRequestBodySize, oldAttributes, context); } Long rpcClientResponseBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_RESPONSE_SIZE); if (rpcClientResponseBodySize != null) { - oldClientResponseSize.record( - rpcClientResponseBodySize, - SemconvStability.getOldRpcMetricAttributes(attributes), - context); + oldClientResponseSize.record(rpcClientResponseBodySize, oldAttributes, context); } } } + private static Attributes getOldAttributes(Attributes attributes, State state) { + String oldRpcMethod = state.oldRpcMethod(); + if (oldRpcMethod != null) { + // dup mode: replace stable rpc.method with old value + return attributes.toBuilder().put(RPC_METHOD, oldRpcMethod).build(); + } + return attributes; + } + @AutoValue abstract static class State { abstract Attributes startAttributes(); abstract long startTimeNanos(); + + @Nullable + abstract String oldRpcMethod(); } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java index e5749e27d164..33e73d2cfea9 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java @@ -11,15 +11,34 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextKey; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; import io.opentelemetry.instrumentation.api.internal.SemconvStability; import javax.annotation.Nullable; -abstract class RpcCommonAttributesExtractor +public abstract class RpcCommonAttributesExtractor implements AttributesExtractor { static final AttributeKey RPC_METHOD = AttributeKey.stringKey("rpc.method"); + static final ContextKey OLD_RPC_METHOD_CONTEXT_KEY = + ContextKey.named("otel-rpc-old-method"); + + @SuppressWarnings("deprecation") // for getMethod() + public static ContextCustomizer oldMethodContextCustomizer( + RpcAttributesGetter getter) { + return (context, request, startAttributes) -> { + if (SemconvStability.emitOldRpcSemconv() && SemconvStability.emitStableRpcSemconv()) { + String oldMethod = getter.getMethod(request); + if (oldMethod != null) { + return context.with(OLD_RPC_METHOD_CONTEXT_KEY, oldMethod); + } + } + return context; + }; + } + // Stable semconv keys static final AttributeKey RPC_SYSTEM_NAME = AttributeKey.stringKey("rpc.system.name"); @@ -60,8 +79,10 @@ public final void onStart(AttributesBuilder attributes, Context parentContext, R if (SemconvStability.emitOldRpcSemconv()) { internalSet(attributes, RPC_SYSTEM, system); internalSet(attributes, RPC_SERVICE, getter.getService(request)); - internalSet( - attributes, SemconvStability.getOldRpcMethodAttributeKey(), getter.getMethod(request)); + if (!SemconvStability.emitStableRpcSemconv()) { + // only set old rpc.method on spans when there's no clash with stable rpc.method + internalSet(attributes, RPC_METHOD, getter.getMethod(request)); + } } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java index 9012e4b68705..7c87351936eb 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetrics.java @@ -5,6 +5,8 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.rpc; +import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcCommonAttributesExtractor.OLD_RPC_METHOD_CONTEXT_KEY; +import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcCommonAttributesExtractor.RPC_METHOD; import static java.util.logging.Level.FINE; import com.google.auto.value.AutoValue; @@ -103,7 +105,8 @@ public static OperationMetrics get() { public Context onStart(Context context, Attributes startAttributes, long startNanos) { return context.with( RPC_SERVER_REQUEST_METRICS_STATE, - new AutoValue_RpcServerMetrics_State(startAttributes, startNanos)); + new AutoValue_RpcServerMetrics_State( + startAttributes, startNanos, context.get(OLD_RPC_METHOD_CONTEXT_KEY))); } @Override @@ -121,10 +124,8 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { // Record to old histogram (milliseconds) if (oldServerDurationHistogram != null) { - oldServerDurationHistogram.record( - durationNanos / NANOS_PER_MS, - SemconvStability.getOldRpcMetricAttributes(attributes), - context); + Attributes oldAttributes = getOldAttributes(attributes, state); + oldServerDurationHistogram.record(durationNanos / NANOS_PER_MS, oldAttributes, context); } // Record to stable histogram (seconds) @@ -133,29 +134,37 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { } if (SemconvStability.emitOldRpcSemconv()) { + Attributes oldAttributes = getOldAttributes(attributes, state); + Long rpcServerRequestBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE); if (rpcServerRequestBodySize != null) { - oldServerRequestSize.record( - rpcServerRequestBodySize, - SemconvStability.getOldRpcMetricAttributes(attributes), - context); + oldServerRequestSize.record(rpcServerRequestBodySize, oldAttributes, context); } Long rpcServerResponseBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_RESPONSE_SIZE); if (rpcServerResponseBodySize != null) { - oldServerResponseSize.record( - rpcServerResponseBodySize, - SemconvStability.getOldRpcMetricAttributes(attributes), - context); + oldServerResponseSize.record(rpcServerResponseBodySize, oldAttributes, context); } } } + private static Attributes getOldAttributes(Attributes attributes, State state) { + String oldRpcMethod = state.oldRpcMethod(); + if (oldRpcMethod != null) { + // dup mode: replace stable rpc.method with old value + return attributes.toBuilder().put(RPC_METHOD, oldRpcMethod).build(); + } + return attributes; + } + @AutoValue abstract static class State { abstract Attributes startAttributes(); abstract long startTimeNanos(); + + @Nullable + abstract String oldRpcMethod(); } } diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java index a2bacc147858..bd735e18e24e 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesExtractorTest.java @@ -135,7 +135,9 @@ private static void testExtractor( if (SemconvStability.emitOldRpcSemconv()) { expectedEntries.add(entry(RPC_SYSTEM, "test")); expectedEntries.add(entry(RPC_SERVICE, "my.Service")); - expectedEntries.add(entry(SemconvStability.getOldRpcMethodAttributeKey(), "Method")); + if (!SemconvStability.emitStableRpcSemconv()) { + expectedEntries.add(entry(RPC_METHOD, "Method")); + } } // safe conversion for test assertions diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetricsTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetricsTest.java index d12fdb3f4235..897bd24e9341 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetricsTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetricsTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.rpc; +import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcCommonAttributesExtractor.OLD_RPC_METHOD_CONTEXT_KEY; import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcCommonAttributesExtractor.RPC_SYSTEM_NAME; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; @@ -75,12 +76,19 @@ void collectsMetrics() { "090a0b0c0d0e0f00", TraceFlags.getSampled(), TraceState.getDefault()))); + if (SemconvStability.emitOldRpcSemconv() && SemconvStability.emitStableRpcSemconv()) { + parent = parent.with(OLD_RPC_METHOD_CONTEXT_KEY, "exampleMethod"); + } Context context1 = listener.onStart(parent, requestAttributes1, nanos(100)); assertThat(metricReader.collectAllMetrics()).isEmpty(); - Context context2 = listener.onStart(Context.root(), requestAttributes2, nanos(150)); + Context context2Root = Context.root(); + if (SemconvStability.emitOldRpcSemconv() && SemconvStability.emitStableRpcSemconv()) { + context2Root = context2Root.with(OLD_RPC_METHOD_CONTEXT_KEY, "exampleMethod"); + } + Context context2 = listener.onStart(context2Root, requestAttributes2, nanos(150)); assertThat(metricReader.collectAllMetrics()).isEmpty(); @@ -304,7 +312,9 @@ private static Attributes buildRequestAttributes( if (SemconvStability.emitOldRpcSemconv()) { builder.put(RPC_SYSTEM, system); builder.put(RPC_SERVICE, service); - builder.put(SemconvStability.getOldRpcMethodAttributeKey(), method); + if (!SemconvStability.emitStableRpcSemconv()) { + builder.put(RPC_METHOD, method); + } } if (SemconvStability.emitStableRpcSemconv()) { diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetricsTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetricsTest.java index ee178370b9cd..1863c7873245 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetricsTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcServerMetricsTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.rpc; +import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcCommonAttributesExtractor.OLD_RPC_METHOD_CONTEXT_KEY; import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcCommonAttributesExtractor.RPC_SYSTEM_NAME; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; @@ -78,12 +79,19 @@ void collectsMetrics() { "090a0b0c0d0e0f00", TraceFlags.getSampled(), TraceState.getDefault()))); + if (SemconvStability.emitOldRpcSemconv() && SemconvStability.emitStableRpcSemconv()) { + parent = parent.with(OLD_RPC_METHOD_CONTEXT_KEY, "exampleMethod"); + } Context context1 = listener.onStart(parent, requestAttributes1, nanos(100)); assertThat(metricReader.collectAllMetrics()).isEmpty(); - Context context2 = listener.onStart(Context.root(), requestAttributes2, nanos(150)); + Context context2Root = Context.root(); + if (SemconvStability.emitOldRpcSemconv() && SemconvStability.emitStableRpcSemconv()) { + context2Root = context2Root.with(OLD_RPC_METHOD_CONTEXT_KEY, "exampleMethod"); + } + Context context2 = listener.onStart(context2Root, requestAttributes2, nanos(150)); assertThat(metricReader.collectAllMetrics()).isEmpty(); @@ -309,7 +317,9 @@ private static Attributes buildRequestAttributes( if (SemconvStability.emitOldRpcSemconv()) { builder.put(RPC_SYSTEM, system); builder.put(RPC_SERVICE, service); - builder.put(SemconvStability.getOldRpcMethodAttributeKey(), method); + if (!SemconvStability.emitStableRpcSemconv()) { + builder.put(RPC_METHOD, method); + } } if (SemconvStability.emitStableRpcSemconv()) { 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 5180e905f5e5..6547efca8950 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 @@ -7,8 +7,6 @@ import static java.util.Arrays.asList; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -20,13 +18,6 @@ */ public final class SemconvStability { - // copied from RpcIncubatingAttributes - private static final AttributeKey RPC_METHOD = AttributeKey.stringKey("rpc.method"); - - // virtual key to avoid clash with stable rpc.method - private static final AttributeKey RPC_METHOD_OLD = - AttributeKey.stringKey("rpc.method.deprecated"); - private static final boolean emitOldDatabaseSemconv; private static final boolean emitStableDatabaseSemconv; @@ -181,21 +172,5 @@ public static String stableRpcSystemName(String oldRpcSystem) { return rpcSystemName != null ? rpcSystemName : oldRpcSystem; } - public static AttributeKey getOldRpcMethodAttributeKey() { - if (emitStableRpcSemconv()) { - // to avoid clash when both semconv are emitted - return RPC_METHOD_OLD; - } - return RPC_METHOD; - } - - public static Attributes getOldRpcMetricAttributes(Attributes attributes) { - if (emitStableRpcSemconv()) { - // need to copy attributes - return attributes.toBuilder().put(RPC_METHOD, attributes.get(RPC_METHOD_OLD)).build(); - } - return attributes; - } - private SemconvStability() {} } diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboTelemetryBuilder.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboTelemetryBuilder.java index a0645eb5e21e..92020fc47700 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboTelemetryBuilder.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboTelemetryBuilder.java @@ -11,6 +11,7 @@ import io.opentelemetry.instrumentation.apachedubbo.v2_7.internal.DubboClientNetworkAttributesGetter; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcClientAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcClientMetrics; +import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcCommonAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcServerAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcServerMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcSpanNameExtractor; @@ -116,7 +117,9 @@ public DubboTelemetry build() { .addAttributesExtractor(RpcServerAttributesExtractor.create(rpcAttributesGetter)) .addAttributesExtractor(NetworkAttributesExtractor.create(netServerAttributesGetter)) .addAttributesExtractors(attributesExtractors) - .addOperationMetrics(RpcServerMetrics.get()); + .addOperationMetrics(RpcServerMetrics.get()) + .addContextCustomizer( + RpcCommonAttributesExtractor.oldMethodContextCustomizer(rpcAttributesGetter)); InstrumenterBuilder clientInstrumenterBuilder = Instrumenter.builder( @@ -125,7 +128,9 @@ public DubboTelemetry build() { .addAttributesExtractor(ServerAttributesExtractor.create(netClientAttributesGetter)) .addAttributesExtractor(NetworkAttributesExtractor.create(netClientAttributesGetter)) .addAttributesExtractors(attributesExtractors) - .addOperationMetrics(RpcClientMetrics.get()); + .addOperationMetrics(RpcClientMetrics.get()) + .addContextCustomizer( + RpcCommonAttributesExtractor.oldMethodContextCustomizer(rpcAttributesGetter)); if (peerService != null) { clientInstrumenterBuilder.addAttributesExtractor( diff --git a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcTelemetryBuilder.java b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcTelemetryBuilder.java index 041cfdbbdc90..91c7ff45f67f 100644 --- a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcTelemetryBuilder.java +++ b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcTelemetryBuilder.java @@ -11,6 +11,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcClientAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcClientMetrics; +import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcCommonAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcServerAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcServerMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcSizeAttributesExtractor; @@ -202,6 +203,8 @@ public GrpcTelemetry build() { .addOperationMetrics(RpcClientMetrics.get()); Experimental.addOperationListenerAttributesExtractor( clientInstrumenterBuilder, RpcSizeAttributesExtractor.create(rpcAttributesGetter)); + clientInstrumenterBuilder.addContextCustomizer( + RpcCommonAttributesExtractor.oldMethodContextCustomizer(rpcAttributesGetter)); serverInstrumenterBuilder .setSpanStatusExtractor(GrpcSpanStatusExtractor.SERVER) .addAttributesExtractors(additionalExtractors) @@ -215,6 +218,8 @@ public GrpcTelemetry build() { .addOperationMetrics(RpcServerMetrics.get()); Experimental.addOperationListenerAttributesExtractor( serverInstrumenterBuilder, RpcSizeAttributesExtractor.create(rpcAttributesGetter)); + serverInstrumenterBuilder.addContextCustomizer( + RpcCommonAttributesExtractor.oldMethodContextCustomizer(rpcAttributesGetter)); if (peerService != null) { clientInstrumenterBuilder.addAttributesExtractor(