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")