From c6f544b0e74f7045ac3179d992c5a6733cbabb93 Mon Sep 17 00:00:00 2001 From: Tav Date: Mon, 31 Oct 2022 15:46:00 +0200 Subject: [PATCH] Added gRPC request instrumentation --- .../grpc/v1_6/GrpcSingletons.java | 3 + .../grpc/v1_6/GrpcAttributesExtractor.java | 33 +++++++- .../grpc/v1_6/GrpcRpcAttributesGetter.java | 26 +++++++ .../grpc/v1_6/GrpcTelemetryBuilder.java | 14 +++- .../instrumentation/grpc/v1_6/GrpcTest.java | 78 +++++++++++++++++++ .../bootstrap/internal/CommonConfig.java | 12 ++- 6 files changed, 159 insertions(+), 7 deletions(-) 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 5896459c9584..342c58d157fd 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,6 +11,7 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.grpc.v1_6.GrpcTelemetry; import io.opentelemetry.instrumentation.grpc.v1_6.internal.ContextStorageBridge; +import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig; import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; // Holds singleton references. @@ -27,9 +28,11 @@ public final class GrpcSingletons { InstrumentationConfig.get() .getBoolean("otel.instrumentation.grpc.experimental-span-attributes", false); + GrpcTelemetry telemetry = GrpcTelemetry.builder(GlobalOpenTelemetry.get()) .setCaptureExperimentalSpanAttributes(experimentalSpanAttributes) + .setRequestMetadataValuesToCapture(CommonConfig.get().getGrpcRequestMetadata()) .build(); CLIENT_INTERCEPTOR = telemetry.newClientInterceptor(); diff --git a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcAttributesExtractor.java b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcAttributesExtractor.java index 6de0bdd3baad..e83a56a88539 100644 --- a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcAttributesExtractor.java +++ b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcAttributesExtractor.java @@ -6,17 +6,34 @@ package io.opentelemetry.instrumentation.grpc.v1_6; import io.grpc.Status; +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.semconv.trace.attributes.SemanticAttributes; import javax.annotation.Nullable; +import java.util.List; + +import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet; + final class GrpcAttributesExtractor implements AttributesExtractor { + // TODO: This should be added to package io.opentelemetry.semconv.trace.attributes in the next release + public static final String GRPC_METADATA_ATTRIBUTE_VALUE_PREFIX = "rpc.request.metadata"; + + private final GrpcRpcAttributesGetter getter; + private final List requestMetadataValuesToCapture; + + GrpcAttributesExtractor( + GrpcRpcAttributesGetter getter, + List requestMetadataValuesToCapture) { + this.getter = getter; + this.requestMetadataValuesToCapture = requestMetadataValuesToCapture; + } + @Override - public void onStart( - AttributesBuilder attributes, Context parentContext, GrpcRequest grpcRequest) { - // No request attributes + public void onStart(AttributesBuilder attributes, Context parentContext, GrpcRequest grpcRequest) { + // Request attributes captured on request end (onEnd) } @Override @@ -29,5 +46,15 @@ public void onEnd( if (status != null) { attributes.put(SemanticAttributes.RPC_GRPC_STATUS_CODE, status.getCode().value()); } + + if (requestMetadataValuesToCapture != null) { + for (String key : requestMetadataValuesToCapture) { + internalSet( + attributes, + AttributeKey.stringArrayKey(GRPC_METADATA_ATTRIBUTE_VALUE_PREFIX + "." + key), + getter.metadataValue(request, key) + ); + } + } } } 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 4f1087ae83f2..24229e063716 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 @@ -5,8 +5,12 @@ package io.opentelemetry.instrumentation.grpc.v1_6; +import io.grpc.Metadata; import io.opentelemetry.instrumentation.api.instrumenter.rpc.RpcAttributesGetter; import javax.annotation.Nullable; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; enum GrpcRpcAttributesGetter implements RpcAttributesGetter { INSTANCE; @@ -37,4 +41,26 @@ public String method(GrpcRequest request) { } return fullMethodName.substring(slashIndex + 1); } + + @Nullable + public List metadataValue(GrpcRequest request, String key) { + if (request.getMetadata() == null) { + return null; + } + + if (key == null || key.isEmpty()) { + return null; + } + + Iterable values = request.getMetadata().getAll( + Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER) + ); + + if (values == null) { + return null; + } + + return StreamSupport.stream(values.spliterator(), false) + .collect(Collectors.toList()); + } } diff --git a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcTelemetryBuilder.java b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcTelemetryBuilder.java index 1ce44d1dbc44..0bd6a02b7b99 100644 --- a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcTelemetryBuilder.java +++ b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcTelemetryBuilder.java @@ -50,6 +50,8 @@ public final class GrpcTelemetryBuilder { additionalClientExtractors = new ArrayList<>(); private boolean captureExperimentalSpanAttributes; + private List requestMetadataValuesToCapture; + GrpcTelemetryBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -115,6 +117,14 @@ public GrpcTelemetryBuilder setCaptureExperimentalSpanAttributes( return this; } + /** Sets which metadata request values should be captured as span attributes. */ + @CanIgnoreReturnValue + public GrpcTelemetryBuilder setRequestMetadataValuesToCapture( + List requestMetadataValuesToCapture) { + this.requestMetadataValuesToCapture = requestMetadataValuesToCapture; + return this; + } + /** Returns a new {@link GrpcTelemetry} with the settings of this {@link GrpcTelemetryBuilder}. */ public GrpcTelemetry build() { SpanNameExtractor originalSpanNameExtractor = new GrpcSpanNameExtractor(); @@ -139,7 +149,9 @@ public GrpcTelemetry build() { instrumenter -> instrumenter .setSpanStatusExtractor(new GrpcSpanStatusExtractor()) - .addAttributesExtractor(new GrpcAttributesExtractor()) + .addAttributesExtractor( + new GrpcAttributesExtractor( + GrpcRpcAttributesGetter.INSTANCE, requestMetadataValuesToCapture)) .addAttributesExtractors(additionalExtractors)); GrpcNetClientAttributesGetter netClientAttributesGetter = new GrpcNetClientAttributesGetter(); 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 cada74856619..fbdd1de31bc6 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 @@ -24,6 +24,8 @@ import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; +import java.util.Collections; +import java.util.List; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import org.junit.jupiter.api.Test; @@ -35,6 +37,7 @@ class GrpcTest extends AbstractGrpcTest { static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); private static final AttributeKey CUSTOM_KEY = AttributeKey.stringKey("customKey"); + private static final String METADATA_ATTRIBUTE_PREFIX = "rpc.request.metadata."; private static final Metadata.Key CUSTOM_METADATA_KEY = Metadata.Key.of("customMetadataKey", Metadata.ASCII_STRING_MARSHALLER); @@ -56,6 +59,81 @@ protected InstrumentationExtension testing() { return testing; } + @Test + void grpcAttributesExtractor() throws Exception { + String metadataKey = "some-key"; + AttributeKey> attributeKey = AttributeKey.stringArrayKey(METADATA_ATTRIBUTE_PREFIX + metadataKey); + String metadataValue = "some-value"; + List metadataValueAsList = Collections.singletonList("some-value"); + + BindableService greeter = + new GreeterGrpc.GreeterImplBase() { + @Override + public void sayHello( + Helloworld.Request req, StreamObserver responseObserver) { + Helloworld.Response reply = + Helloworld.Response.newBuilder().setMessage("Hello " + req.getName()).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + }; + + GrpcAttributesExtractor grpcAttributesExtractor = new GrpcAttributesExtractor( + GrpcRpcAttributesGetter.INSTANCE, + Collections.singletonList(metadataKey)); + + Server server = + ServerBuilder.forPort(0) + .addService(greeter) + .intercept( + GrpcTelemetry.builder(testing.getOpenTelemetry()) + .addAttributeExtractor(grpcAttributesExtractor) + .build() + .newServerInterceptor()) + .build() + .start(); + + ManagedChannel channel = + createChannel( + ManagedChannelBuilder.forAddress("localhost", server.getPort()) + .intercept( + GrpcTelemetry.builder(testing.getOpenTelemetry()) + .addAttributeExtractor(grpcAttributesExtractor) + .build() + .newClientInterceptor())); + + Metadata extraMetadata = new Metadata(); + extraMetadata.put(Metadata.Key.of(metadataKey, Metadata.ASCII_STRING_MARSHALLER), metadataValue); + + GreeterGrpc.GreeterBlockingStub client = + GreeterGrpc.newBlockingStub(channel) + .withInterceptors(MetadataUtils.newAttachHeadersInterceptor(extraMetadata)); + + Helloworld.Response response = + testing() + .runWithSpan( + "parent", + () -> client.sayHello(Helloworld.Request.newBuilder().setName("test").build())); + + OpenTelemetryAssertions.assertThat(response.getMessage()).isEqualTo("Hello test"); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("example.Greeter/SayHello") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttribute(attributeKey, metadataValueAsList), + span -> + span.hasName("example.Greeter/SayHello") + .hasKind(SpanKind.SERVER) + .hasParent(trace.getSpan(1)) + .hasAttribute(attributeKey, metadataValueAsList))); + } + @Test void metadataProvided() throws Exception { BindableService greeter = diff --git a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/CommonConfig.java b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/CommonConfig.java index 7072f64809c9..731cff65a141 100644 --- a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/CommonConfig.java +++ b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/CommonConfig.java @@ -28,6 +28,7 @@ public static CommonConfig get() { private final List clientResponseHeaders; private final List serverRequestHeaders; private final List serverResponseHeaders; + private final List grpcRequestMetadata; private final boolean statementSanitizationEnabled; CommonConfig(InstrumentationConfig config) { @@ -41,17 +42,18 @@ public static CommonConfig get() { config.getList("otel.instrumentation.http.capture-headers.server.request", emptyList()); serverResponseHeaders = config.getList("otel.instrumentation.http.capture-headers.server.response", emptyList()); + grpcRequestMetadata = + config.getList("otel.instrumentation.grpc.capture-metadata.request", emptyList()); statementSanitizationEnabled = config.getBoolean("otel.instrumentation.common.db-statement-sanitizer.enabled", true); + } public Map getPeerServiceMapping() { return peerServiceMapping; } - public List getClientRequestHeaders() { - return clientRequestHeaders; - } + public List getClientRequestHeaders() { return clientRequestHeaders; } public List getClientResponseHeaders() { return clientResponseHeaders; @@ -65,6 +67,10 @@ public List getServerResponseHeaders() { return serverResponseHeaders; } + public List getGrpcRequestMetadata() { + return grpcRequestMetadata; + } + public boolean isStatementSanitizationEnabled() { return statementSanitizationEnabled; }