diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index a484c3f771b6..19751bf0ec05 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -42,6 +42,7 @@ These are the supported libraries and frameworks: | [Apache Tapestry](https://tapestry.apache.org/) | 5.4+ | N/A | Provides `http.route` [2], Controller Spans [3] | | [Apache Wicket](https://wicket.apache.org/) | 8.0+ | N/A | Provides `http.route` [2] | | [Armeria](https://armeria.dev) | 1.3+ | [opentelemetry-armeria-1.3](../instrumentation/armeria-1.3/library) | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] | +| [Armeria gRPC](https://armeria.dev) | 1.14+ | | [RPC Client Spans], [RPC Client Metrics], [RPC Server Spans], [RPC Server Metrics] | | [AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client) | 1.9+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] | | [AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/java-handler.html) | 1.0+ | [opentelemetry-aws-lambda-core-1.0](../instrumentation/aws-lambda/aws-lambda-core-1.0/library),
[opentelemetry-aws-lambda-events-2.2](../instrumentation/aws-lambda/aws-lambda-events-2.2/library) | [FaaS Server Spans] | | [AWS SDK](https://aws.amazon.com/sdk-for-java/) | 1.11.x and 2.2+ | [opentelemetry-aws-sdk-1.11](../instrumentation/aws-sdk/aws-sdk-1.11/library),
[opentelemetry-aws-sdk-1.11-autoconfigure](../instrumentation/aws-sdk/aws-sdk-1.11/library-autoconfigure),
[opentelemetry-aws-sdk-2.2](../instrumentation/aws-sdk/aws-sdk-2.2/library),
[opentelemetry-aws-sdk-2.2-autoconfigure](../instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure) | [Messaging Spans], [Database Client Spans], [HTTP Client Spans] | diff --git a/instrumentation/armeria-grpc-1.14/javaagent/build.gradle.kts b/instrumentation/armeria-grpc-1.14/javaagent/build.gradle.kts new file mode 100644 index 000000000000..65a2309b0f69 --- /dev/null +++ b/instrumentation/armeria-grpc-1.14/javaagent/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("com.linecorp.armeria") + module.set("armeria-grpc") + versions.set("[1.14.0,)") + assertInverse.set(true) + } +} + +dependencies { + library("com.linecorp.armeria:armeria-grpc:1.14.0") + implementation(project(":instrumentation:grpc-1.6:library")) + + testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent")) + testInstrumentation(project(":instrumentation:grpc-1.6:javaagent")) + + testImplementation(project(":instrumentation:grpc-1.6:testing")) + testLibrary("com.linecorp.armeria:armeria-junit5:1.14.0") +} diff --git a/instrumentation/armeria-grpc-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcClientBuilderInstrumentation.java b/instrumentation/armeria-grpc-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcClientBuilderInstrumentation.java new file mode 100644 index 000000000000..62364832dfa5 --- /dev/null +++ b/instrumentation/armeria-grpc-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcClientBuilderInstrumentation.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.armeria.grpc.v1_14; + +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; + +import com.linecorp.armeria.client.grpc.GrpcClientBuilder; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.grpc.v1_6.GrpcTelemetry; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class ArmeriaGrpcClientBuilderInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return named("com.linecorp.armeria.client.grpc.GrpcClientBuilder"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod().and(isPublic()).and(named("build")), + ArmeriaGrpcClientBuilderInstrumentation.class.getName() + "$BuildAdvice"); + } + + @SuppressWarnings("unused") + public static class BuildAdvice { + + @Advice.OnMethodEnter + public static void onEnter(@Advice.This GrpcClientBuilder builder) { + builder.intercept(GrpcTelemetry.create(GlobalOpenTelemetry.get()).newClientInterceptor()); + } + } +} diff --git a/instrumentation/armeria-grpc-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcInstrumentationModule.java b/instrumentation/armeria-grpc-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcInstrumentationModule.java new file mode 100644 index 000000000000..5eb6733689cc --- /dev/null +++ b/instrumentation/armeria-grpc-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcInstrumentationModule.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.armeria.grpc.v1_14; + +import static java.util.Arrays.asList; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.List; + +@AutoService(InstrumentationModule.class) +public class ArmeriaGrpcInstrumentationModule extends InstrumentationModule { + public ArmeriaGrpcInstrumentationModule() { + super("armeria", "armeria-1.14", "armeria-grpc-1.14"); + } + + @Override + public List typeInstrumentations() { + return asList( + new ArmeriaGrpcClientBuilderInstrumentation(), + new ArmeriaGrpcServiceBuilderInstrumentation()); + } +} diff --git a/instrumentation/armeria-grpc-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcServiceBuilderInstrumentation.java b/instrumentation/armeria-grpc-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcServiceBuilderInstrumentation.java new file mode 100644 index 000000000000..7d05757f5c4c --- /dev/null +++ b/instrumentation/armeria-grpc-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcServiceBuilderInstrumentation.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.armeria.grpc.v1_14; + +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; + +import com.linecorp.armeria.server.grpc.GrpcServiceBuilder; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.grpc.v1_6.GrpcTelemetry; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class ArmeriaGrpcServiceBuilderInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("com.linecorp.armeria.server.grpc.GrpcServiceBuilder"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod().and(isPublic()).and(named("build")), + ArmeriaGrpcServiceBuilderInstrumentation.class.getName() + "$BuildAdvice"); + } + + @SuppressWarnings("unused") + public static class BuildAdvice { + + @Advice.OnMethodEnter + public static void onEnter(@Advice.This GrpcServiceBuilder builder) { + builder.intercept(GrpcTelemetry.create(GlobalOpenTelemetry.get()).newServerInterceptor()); + } + } +} diff --git a/instrumentation/armeria-grpc-1.14/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcTest.java b/instrumentation/armeria-grpc-1.14/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcTest.java new file mode 100644 index 000000000000..2402365b9efa --- /dev/null +++ b/instrumentation/armeria-grpc-1.14/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcTest.java @@ -0,0 +1,127 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.armeria.grpc.v1_14; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static org.assertj.core.api.Assertions.assertThat; + +import com.linecorp.armeria.client.grpc.GrpcClients; +import com.linecorp.armeria.server.ServerBuilder; +import com.linecorp.armeria.server.grpc.GrpcService; +import com.linecorp.armeria.testing.junit5.server.ServerExtension; +import example.GreeterGrpc; +import example.Helloworld; +import io.grpc.Status; +import io.grpc.stub.StreamObserver; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.semconv.ServerAttributes; +import io.opentelemetry.semconv.incubating.MessageIncubatingAttributes; +import io.opentelemetry.semconv.incubating.RpcIncubatingAttributes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class ArmeriaGrpcTest { + + @RegisterExtension + static final AgentInstrumentationExtension testing = AgentInstrumentationExtension.create(); + + @RegisterExtension + static final ServerExtension server = + new ServerExtension() { + @Override + protected void configure(ServerBuilder sb) throws Exception { + sb.service( + GrpcService.builder() + .addService( + new GreeterGrpc.GreeterImplBase() { + @Override + public void sayHello( + Helloworld.Request request, + StreamObserver responseObserver) { + responseObserver.onNext( + Helloworld.Response.newBuilder() + .setMessage("Hello " + request.getName()) + .build()); + responseObserver.onCompleted(); + } + }) + .build()); + } + }; + + @Test + void grpcInstrumentation() { + GreeterGrpc.GreeterBlockingStub client = + GrpcClients.builder(server.httpUri()).build(GreeterGrpc.GreeterBlockingStub.class); + + Helloworld.Response response = + testing.runWithSpan( + "parent", + () -> client.sayHello(Helloworld.Request.newBuilder().setName("test").build())); + + 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)) + .hasAttributesSatisfyingExactly( + equalTo(RpcIncubatingAttributes.RPC_SYSTEM, "grpc"), + equalTo(RpcIncubatingAttributes.RPC_SERVICE, "example.Greeter"), + equalTo(RpcIncubatingAttributes.RPC_METHOD, "SayHello"), + equalTo( + RpcIncubatingAttributes.RPC_GRPC_STATUS_CODE, + (long) Status.Code.OK.value()), + equalTo(ServerAttributes.SERVER_ADDRESS, "127.0.0.1"), + equalTo(ServerAttributes.SERVER_PORT, (long) server.httpPort())) + .hasEventsSatisfyingExactly( + event -> + event + .hasName("message") + .hasAttributesSatisfyingExactly( + equalTo(MessageIncubatingAttributes.MESSAGE_TYPE, "SENT"), + equalTo(MessageIncubatingAttributes.MESSAGE_ID, 1L)), + event -> + event + .hasName("message") + .hasAttributesSatisfyingExactly( + equalTo( + MessageIncubatingAttributes.MESSAGE_TYPE, "RECEIVED"), + equalTo(MessageIncubatingAttributes.MESSAGE_ID, 2L))), + span -> + span.hasName("example.Greeter/SayHello") + .hasKind(SpanKind.SERVER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(RpcIncubatingAttributes.RPC_SYSTEM, "grpc"), + equalTo(RpcIncubatingAttributes.RPC_SERVICE, "example.Greeter"), + equalTo(RpcIncubatingAttributes.RPC_METHOD, "SayHello"), + equalTo( + RpcIncubatingAttributes.RPC_GRPC_STATUS_CODE, + (long) Status.Code.OK.value()), + equalTo(ServerAttributes.SERVER_ADDRESS, "127.0.0.1"), + equalTo(ServerAttributes.SERVER_PORT, server.httpPort())) + .hasEventsSatisfyingExactly( + event -> + event + .hasName("message") + .hasAttributesSatisfyingExactly( + equalTo( + MessageIncubatingAttributes.MESSAGE_TYPE, "RECEIVED"), + equalTo(MessageIncubatingAttributes.MESSAGE_ID, 1L)), + event -> + event + .hasName("message") + .hasAttributesSatisfyingExactly( + equalTo(MessageIncubatingAttributes.MESSAGE_TYPE, "SENT"), + equalTo(MessageIncubatingAttributes.MESSAGE_ID, 2L))))); + } +} 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 f540b328cefd..719a32cea4a2 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 @@ -9,6 +9,7 @@ import io.grpc.ForwardingServerCall; import io.grpc.ForwardingServerCallListener; import io.grpc.Grpc; +import io.grpc.InternalMetadata; import io.grpc.Metadata; import io.grpc.ServerCall; import io.grpc.ServerCallHandler; @@ -28,6 +29,9 @@ final class TracingServerInterceptor implements ServerInterceptor { private static final AtomicLongFieldUpdater MESSAGE_ID_UPDATER = AtomicLongFieldUpdater.newUpdater(TracingServerCall.class, "messageId"); + private static final Metadata.Key AUTHORITY_KEY = + InternalMetadata.keyOf(":authority", Metadata.ASCII_STRING_MARSHALLER); + private final Instrumenter instrumenter; private final boolean captureExperimentalSpanAttributes; @@ -42,12 +46,17 @@ public ServerCall.Listener interceptCall( ServerCall call, Metadata headers, ServerCallHandler next) { + String authority = call.getAuthority(); + if (authority == null && headers != null) { + // armeria grpc client exposes authority in a header + authority = headers.get(AUTHORITY_KEY); + } GrpcRequest request = new GrpcRequest( call.getMethodDescriptor(), headers, call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR), - call.getAuthority()); + authority); Context parentContext = Context.current(); if (!instrumenter.shouldStart(parentContext, request)) { return next.startCall(call, headers); diff --git a/settings.gradle.kts b/settings.gradle.kts index 17793b988bcf..47267c03b057 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -168,6 +168,7 @@ include(":instrumentation:apache-httpclient:apache-httpclient-5.2:library") include(":instrumentation:armeria-1.3:javaagent") include(":instrumentation:armeria-1.3:library") include(":instrumentation:armeria-1.3:testing") +include(":instrumentation:armeria-grpc-1.14:javaagent") include(":instrumentation:async-http-client:async-http-client-1.9:javaagent") include(":instrumentation:async-http-client:async-http-client-2.0:javaagent") include(":instrumentation:aws-lambda:aws-lambda-core-1.0:javaagent")