From fc4a9eb4d8ac525a282b1487e93b58ac5611f803 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 10 Feb 2026 12:26:50 +0100 Subject: [PATCH 01/11] Add dual-semconv support to RPC attributes extractors and span name extractor RpcCommonAttributesExtractor now conditionally emits old (rpc.system, rpc.service, rpc.method) and/or stable (rpc.system.name, rpc.method, rpc.method_original, error.type) attributes based on SemconvStability flags. RpcSpanNameExtractor uses getRpcMethod() for stable semconv with system name fallback. In dup mode, old rpc.method is omitted from spans to avoid clashing with stable rpc.method. Instead, a ContextCustomizer stores the old method value in context via a ContextKey for metrics to read. Also adds RpcAttributesGetter.isPredefined() for rpc.method vs rpc.method_original handling. --- .../semconv/rpc/RpcAttributesGetter.java | 27 +++ .../rpc/RpcCommonAttributesExtractor.java | 49 ++++- .../semconv/rpc/RpcSpanNameExtractor.java | 13 ++ .../rpc/RpcAttributesExtractorTest.java | 169 ++++++++++++++++-- .../semconv/rpc/RpcSpanNameExtractorTest.java | 42 ++++- 5 files changed, 274 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 b05f9bd4331b..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 @@ -69,4 +69,31 @@ 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. + * + *

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}. + * + *

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/RpcCommonAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java index 99bcaaa3032f..9bbf2c69e620 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,21 +6,32 @@ 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"); + static final AttributeKey RPC_METHOD_ORIGINAL = + AttributeKey.stringKey("rpc.method_original"); + private final RpcAttributesGetter getter; RpcCommonAttributesExtractor(RpcAttributesGetter getter) { @@ -30,9 +41,30 @@ abstract class RpcCommonAttributesExtractor @SuppressWarnings("deprecation") // for getMethod() @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)); + String method = getter.getRpcMethod(request); + if (getter.isPredefined(request)) { + internalSet(attributes, RPC_METHOD, method); + } else { + internalSet(attributes, RPC_METHOD_ORIGINAL, method); + internalSet(attributes, RPC_METHOD, "_OTHER"); + } + } + + if (SemconvStability.emitOldRpcSemconv()) { + internalSet(attributes, RPC_SYSTEM, system); + internalSet(attributes, RPC_SERVICE, getter.getService(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)); + } + } } @Override @@ -42,6 +74,13 @@ public final void onEnd( REQUEST request, @Nullable RESPONSE response, @Nullable Throwable error) { - // No response attributes + 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/RpcSpanNameExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcSpanNameExtractor.java index 41fb57a7a023..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 @@ -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 { @@ -28,6 +29,18 @@ private RpcSpanNameExtractor(RpcAttributesGetter getter) { @SuppressWarnings("deprecation") // for getMethod() @Override public String extract(REQUEST request) { + if (SemconvStability.emitStableRpcSemconv()) { + String method = getter.getRpcMethod(request); + if (method != null) { + return method; + } + String system = getter.getSystem(request); + if (system != null) { + return system; + } + return "RPC 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 7357ac924323..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 @@ -5,23 +5,34 @@ 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 io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; 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 javax.annotation.Nullable; import org.junit.jupiter.api.Test; @SuppressWarnings("deprecation") // using deprecated semconv class RpcAttributesExtractorTest { - enum TestGetter implements RpcAttributesGetter, Void> { - INSTANCE; + private static class TestGetter implements RpcAttributesGetter, Void> { + + private final boolean predefined; + + private TestGetter(boolean predefined) { + this.predefined = predefined; + } @Override public String getSystem(Map request) { @@ -38,37 +49,163 @@ 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; + } + + @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; + } } @Test void server() { - testExtractor(RpcServerAttributesExtractor.create(TestGetter.INSTANCE)); + testExtractor(RpcServerAttributesExtractor.create(new TestGetter(false)), "my.Service/Method"); + } + + @Test + void serverPredefined() { + 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 clientPredefined() { + testExtractor(RpcClientAttributesExtractor.create(new TestGetter(true)), null); + } + + // 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, @Nullable String originalMethod) { + Map request = new HashMap<>(); + request.put("service", "my.Service"); + request.put("method", "Method"); + + Context context = Context.root(); + + AttributesBuilder attributes = Attributes.builder(); + extractor.onStart(attributes, context, request); + + // Build expected entries list based on semconv mode + List, ?>> expectedEntries = new ArrayList<>(); + + if (SemconvStability.emitStableRpcSemconv()) { + expectedEntries.add(entry(RPC_SYSTEM_NAME, "test")); + 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()) { + expectedEntries.add(entry(RPC_SYSTEM, "test")); + expectedEntries.add(entry(RPC_SERVICE, "my.Service")); + if (!SemconvStability.emitStableRpcSemconv()) { + expectedEntries.add(entry(RPC_METHOD, "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(expectedArray); } - private static void testExtractor(AttributesExtractor, Void> extractor) { + @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); - assertThat(attributes.build()) - .containsOnly( - entry(RpcIncubatingAttributes.RPC_SYSTEM, "test"), - entry(RpcIncubatingAttributes.RPC_SERVICE, "my.Service"), - entry(RpcIncubatingAttributes.RPC_METHOD, "Method")); 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")); + + if (SemconvStability.emitStableRpcSemconv()) { + assertThat(attributes.build()).doesNotContainKey(ERROR_TYPE); + } } } 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 27e34a448341..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,9 +6,11 @@ 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; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -17,23 +19,29 @@ @ExtendWith(MockitoExtension.class) class RpcSpanNameExtractorTest { - @Mock RpcAttributesGetter getter; + @Mock RpcAttributesGetter getter; - @SuppressWarnings("deprecation") // testing deprecated method @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"); } - @SuppressWarnings("deprecation") // testing deprecated method @Test + @SuppressWarnings("deprecation") // testing deprecated method void serviceNull() { + assumeTrue(!SemconvStability.emitStableRpcSemconv()); + RpcRequest request = new RpcRequest(); when(getter.getMethod(request)).thenReturn("Method"); @@ -44,6 +52,8 @@ void serviceNull() { @Test void methodNull() { + assumeTrue(!SemconvStability.emitStableRpcSemconv()); + RpcRequest request = new RpcRequest(); when(getter.getService(request)).thenReturn("my.Service"); @@ -52,5 +62,27 @@ void methodNull() { 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"); + } + static class RpcRequest {} } From 869670a94ef8f555972865c7c6eb2631daad62d9 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 11 Feb 2026 17:16:31 +0100 Subject: [PATCH 02/11] pr review --- .../incubator/semconv/rpc/RpcAttributesExtractorTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 bd735e18e24e..03be85ebf459 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 @@ -16,6 +16,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.internal.SemconvStability; +import io.opentelemetry.semconv.incubating.RpcIncubatingAttributes; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -100,13 +101,13 @@ void clientPredefined() { // Old semconv keys (from RpcIncubatingAttributes) private static final AttributeKey RPC_SYSTEM = - io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM; + RpcIncubatingAttributes.RPC_SYSTEM; private static final AttributeKey RPC_SERVICE = - io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SERVICE; + RpcIncubatingAttributes.RPC_SERVICE; private static final AttributeKey RPC_METHOD = - io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_METHOD; + RpcIncubatingAttributes.RPC_METHOD; private static void testExtractor( AttributesExtractor, Void> extractor, @Nullable String originalMethod) { From 1a5cf070acc41d0ab5e8de7b74cf761487a100e5 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 11 Feb 2026 17:17:18 +0100 Subject: [PATCH 03/11] pr review --- .../semconv/rpc/RpcAttributesExtractorTest.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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 03be85ebf459..f973f337caed 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 @@ -100,14 +100,11 @@ void clientPredefined() { AttributeKey.stringKey("rpc.system.name"); // Old semconv keys (from RpcIncubatingAttributes) - private static final AttributeKey RPC_SYSTEM = - RpcIncubatingAttributes.RPC_SYSTEM; + private static final AttributeKey RPC_SYSTEM = RpcIncubatingAttributes.RPC_SYSTEM; - private static final AttributeKey RPC_SERVICE = - RpcIncubatingAttributes.RPC_SERVICE; + private static final AttributeKey RPC_SERVICE = RpcIncubatingAttributes.RPC_SERVICE; - private static final AttributeKey RPC_METHOD = - RpcIncubatingAttributes.RPC_METHOD; + private static final AttributeKey RPC_METHOD = RpcIncubatingAttributes.RPC_METHOD; private static void testExtractor( AttributesExtractor, Void> extractor, @Nullable String originalMethod) { From 287b28b1aa2711ff68e5105a333f85350e402b91 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 20 Feb 2026 13:42:57 +0100 Subject: [PATCH 04/11] Address review feedback: introduce getRpcSystemName, remove isPredefined MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add getRpcSystemName() to RpcAttributesGetter, deprecate getSystem() - Remove isPredefined() and RPC_METHOD_ORIGINAL — defer until use case exists - Getter implementations now responsible for returning stable/old system names - Remove system fallback in RpcSpanNameExtractor (system name is required) - Simplify test setup per reviewer suggestions Signed-off-by: Gregor Zeitlinger --- .../semconv/rpc/RpcAttributesGetter.java | 41 +++++++----------- .../rpc/RpcCommonAttributesExtractor.java | 21 ++-------- .../semconv/rpc/RpcSpanNameExtractor.java | 4 -- .../rpc/RpcAttributesExtractorTest.java | 42 +++++-------------- .../semconv/rpc/RpcSpanNameExtractorTest.java | 26 ++++-------- 5 files changed, 37 insertions(+), 97 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 748eac2cc160..eccca1f254d6 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 @@ -16,6 +16,21 @@ */ public interface RpcAttributesGetter { + /** + * Returns the stable semconv system name for the RPC framework (e.g. {@code "grpc"}, {@code + * "java_rmi"}, {@code "dotnet_wcf"}). + * + * @see rpc.system.name + * spec + */ + @Nullable + default String getRpcSystemName(REQUEST request) { + return null; + } + + /** @deprecated Use {@link #getRpcSystemName(REQUEST)}. To be removed in 3.0. */ + @Deprecated @Nullable String getSystem(REQUEST request); @@ -70,30 +85,4 @@ default String getErrorType( return null; } - /** - * 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}. - * - *

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/RpcCommonAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java index 9bbf2c69e620..80dddba40224 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,36 +29,23 @@ 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) { this.getter = getter; } - @SuppressWarnings("deprecation") // for getMethod() + @SuppressWarnings("deprecation") // for getSystem(), getMethod() @Override public final void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) { - String system = getter.getSystem(request); if (SemconvStability.emitStableRpcSemconv()) { - internalSet( - attributes, - RPC_SYSTEM_NAME, - system == null ? null : SemconvStability.stableRpcSystemName(system)); - String method = getter.getRpcMethod(request); - if (getter.isPredefined(request)) { - internalSet(attributes, RPC_METHOD, method); - } else { - internalSet(attributes, RPC_METHOD_ORIGINAL, method); - internalSet(attributes, RPC_METHOD, "_OTHER"); - } + internalSet(attributes, RPC_SYSTEM_NAME, getter.getRpcSystemName(request)); + internalSet(attributes, RPC_METHOD, getter.getRpcMethod(request)); } if (SemconvStability.emitOldRpcSemconv()) { - internalSet(attributes, RPC_SYSTEM, system); + internalSet(attributes, RPC_SYSTEM, getter.getSystem(request)); internalSet(attributes, RPC_SERVICE, getter.getService(request)); if (!SemconvStability.emitStableRpcSemconv()) { // only set old rpc.method on spans when there's no clash with stable rpc.method 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 fcd07fefd017..450feaabef47 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 @@ -34,10 +34,6 @@ public String extract(REQUEST request) { if (method != null) { return method; } - String system = getter.getSystem(request); - if (system != null) { - return system; - } return "RPC 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 f973f337caed..487bf665517e 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,7 +5,6 @@ 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 io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; import static org.assertj.core.api.Assertions.entry; @@ -29,10 +28,9 @@ class RpcAttributesExtractorTest { private static class TestGetter implements RpcAttributesGetter, Void> { - private final boolean predefined; - - private TestGetter(boolean predefined) { - this.predefined = predefined; + @Override + public String getRpcSystemName(Map request) { + return "test"; } @Override @@ -68,31 +66,16 @@ public String getErrorType( Map request, @Nullable Void response, @Nullable Throwable error) { return request.get("errorType"); } - - @Override - public boolean isPredefined(Map stringStringMap) { - return predefined; - } } @Test void server() { - testExtractor(RpcServerAttributesExtractor.create(new TestGetter(false)), "my.Service/Method"); - } - - @Test - void serverPredefined() { - testExtractor(RpcServerAttributesExtractor.create(new TestGetter(true)), null); + testExtractor(RpcServerAttributesExtractor.create(new TestGetter())); } @Test void client() { - testExtractor(RpcClientAttributesExtractor.create(new TestGetter(false)), "my.Service/Method"); - } - - @Test - void clientPredefined() { - testExtractor(RpcClientAttributesExtractor.create(new TestGetter(true)), null); + testExtractor(RpcClientAttributesExtractor.create(new TestGetter())); } // Stable semconv keys @@ -107,7 +90,7 @@ void clientPredefined() { private static final AttributeKey RPC_METHOD = RpcIncubatingAttributes.RPC_METHOD; private static void testExtractor( - AttributesExtractor, Void> extractor, @Nullable String originalMethod) { + AttributesExtractor, Void> extractor) { Map request = new HashMap<>(); request.put("service", "my.Service"); request.put("method", "Method"); @@ -122,12 +105,7 @@ private static void testExtractor( if (SemconvStability.emitStableRpcSemconv()) { expectedEntries.add(entry(RPC_SYSTEM_NAME, "test")); - 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")); - } + expectedEntries.add(entry(RPC_METHOD, "my.Service/Method")); } if (SemconvStability.emitOldRpcSemconv()) { @@ -156,7 +134,7 @@ void shouldExtractErrorType_getter() { request.put("errorType", "CANCELLED"); AttributesExtractor, Void> extractor = - RpcServerAttributesExtractor.create(new TestGetter(false)); + RpcServerAttributesExtractor.create(new TestGetter()); Context context = Context.root(); AttributesBuilder attributes = Attributes.builder(); @@ -175,7 +153,7 @@ void shouldExtractErrorType_exceptionClassName() { request.put("method", "Method"); AttributesExtractor, Void> extractor = - RpcServerAttributesExtractor.create(new TestGetter(false)); + RpcServerAttributesExtractor.create(new TestGetter()); Context context = Context.root(); AttributesBuilder attributes = Attributes.builder(); @@ -195,7 +173,7 @@ void shouldNotExtractErrorType_noError() { request.put("method", "Method"); AttributesExtractor, Void> extractor = - RpcServerAttributesExtractor.create(new TestGetter(false)); + RpcServerAttributesExtractor.create(new TestGetter()); Context context = Context.root(); AttributesBuilder attributes = Attributes.builder(); 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 503367ed0add..97bf4c47b660 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 @@ -7,6 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; @@ -26,15 +27,16 @@ class RpcSpanNameExtractorTest { void normal() { RpcRequest request = new RpcRequest(); + lenient().when(getter.getRpcMethod(request)).thenReturn("my.Service/Method"); + lenient().when(getter.getService(request)).thenReturn("my.Service"); + lenient().when(getter.getMethod(request)).thenReturn("Method"); + + SpanNameExtractor extractor = RpcSpanNameExtractor.create(getter); if (SemconvStability.emitStableRpcSemconv()) { - when(getter.getRpcMethod(request)).thenReturn("my.Service/Method"); + assertThat(extractor.extract(request)).isEqualTo("my.Service/Method"); } else { - when(getter.getService(request)).thenReturn("my.Service"); - when(getter.getMethod(request)).thenReturn("Method"); + assertThat(extractor.extract(request)).isEqualTo("my.Service/Method"); } - - SpanNameExtractor extractor = RpcSpanNameExtractor.create(getter); - assertThat(extractor.extract(request)).isEqualTo("my.Service/Method"); } @Test @@ -62,18 +64,6 @@ void methodNull() { 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()); From 69a898a53d729dbbf670b747841661d2c30c6d87 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 20 Feb 2026 19:43:10 +0100 Subject: [PATCH 05/11] Replace internalSet with attributes.put after AttributesExtractorUtil removal Signed-off-by: Gregor Zeitlinger --- .../incubator/semconv/rpc/RpcAttributesGetter.java | 5 +++-- .../semconv/rpc/RpcCommonAttributesExtractor.java | 13 ++++++------- .../semconv/rpc/RpcAttributesExtractorTest.java | 3 +-- .../semconv/rpc/RpcSpanNameExtractorTest.java | 6 +----- 4 files changed, 11 insertions(+), 16 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 eccca1f254d6..853816a66534 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 @@ -29,7 +29,9 @@ default String getRpcSystemName(REQUEST request) { return null; } - /** @deprecated Use {@link #getRpcSystemName(REQUEST)}. To be removed in 3.0. */ + /** + * @deprecated Use {@link #getRpcSystemName(REQUEST)}. To be removed in 3.0. + */ @Deprecated @Nullable String getSystem(REQUEST request); @@ -84,5 +86,4 @@ default String getErrorType( REQUEST request, @Nullable RESPONSE response, @Nullable Throwable error) { return 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 80dddba40224..e37d77f17a6d 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 @@ -5,7 +5,6 @@ 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; @@ -40,16 +39,16 @@ abstract class RpcCommonAttributesExtractor public final void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) { if (SemconvStability.emitStableRpcSemconv()) { - internalSet(attributes, RPC_SYSTEM_NAME, getter.getRpcSystemName(request)); - internalSet(attributes, RPC_METHOD, getter.getRpcMethod(request)); + attributes.put(RPC_SYSTEM_NAME, getter.getRpcSystemName(request)); + attributes.put(RPC_METHOD, getter.getRpcMethod(request)); } if (SemconvStability.emitOldRpcSemconv()) { - internalSet(attributes, RPC_SYSTEM, getter.getSystem(request)); - internalSet(attributes, RPC_SERVICE, getter.getService(request)); + attributes.put(RPC_SYSTEM, getter.getSystem(request)); + attributes.put(RPC_SERVICE, getter.getService(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)); + attributes.put(RPC_METHOD, getter.getMethod(request)); } } } @@ -67,7 +66,7 @@ public final void onEnd( if (errorType == null && error != null) { errorType = error.getClass().getName(); } - internalSet(attributes, ERROR_TYPE, errorType); + attributes.put(ERROR_TYPE, errorType); } } } 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 487bf665517e..3ca7734b2851 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 @@ -89,8 +89,7 @@ void client() { private static final AttributeKey RPC_METHOD = RpcIncubatingAttributes.RPC_METHOD; - private static void testExtractor( - AttributesExtractor, Void> extractor) { + private static void testExtractor(AttributesExtractor, Void> extractor) { Map request = new HashMap<>(); request.put("service", "my.Service"); request.put("method", "Method"); 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 97bf4c47b660..f60d70a441e4 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 @@ -32,11 +32,7 @@ void normal() { lenient().when(getter.getMethod(request)).thenReturn("Method"); SpanNameExtractor extractor = RpcSpanNameExtractor.create(getter); - if (SemconvStability.emitStableRpcSemconv()) { - assertThat(extractor.extract(request)).isEqualTo("my.Service/Method"); - } else { - assertThat(extractor.extract(request)).isEqualTo("my.Service/Method"); - } + assertThat(extractor.extract(request)).isEqualTo("my.Service/Method"); } @Test From 83ffb47d3afc0e61f9c912a318624f584a3d5de0 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 24 Feb 2026 08:09:12 +0100 Subject: [PATCH 06/11] Address review feedback: system name fallback, TODO comments, javadoc fixes Signed-off-by: Gregor Zeitlinger --- .../incubator/semconv/rpc/RpcAttributesGetter.java | 8 +++++--- .../semconv/rpc/RpcCommonAttributesExtractor.java | 2 +- .../semconv/rpc/RpcSpanNameExtractor.java | 5 +++++ .../semconv/rpc/RpcSpanNameExtractorTest.java | 14 +++++++++++++- 4 files changed, 24 insertions(+), 5 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 853816a66534..fd5d25359155 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 @@ -64,6 +64,7 @@ default Long getResponseSize(REQUEST request) { * method is unavailable */ @Nullable + // TODO remove default implementation default String getRpcMethod(REQUEST request) { return null; } @@ -73,15 +74,16 @@ default String getRpcMethod(REQUEST request) { * *

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. + *

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

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} + *

Examples: {@code CANCELLED}, {@code UNKNOWN}, {@code -32602} */ @Nullable + // TODO remove default implementation default String getErrorType( REQUEST request, @Nullable RESPONSE response, @Nullable Throwable error) { return 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 e37d77f17a6d..5f5559f30df2 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 @@ -62,7 +62,7 @@ public final void onEnd( @Nullable Throwable error) { if (SemconvStability.emitStableRpcSemconv()) { String errorType = getter.getErrorType(request, response, error); - // fall back to exception class name & _OTHER + // fall back to exception class name if (errorType == null && error != null) { errorType = error.getClass().getName(); } 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 450feaabef47..8364c349bfed 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 @@ -34,6 +34,11 @@ public String extract(REQUEST request) { if (method != null) { return method; } + // fall back to rpc.system.name + String systemName = getter.getRpcSystemName(request); + if (systemName != null) { + return systemName; + } return "RPC 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 f60d70a441e4..db2dbcfdea6f 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 @@ -61,7 +61,19 @@ void methodNull() { } @Test - void rpcMethodNull() { + void rpcMethodNull_fallsBackToSystemName() { + assumeTrue(SemconvStability.emitStableRpcSemconv()); + + RpcRequest request = new RpcRequest(); + + when(getter.getRpcSystemName(request)).thenReturn("grpc"); + + SpanNameExtractor extractor = RpcSpanNameExtractor.create(getter); + assertThat(extractor.extract(request)).isEqualTo("grpc"); + } + + @Test + void rpcMethodAndSystemNameNull() { assumeTrue(SemconvStability.emitStableRpcSemconv()); RpcRequest request = new RpcRequest(); From 7b59479b0cdae3dd68a62fc3f6a96bd175ec106c Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 24 Feb 2026 17:44:04 +0100 Subject: [PATCH 07/11] Fix spotless: use static imports for SemconvStability methods Signed-off-by: Gregor Zeitlinger --- .../semconv/rpc/RpcAttributesGetter.java | 4 ++-- .../semconv/rpc/RpcCommonAttributesExtractor.java | 11 ++++++----- .../semconv/rpc/RpcSpanNameExtractor.java | 5 +++-- .../semconv/rpc/RpcAttributesExtractorTest.java | 15 ++++++++------- .../semconv/rpc/RpcSpanNameExtractorTest.java | 10 +++++----- 5 files changed, 24 insertions(+), 21 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 fd5d25359155..e92b6742414c 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 @@ -74,8 +74,8 @@ default String getRpcMethod(REQUEST request) { * *

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

If this method returns {@code null}, the exception class name - * will be used as error type if one was thrown. + *

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

The cardinality of the error type should be low. The instrumentations implementing this * method are recommended to document the custom values they support. 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 5f5559f30df2..724738af2196 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 @@ -5,13 +5,14 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.rpc; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldRpcSemconv; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableRpcSemconv; 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 @@ -38,15 +39,15 @@ abstract class RpcCommonAttributesExtractor @Override public final void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) { - if (SemconvStability.emitStableRpcSemconv()) { + if (emitStableRpcSemconv()) { attributes.put(RPC_SYSTEM_NAME, getter.getRpcSystemName(request)); attributes.put(RPC_METHOD, getter.getRpcMethod(request)); } - if (SemconvStability.emitOldRpcSemconv()) { + if (emitOldRpcSemconv()) { attributes.put(RPC_SYSTEM, getter.getSystem(request)); attributes.put(RPC_SERVICE, getter.getService(request)); - if (!SemconvStability.emitStableRpcSemconv()) { + if (!emitStableRpcSemconv()) { // only set old rpc.method on spans when there's no clash with stable rpc.method attributes.put(RPC_METHOD, getter.getMethod(request)); } @@ -60,7 +61,7 @@ public final void onEnd( REQUEST request, @Nullable RESPONSE response, @Nullable Throwable error) { - if (SemconvStability.emitStableRpcSemconv()) { + if (emitStableRpcSemconv()) { String errorType = getter.getErrorType(request, response, error); // fall back to exception class name if (errorType == null && error != null) { 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 8364c349bfed..a9d1b6b380e0 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 @@ -5,8 +5,9 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.rpc; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableRpcSemconv; + 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 { @@ -29,7 +30,7 @@ private RpcSpanNameExtractor(RpcAttributesGetter getter) { @SuppressWarnings("deprecation") // for getMethod() @Override public String extract(REQUEST request) { - if (SemconvStability.emitStableRpcSemconv()) { + if (emitStableRpcSemconv()) { String method = getter.getRpcMethod(request); if (method != null) { return 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 3ca7734b2851..f26960f00419 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,8 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.rpc; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldRpcSemconv; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableRpcSemconv; 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; @@ -14,7 +16,6 @@ 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 io.opentelemetry.semconv.incubating.RpcIncubatingAttributes; import java.util.ArrayList; import java.util.HashMap; @@ -102,15 +103,15 @@ private static void testExtractor(AttributesExtractor, Void> // Build expected entries list based on semconv mode List, ?>> expectedEntries = new ArrayList<>(); - if (SemconvStability.emitStableRpcSemconv()) { + if (emitStableRpcSemconv()) { expectedEntries.add(entry(RPC_SYSTEM_NAME, "test")); expectedEntries.add(entry(RPC_METHOD, "my.Service/Method")); } - if (SemconvStability.emitOldRpcSemconv()) { + if (emitOldRpcSemconv()) { expectedEntries.add(entry(RPC_SYSTEM, "test")); expectedEntries.add(entry(RPC_SERVICE, "my.Service")); - if (!SemconvStability.emitStableRpcSemconv()) { + if (!emitStableRpcSemconv()) { expectedEntries.add(entry(RPC_METHOD, "Method")); } } @@ -140,7 +141,7 @@ void shouldExtractErrorType_getter() { extractor.onStart(attributes, context, request); extractor.onEnd(attributes, context, request, null, null); - if (SemconvStability.emitStableRpcSemconv()) { + if (emitStableRpcSemconv()) { assertThat(attributes.build()).containsEntry(ERROR_TYPE, "CANCELLED"); } } @@ -159,7 +160,7 @@ void shouldExtractErrorType_exceptionClassName() { extractor.onStart(attributes, context, request); extractor.onEnd(attributes, context, request, null, new IllegalArgumentException()); - if (SemconvStability.emitStableRpcSemconv()) { + if (emitStableRpcSemconv()) { assertThat(attributes.build()) .containsEntry(ERROR_TYPE, "java.lang.IllegalArgumentException"); } @@ -179,7 +180,7 @@ void shouldNotExtractErrorType_noError() { extractor.onStart(attributes, context, request); extractor.onEnd(attributes, context, request, null, null); - if (SemconvStability.emitStableRpcSemconv()) { + if (emitStableRpcSemconv()) { assertThat(attributes.build()).doesNotContainKey(ERROR_TYPE); } } 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 db2dbcfdea6f..8b533d50e5e7 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 @@ -5,13 +5,13 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.rpc; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableRpcSemconv; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.mockito.Mockito.lenient; 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; @@ -38,7 +38,7 @@ void normal() { @Test @SuppressWarnings("deprecation") // testing deprecated method void serviceNull() { - assumeTrue(!SemconvStability.emitStableRpcSemconv()); + assumeTrue(!emitStableRpcSemconv()); RpcRequest request = new RpcRequest(); @@ -50,7 +50,7 @@ void serviceNull() { @Test void methodNull() { - assumeTrue(!SemconvStability.emitStableRpcSemconv()); + assumeTrue(!emitStableRpcSemconv()); RpcRequest request = new RpcRequest(); @@ -62,7 +62,7 @@ void methodNull() { @Test void rpcMethodNull_fallsBackToSystemName() { - assumeTrue(SemconvStability.emitStableRpcSemconv()); + assumeTrue(emitStableRpcSemconv()); RpcRequest request = new RpcRequest(); @@ -74,7 +74,7 @@ void rpcMethodNull_fallsBackToSystemName() { @Test void rpcMethodAndSystemNameNull() { - assumeTrue(SemconvStability.emitStableRpcSemconv()); + assumeTrue(emitStableRpcSemconv()); RpcRequest request = new RpcRequest(); From 2622a3ef09b50f3dd9968b2baf545860e113b3a5 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 25 Feb 2026 14:18:29 +0100 Subject: [PATCH 08/11] Fix spotless: use static imports for RpcIncubatingAttributes constants Signed-off-by: Gregor Zeitlinger --- .../semconv/rpc/RpcAttributesExtractorTest.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) 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 f26960f00419..dde44959a5e0 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 @@ -9,6 +9,9 @@ import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableRpcSemconv; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; +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 static org.assertj.core.api.Assertions.entry; import io.opentelemetry.api.common.AttributeKey; @@ -16,7 +19,6 @@ 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 java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -83,13 +85,6 @@ void client() { private static final AttributeKey RPC_SYSTEM_NAME = AttributeKey.stringKey("rpc.system.name"); - // Old semconv keys (from RpcIncubatingAttributes) - private static final AttributeKey RPC_SYSTEM = RpcIncubatingAttributes.RPC_SYSTEM; - - private static final AttributeKey RPC_SERVICE = RpcIncubatingAttributes.RPC_SERVICE; - - private static final AttributeKey RPC_METHOD = RpcIncubatingAttributes.RPC_METHOD; - private static void testExtractor(AttributesExtractor, Void> extractor) { Map request = new HashMap<>(); request.put("service", "my.Service"); From efc5ca83a99f82ef740579e5d3b304ba23d864bd Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 25 Feb 2026 17:56:08 +0100 Subject: [PATCH 09/11] Make getRpcSystemName non-nullable, default to getSystem() Signed-off-by: Gregor Zeitlinger --- .../api/incubator/semconv/rpc/RpcAttributesGetter.java | 5 ++--- .../api/incubator/semconv/rpc/RpcSpanNameExtractor.java | 6 +----- .../incubator/semconv/rpc/RpcSpanNameExtractorTest.java | 7 +++++-- 3 files changed, 8 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 e92b6742414c..397cd3711fcf 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 @@ -24,16 +24,15 @@ public interface RpcAttributesGetter { * href="https://opentelemetry.io/docs/specs/semconv/attributes-registry/rpc/">rpc.system.name * spec */ - @Nullable + @SuppressWarnings("deprecation") default String getRpcSystemName(REQUEST request) { - return null; + return getSystem(request); } /** * @deprecated Use {@link #getRpcSystemName(REQUEST)}. To be removed in 3.0. */ @Deprecated - @Nullable String getSystem(REQUEST request); @Nullable 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 a9d1b6b380e0..45ef81b80bd9 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 @@ -36,11 +36,7 @@ public String extract(REQUEST request) { return method; } // fall back to rpc.system.name - String systemName = getter.getRpcSystemName(request); - if (systemName != null) { - return systemName; - } - return "RPC request"; + return getter.getRpcSystemName(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 8b533d50e5e7..be36eafc19a4 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 @@ -73,13 +73,16 @@ void rpcMethodNull_fallsBackToSystemName() { } @Test - void rpcMethodAndSystemNameNull() { + @SuppressWarnings("deprecation") // testing deprecated method fallback + void rpcMethodNull_fallsBackToSystemName_viaGetSystem() { assumeTrue(emitStableRpcSemconv()); RpcRequest request = new RpcRequest(); + when(getter.getSystem(request)).thenReturn("grpc"); + SpanNameExtractor extractor = RpcSpanNameExtractor.create(getter); - assertThat(extractor.extract(request)).isEqualTo("RPC request"); + assertThat(extractor.extract(request)).isEqualTo("grpc"); } static class RpcRequest {} From 4ede0fe3d53be210910d167ba472a15ece080ac5 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 25 Feb 2026 11:02:04 -0800 Subject: [PATCH 10/11] Fix RpcSpanNameExtractorTest to use thenCallRealMethod for default interface method --- .../api/incubator/semconv/rpc/RpcSpanNameExtractorTest.java | 1 + 1 file changed, 1 insertion(+) 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 be36eafc19a4..a411d58280c2 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 @@ -79,6 +79,7 @@ void rpcMethodNull_fallsBackToSystemName_viaGetSystem() { RpcRequest request = new RpcRequest(); + when(getter.getRpcSystemName(request)).thenCallRealMethod(); when(getter.getSystem(request)).thenReturn("grpc"); SpanNameExtractor extractor = RpcSpanNameExtractor.create(getter); From 4183e632d0a10ea69c383e124c3cbbefa8f39dbf Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 25 Feb 2026 11:37:18 -0800 Subject: [PATCH 11/11] update --- .../semconv/rpc/RpcSpanNameExtractorTest.java | 10 ++++++---- 1 file changed, 6 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 a411d58280c2..070614c25a6f 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 @@ -8,7 +8,6 @@ import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableRpcSemconv; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; @@ -27,9 +26,12 @@ class RpcSpanNameExtractorTest { void normal() { RpcRequest request = new RpcRequest(); - lenient().when(getter.getRpcMethod(request)).thenReturn("my.Service/Method"); - lenient().when(getter.getService(request)).thenReturn("my.Service"); - lenient().when(getter.getMethod(request)).thenReturn("Method"); + if (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");