diff --git a/CHANGELOG.md b/CHANGELOG.md index b5ca7b9e69b8..b65ab8bcf090 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +### ⚠️ Breaking changes to non-stable APIs + +- `GrpcRequest.getMethod()` now returns `@Nullable` to support unregistered-service requests where + no `MethodDescriptor` is available + ([#16214](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/16214)) + ## Version 2.25.0 (2026-02-13) ### ⚠️ Breaking changes to non-stable APIs 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 397cd3711fcf..8d821179afa4 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 @@ -68,6 +68,15 @@ default String getRpcMethod(REQUEST request) { return null; } + /** + * Returns the original method name when the method reported via {@link #getRpcMethod(REQUEST)} is + * set to {@code _OTHER} because the method is not recognized by the RPC framework. + */ + @Nullable + default String getRpcMethodOriginal(REQUEST request) { + return null; + } + /** * Returns a description of a class of error the operation ended with. * 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 724738af2196..84c849fc8cda 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 @@ -19,6 +19,8 @@ abstract class RpcCommonAttributesExtractor implements AttributesExtractor { static final AttributeKey RPC_METHOD = AttributeKey.stringKey("rpc.method"); + static final AttributeKey RPC_METHOD_ORIGINAL = + AttributeKey.stringKey("rpc.method_original"); // Stable semconv keys static final AttributeKey RPC_SYSTEM_NAME = AttributeKey.stringKey("rpc.system.name"); @@ -42,6 +44,7 @@ public final void onStart(AttributesBuilder attributes, Context parentContext, R if (emitStableRpcSemconv()) { attributes.put(RPC_SYSTEM_NAME, getter.getRpcSystemName(request)); attributes.put(RPC_METHOD, getter.getRpcMethod(request)); + attributes.put(RPC_METHOD_ORIGINAL, getter.getRpcMethodOriginal(request)); } if (emitOldRpcSemconv()) { diff --git a/instrumentation/armeria/armeria-grpc-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcServiceBuilderInstrumentation.java b/instrumentation/armeria/armeria-grpc-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcServiceBuilderInstrumentation.java index 8c97742ade8e..48921b203f2f 100644 --- a/instrumentation/armeria/armeria-grpc-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcServiceBuilderInstrumentation.java +++ b/instrumentation/armeria/armeria-grpc-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcServiceBuilderInstrumentation.java @@ -12,6 +12,7 @@ import com.linecorp.armeria.server.grpc.GrpcServiceBuilder; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.grpc.v1_6.GrpcTelemetry; +import io.opentelemetry.instrumentation.grpc.v1_6.internal.Internal; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import net.bytebuddy.asm.Advice; @@ -36,7 +37,8 @@ public static class BuildAdvice { @Advice.OnMethodEnter public static void onEnter(@Advice.This GrpcServiceBuilder builder) { - builder.intercept(GrpcTelemetry.create(GlobalOpenTelemetry.get()).createServerInterceptor()); + GrpcTelemetry telemetry = GrpcTelemetry.create(GlobalOpenTelemetry.get()); + builder.intercept(Internal.createServerInterceptor(telemetry)); } } } diff --git a/instrumentation/grpc-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grpc/v1_6/GrpcServerBuilderInstrumentation.java b/instrumentation/grpc-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grpc/v1_6/GrpcServerBuilderInstrumentation.java index 022f48885fd6..a35bf76a6c73 100644 --- a/instrumentation/grpc-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grpc/v1_6/GrpcServerBuilderInstrumentation.java +++ b/instrumentation/grpc-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grpc/v1_6/GrpcServerBuilderInstrumentation.java @@ -50,7 +50,7 @@ public static CallDepth onEnter(@Advice.This ServerBuilder serverBuilder) { return callDepth; } if (!Boolean.TRUE.equals(SERVER_BUILDER_INSTRUMENTED.get(serverBuilder))) { - serverBuilder.intercept(GrpcSingletons.SERVER_INTERCEPTOR); + GrpcSingletons.configureServerBuilder(serverBuilder); SERVER_BUILDER_INSTRUMENTED.set(serverBuilder, true); } return callDepth; diff --git a/instrumentation/grpc-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grpc/v1_6/GrpcSingletons.java b/instrumentation/grpc-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grpc/v1_6/GrpcSingletons.java index a28f136fa76b..2ce252d56328 100644 --- a/instrumentation/grpc-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grpc/v1_6/GrpcSingletons.java +++ b/instrumentation/grpc-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grpc/v1_6/GrpcSingletons.java @@ -11,7 +11,6 @@ import io.grpc.Context; import io.grpc.ManagedChannelBuilder; import io.grpc.ServerBuilder; -import io.grpc.ServerInterceptor; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import io.opentelemetry.instrumentation.api.incubator.config.internal.DeclarativeConfigUtil; @@ -33,7 +32,7 @@ public final class GrpcSingletons { public static final ClientInterceptor CLIENT_INTERCEPTOR; - public static final ServerInterceptor SERVER_INTERCEPTOR; + private static final GrpcTelemetry GRPC_TELEMETRY; private static final AtomicReference STORAGE_REFERENCE = new AtomicReference<>(); @@ -56,7 +55,7 @@ public final class GrpcSingletons { .get("server") .getScalarList("request", String.class, emptyList()); - GrpcTelemetry telemetry = + GRPC_TELEMETRY = GrpcTelemetry.builder(GlobalOpenTelemetry.get()) .setEmitMessageEvents(emitMessageEvents) .setCaptureExperimentalSpanAttributes(experimentalSpanAttributes) @@ -64,8 +63,7 @@ public final class GrpcSingletons { .setCapturedServerRequestMetadata(serverRequestMetadata) .build(); - CLIENT_INTERCEPTOR = telemetry.createClientInterceptor(); - SERVER_INTERCEPTOR = telemetry.createServerInterceptor(); + CLIENT_INTERCEPTOR = GRPC_TELEMETRY.createClientInterceptor(); } public static Context.Storage getStorage() { @@ -77,5 +75,9 @@ public static Context.Storage setStorage(Context.Storage storage) { return getStorage(); } + public static void configureServerBuilder(ServerBuilder serverBuilder) { + GRPC_TELEMETRY.configureServerBuilder(serverBuilder); + } + private GrpcSingletons() {} } diff --git a/instrumentation/grpc-1.6/library/README.md b/instrumentation/grpc-1.6/library/README.md index 09674fbb97a5..513baf22cf7e 100644 --- a/instrumentation/grpc-1.6/library/README.md +++ b/instrumentation/grpc-1.6/library/README.md @@ -37,9 +37,9 @@ void configureClientInterceptor(OpenTelemetry openTelemetry, NettyChannelBuilder nettyChannelBuilder.intercept(grpcTelemetry.createClientInterceptor()); } -// For server-side, attatch the interceptor to your service. -ServerServiceDefinition configureServerInterceptor(OpenTelemetry openTelemetry, ServerServiceDefinition serviceDefinition) { +// For server-side, configure the server builder. +void configureServer(OpenTelemetry openTelemetry, ServerBuilder serverBuilder) { GrpcTelemetry grpcTelemetry = GrpcTelemetry.create(openTelemetry); - return ServerInterceptors.intercept(serviceDefinition, grpcTelemetry.createServerInterceptor()); + grpcTelemetry.configureServerBuilder(serverBuilder); } ``` diff --git a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRequest.java b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRequest.java index 926799191bb2..9d22bb58d281 100644 --- a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRequest.java +++ b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRequest.java @@ -12,7 +12,9 @@ public final class GrpcRequest { - private final MethodDescriptor method; + @Nullable private volatile MethodDescriptor method; + private final String fullMethodName; + @Nullable private final String originalFullMethodName; @Nullable private volatile Metadata metadata; @@ -29,11 +31,19 @@ public final class GrpcRequest { @Nullable SocketAddress peerSocketAddress, @Nullable String authority) { this.method = method; + this.fullMethodName = method.getFullMethodName(); + this.originalFullMethodName = null; this.metadata = metadata; this.peerSocketAddress = peerSocketAddress; setLogicalAddress(authority); } + GrpcRequest(String fullMethodName, @Nullable String originalFullMethodName, Metadata metadata) { + this.fullMethodName = fullMethodName; + this.originalFullMethodName = originalFullMethodName; + this.metadata = metadata; + } + private void setLogicalAddress(@Nullable String authority) { if (authority == null) { return; @@ -51,10 +61,20 @@ private void setLogicalAddress(@Nullable String authority) { } } + @Nullable public MethodDescriptor getMethod() { return method; } + String getFullMethodName() { + return fullMethodName; + } + + @Nullable + String getOriginalFullMethodName() { + return originalFullMethodName; + } + @Nullable public Metadata getMetadata() { return metadata; 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 99f5d4fba3e1..3e87df9bdf3d 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 @@ -26,7 +26,7 @@ public String getSystem(GrpcRequest request) { @Override @Nullable public String getService(GrpcRequest request) { - String fullMethodName = request.getMethod().getFullMethodName(); + String fullMethodName = request.getFullMethodName(); int slashIndex = fullMethodName.lastIndexOf('/'); if (slashIndex == -1) { return null; @@ -38,7 +38,7 @@ public String getService(GrpcRequest request) { @Override @Nullable public String getMethod(GrpcRequest request) { - String fullMethodName = request.getMethod().getFullMethodName(); + String fullMethodName = request.getFullMethodName(); int slashIndex = fullMethodName.lastIndexOf('/'); if (slashIndex == -1) { return null; @@ -47,8 +47,9 @@ public String getMethod(GrpcRequest request) { } @Override + @Nullable public String getRpcMethod(GrpcRequest request) { - return request.getMethod().getFullMethodName(); + return request.getFullMethodName(); } @Override @@ -63,6 +64,12 @@ public Long getResponseSize(GrpcRequest request) { return request.getResponseSize(); } + @Override + @Nullable + public String getRpcMethodOriginal(GrpcRequest request) { + return request.getOriginalFullMethodName(); + } + List metadataValue(GrpcRequest request, String key) { if (request.getMetadata() == null) { return emptyList(); diff --git a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcSpanNameExtractor.java b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcSpanNameExtractor.java index 35a08ba0fb16..c44310c5e6ee 100644 --- a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcSpanNameExtractor.java +++ b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcSpanNameExtractor.java @@ -11,6 +11,6 @@ final class GrpcSpanNameExtractor implements SpanNameExtractor { @Override public String extract(GrpcRequest request) { - return request.getMethod().getFullMethodName(); + return request.getFullMethodName(); } } diff --git a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcTelemetry.java b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcTelemetry.java index d4c97c451144..34f2f66470c0 100644 --- a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcTelemetry.java +++ b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcTelemetry.java @@ -6,15 +6,21 @@ package io.opentelemetry.instrumentation.grpc.v1_6; import io.grpc.ClientInterceptor; +import io.grpc.ServerBuilder; import io.grpc.ServerInterceptor; import io.grpc.Status; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.grpc.v1_6.internal.Internal; /** Entrypoint for instrumenting gRPC servers or clients. */ public final class GrpcTelemetry { + static { + Internal.setServerInterceptorFactory(GrpcTelemetry::buildServerInterceptor); + } + /** Returns a new {@link GrpcTelemetry} configured with the given {@link OpenTelemetry}. */ public static GrpcTelemetry create(OpenTelemetry openTelemetry) { return builder(openTelemetry).build(); @@ -53,11 +59,30 @@ public ClientInterceptor createClientInterceptor() { clientInstrumenter, propagators, captureExperimentalSpanAttributes, emitMessageEvents); } + /** + * Configures a {@link ServerBuilder} with both the server interceptor and the stream tracer + * factory. The interceptor handles registered service methods, while the stream tracer factory + * creates spans for requests to unregistered services that are not seen by server interceptors. + */ + public void configureServerBuilder(ServerBuilder serverBuilder) { + serverBuilder.intercept(buildServerInterceptor()); + serverBuilder.addStreamTracerFactory( + new TracingServerStreamTracerFactory(serverInstrumenter, propagators)); + } + /** * Returns a new {@link ServerInterceptor} for use with methods like {@link * io.grpc.ServerBuilder#intercept(ServerInterceptor)}. + * + * @deprecated Use {@link #configureServerBuilder(ServerBuilder)} instead, which also registers + * the stream tracer factory needed to capture requests to unregistered services. */ + @Deprecated public ServerInterceptor createServerInterceptor() { + return buildServerInterceptor(); + } + + ServerInterceptor buildServerInterceptor() { return new TracingServerInterceptor( serverInstrumenter, captureExperimentalSpanAttributes, emitMessageEvents); } diff --git a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/TracingServerInterceptor.java b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/TracingServerInterceptor.java index 5440d9ba34db..2cc9b80b7a1f 100644 --- a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/TracingServerInterceptor.java +++ b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/TracingServerInterceptor.java @@ -70,6 +70,14 @@ public ServerCall.Listener interceptCall( // field. authority = GrpcAuthorityStorage.getAuthority(call); } + + // If a ServerStreamTracer is active, mark it as handled so it won't create a span for this + // request in streamClosed(). + TracingServerStreamTracer streamTracer = TracingServerStreamTracer.STREAM_TRACER_KEY.get(); + if (streamTracer != null) { + streamTracer.markInterceptorHandled(); + } + GrpcRequest request = new GrpcRequest( call.getMethodDescriptor(), diff --git a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/TracingServerStreamTracer.java b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/TracingServerStreamTracer.java new file mode 100644 index 000000000000..b77b0b3dd07e --- /dev/null +++ b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/TracingServerStreamTracer.java @@ -0,0 +1,98 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.grpc.v1_6; + +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableRpcSemconv; + +import io.grpc.Grpc; +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerStreamTracer; +import io.grpc.Status; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; +import java.net.SocketAddress; +import java.time.Instant; +import javax.annotation.Nullable; + +/** + * A {@link ServerStreamTracer} that detects whether a gRPC server request was handled by the {@link + * TracingServerInterceptor}. If the interceptor does not fire (unregistered method), {@link + * #streamClosed(Status)} creates a span for the unhandled request. + */ +final class TracingServerStreamTracer extends ServerStreamTracer { + + static final io.grpc.Context.Key STREAM_TRACER_KEY = + io.grpc.Context.key("otel-grpc-stream-tracer"); + + private static final String UNKNOWN_METHOD_SPAN_NAME = "_OTHER"; + + private final Instrumenter instrumenter; + private final ContextPropagators propagators; + private final String fullMethodName; + private final Metadata headers; + private final Context parentContext; + private final Instant startTime; + + private volatile boolean interceptorHandled; + @Nullable private volatile SocketAddress peerAddress; + + TracingServerStreamTracer( + Instrumenter instrumenter, + ContextPropagators propagators, + String fullMethodName, + Metadata headers, + Context parentContext) { + this.instrumenter = instrumenter; + this.propagators = propagators; + this.fullMethodName = fullMethodName; + this.headers = headers; + this.parentContext = parentContext; + this.startTime = Instant.now(); + } + + void markInterceptorHandled() { + interceptorHandled = true; + } + + @Override + public io.grpc.Context filterContext(io.grpc.Context context) { + return context.withValue(STREAM_TRACER_KEY, this); + } + + @Override + public void serverCallStarted(ServerCall call) { + if (peerAddress == null) { + SocketAddress addr = call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR); + if (addr != null) { + peerAddress = addr; + } + } + } + + @Override + public void streamClosed(Status status) { + if (interceptorHandled || !emitStableRpcSemconv()) { + return; + } + // Interceptor did not fire — this is an unregistered method + GrpcRequest request = new GrpcRequest(UNKNOWN_METHOD_SPAN_NAME, fullMethodName, headers); + if (peerAddress != null) { + request.setPeerSocketAddress(peerAddress); + } + // Extract trace context from incoming headers (e.g. W3C traceparent) + Context extracted = + propagators + .getTextMapPropagator() + .extract(parentContext, request, GrpcRequestGetter.INSTANCE); + if (instrumenter.shouldStart(extracted, request)) { + InstrumenterUtil.startAndEnd( + instrumenter, extracted, request, status, status.getCause(), startTime, Instant.now()); + } + } +} diff --git a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/TracingServerStreamTracerFactory.java b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/TracingServerStreamTracerFactory.java new file mode 100644 index 000000000000..e79cdd3fb81b --- /dev/null +++ b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/TracingServerStreamTracerFactory.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.grpc.v1_6; + +import io.grpc.Metadata; +import io.grpc.ServerStreamTracer; +import io.grpc.Status; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; + +final class TracingServerStreamTracerFactory extends ServerStreamTracer.Factory { + + private final Instrumenter instrumenter; + private final ContextPropagators propagators; + + TracingServerStreamTracerFactory( + Instrumenter instrumenter, ContextPropagators propagators) { + this.instrumenter = instrumenter; + this.propagators = propagators; + } + + @Override + public ServerStreamTracer newServerStreamTracer(String fullMethodName, Metadata headers) { + return new TracingServerStreamTracer( + instrumenter, propagators, fullMethodName, headers, Context.current()); + } +} diff --git a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/internal/Internal.java b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/internal/Internal.java new file mode 100644 index 000000000000..6bc096798134 --- /dev/null +++ b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/internal/Internal.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.grpc.v1_6.internal; + +import io.grpc.ServerInterceptor; +import io.opentelemetry.instrumentation.grpc.v1_6.GrpcTelemetry; +import java.util.function.Function; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class Internal { + + private static volatile Function serverInterceptorFactory; + + public static void setServerInterceptorFactory( + Function factory) { + serverInterceptorFactory = factory; + } + + public static ServerInterceptor createServerInterceptor(GrpcTelemetry telemetry) { + return serverInterceptorFactory.apply(telemetry); + } + + private Internal() {} +} diff --git a/instrumentation/grpc-1.6/library/src/test/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcStreamingTest.java b/instrumentation/grpc-1.6/library/src/test/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcStreamingTest.java index f7323d13e043..ecd04f307b6b 100644 --- a/instrumentation/grpc-1.6/library/src/test/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcStreamingTest.java +++ b/instrumentation/grpc-1.6/library/src/test/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcStreamingTest.java @@ -18,8 +18,8 @@ class GrpcStreamingTest extends AbstractGrpcStreamingTest { @Override protected ServerBuilder configureServer(ServerBuilder server) { - return server.intercept( - GrpcTelemetry.create(testing.getOpenTelemetry()).createServerInterceptor()); + GrpcTelemetry.create(testing.getOpenTelemetry()).configureServerBuilder(server); + return server; } @Override diff --git a/instrumentation/grpc-1.6/library/src/test/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcTest.java b/instrumentation/grpc-1.6/library/src/test/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcTest.java index d24b5c170c02..1789e634651a 100644 --- a/instrumentation/grpc-1.6/library/src/test/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcTest.java +++ b/instrumentation/grpc-1.6/library/src/test/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcTest.java @@ -44,11 +44,12 @@ class GrpcTest extends AbstractGrpcTest { @Override protected ServerBuilder configureServer(ServerBuilder server) { - return server.intercept( + GrpcTelemetry telemetry = GrpcTelemetry.builder(testing.getOpenTelemetry()) .setCapturedServerRequestMetadata(singletonList(SERVER_REQUEST_METADATA_KEY)) - .build() - .createServerInterceptor()); + .build(); + telemetry.configureServerBuilder(server); + return server; } @Override @@ -84,17 +85,14 @@ public void sayHello( } }; - Server server = - ServerBuilder.forPort(0) - .addService(greeter) - .intercept( - GrpcTelemetry.builder(testing.getOpenTelemetry()) - .addAttributesExtractor(new CustomAttributesExtractor()) - .addServerAttributeExtractor(new CustomAttributesExtractorV2("serverSideValue")) - .build() - .createServerInterceptor()) - .build() - .start(); + GrpcTelemetry serverTelemetry = + GrpcTelemetry.builder(testing.getOpenTelemetry()) + .addAttributesExtractor(new CustomAttributesExtractor()) + .addServerAttributeExtractor(new CustomAttributesExtractorV2("serverSideValue")) + .build(); + ServerBuilder serverBuilder = ServerBuilder.forPort(0).addService(greeter); + serverTelemetry.configureServerBuilder(serverBuilder); + Server server = serverBuilder.build().start(); ManagedChannel channel = createChannel( diff --git a/instrumentation/grpc-1.6/testing/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/AbstractGrpcTest.java b/instrumentation/grpc-1.6/testing/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/AbstractGrpcTest.java index 493936506493..13e0b751f3c8 100644 --- a/instrumentation/grpc-1.6/testing/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/AbstractGrpcTest.java +++ b/instrumentation/grpc-1.6/testing/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/AbstractGrpcTest.java @@ -23,6 +23,7 @@ import static io.opentelemetry.semconv.incubating.MessageIncubatingAttributes.MESSAGE_TYPE; import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_GRPC_STATUS_CODE; import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_METHOD; +import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_METHOD_ORIGINAL; import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_RESPONSE_STATUS_CODE; import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SERVICE; import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM; @@ -68,6 +69,7 @@ import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.util.ThrowingRunnable; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; import io.opentelemetry.sdk.trace.data.StatusData; import java.util.ArrayList; import java.util.List; @@ -78,6 +80,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import java.util.stream.Stream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -809,6 +812,82 @@ private static Stream provideErrorArguments() { arguments(Status.NOT_FOUND.withDescription("some description"))); } + @Test + void unknownService() throws Exception { + // Create a server without GreeterService registered + Server server = configureServer(ServerBuilder.forPort(0)).build().start(); + ManagedChannel channel = createChannel(server); + closer.add(() -> channel.shutdownNow().awaitTermination(10, SECONDS)); + closer.add(() -> server.shutdownNow().awaitTermination()); + + GreeterGrpc.GreeterBlockingStub client = GreeterGrpc.newBlockingStub(channel); + + Helloworld.Request request = Helloworld.Request.newBuilder().setName("test").build(); + assertThatThrownBy(() -> client.sayHello(request)) + .isInstanceOfSatisfying( + StatusRuntimeException.class, + t -> assertThat(t.getStatus().getCode()).isEqualTo(Status.Code.UNIMPLEMENTED)); + + List> spanAsserts = new ArrayList<>(); + spanAsserts.add( + span -> + span.hasName("example.Greeter/SayHello") + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasStatus(StatusData.error()) + .hasAttributesSatisfyingExactly( + addExtraClientAttributes( + experimentalSatisfies( + GRPC_RECEIVED_MESSAGE_COUNT, + v -> assertThat(v).isEqualTo(0)), + experimentalSatisfies( + GRPC_SENT_MESSAGE_COUNT, + v -> assertThat(v).isGreaterThan(0)), + equalTo(RPC_SYSTEM, emitOldRpcSemconv() ? "grpc" : null), + equalTo( + RPC_SYSTEM_NAME, emitStableRpcSemconv() ? "grpc" : null), + equalTo( + RPC_SERVICE, + emitOldRpcSemconv() ? "example.Greeter" : null), + equalTo( + RPC_METHOD, + emitStableRpcSemconv() + ? "example.Greeter/SayHello" + : "SayHello"), + equalTo( + RPC_GRPC_STATUS_CODE, + emitOldRpcSemconv() + ? (long) Status.Code.UNIMPLEMENTED.value() + : null), + equalTo( + RPC_RESPONSE_STATUS_CODE, + emitStableRpcSemconv() + ? Status.Code.UNIMPLEMENTED.name() + : null), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(SERVER_PORT, (long) server.getPort())))); + if (emitStableRpcSemconv()) { + spanAsserts.add( + span -> + span.hasName("_OTHER") + .hasKind(SpanKind.SERVER) + .hasParent(trace.getSpan(0)) + .hasStatus(StatusData.error()) + .hasAttributesSatisfyingExactly( + equalTo(RPC_SYSTEM_NAME, "grpc"), + equalTo(RPC_METHOD, "_OTHER"), + equalTo( + RPC_METHOD_ORIGINAL, "example.Greeter/SayHello"), + equalTo( + RPC_RESPONSE_STATUS_CODE, + Status.Code.UNIMPLEMENTED.name()))); + } + + testing() + .waitAndAssertTraces( + trace -> trace.hasSpansSatisfyingExactly(spanAsserts)); + } + @Test void userContextPreserved() throws Exception { Context.Key key = Context.key("cat");