Skip to content

Commit dd57141

Browse files
committed
Capture gRPC UNKNOWN requests
1 parent 4413231 commit dd57141

20 files changed

Lines changed: 472 additions & 34 deletions

File tree

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ default String getRpcMethod(REQUEST request) {
6868
return null;
6969
}
7070

71+
/**
72+
* Returns the original method name when the method reported via {@link #getRpcMethod(REQUEST)} is
73+
* set to {@code _OTHER} because the method is not recognized by the RPC framework.
74+
*/
75+
@Nullable
76+
default String getRpcMethodOriginal(REQUEST request) {
77+
return null;
78+
}
79+
7180
/**
7281
* Returns a description of a class of error the operation ended with.
7382
*

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ abstract class RpcCommonAttributesExtractor<REQUEST, RESPONSE>
1919
implements AttributesExtractor<REQUEST, RESPONSE> {
2020

2121
static final AttributeKey<String> RPC_METHOD = AttributeKey.stringKey("rpc.method");
22+
static final AttributeKey<String> RPC_METHOD_ORIGINAL =
23+
AttributeKey.stringKey("rpc.method_original");
2224

2325
// Stable semconv keys
2426
static final AttributeKey<String> RPC_SYSTEM_NAME = AttributeKey.stringKey("rpc.system.name");
@@ -42,6 +44,7 @@ public final void onStart(AttributesBuilder attributes, Context parentContext, R
4244
if (emitStableRpcSemconv()) {
4345
attributes.put(RPC_SYSTEM_NAME, getter.getRpcSystemName(request));
4446
attributes.put(RPC_METHOD, getter.getRpcMethod(request));
47+
attributes.put(RPC_METHOD_ORIGINAL, getter.getRpcMethodOriginal(request));
4548
}
4649

4750
if (emitOldRpcSemconv()) {

instrumentation/armeria/armeria-grpc-1.14/javaagent/build.gradle.kts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ dependencies {
1818
library("com.linecorp.armeria:armeria-grpc:1.14.0")
1919
implementation(project(":instrumentation:grpc-1.6:library"))
2020

21+
testInstrumentation(project(":instrumentation:armeria:armeria-1.3:javaagent"))
2122
testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent"))
2223
testInstrumentation(project(":instrumentation:grpc-1.6:javaagent"))
2324

@@ -61,7 +62,11 @@ afterEvaluate {
6162

6263
tasks {
6364
withType<Test>().configureEach {
64-
systemProperty("collectMetadata", findProperty("collectMetadata"))
65+
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false")
66+
// The armeria HTTP instrumentation creates an HTTP server span, and then the gRPC
67+
// interceptor creates a second server span from the same incoming context, which triggers the
68+
// context leak debugger.
69+
jvmArgs("-Dotel.javaagent.experimental.thread-propagation-debugger.enabled=false")
6570
}
6671

6772
val testStableSemconv by registering(Test::class) {

instrumentation/armeria/armeria-grpc-1.14/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcServiceBuilderInstrumentation.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
import static net.bytebuddy.matcher.ElementMatchers.named;
1010

1111
import com.linecorp.armeria.server.grpc.GrpcServiceBuilder;
12-
import io.opentelemetry.api.GlobalOpenTelemetry;
13-
import io.opentelemetry.instrumentation.grpc.v1_6.GrpcTelemetry;
1412
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
1513
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
1614
import net.bytebuddy.asm.Advice;
@@ -34,7 +32,7 @@ public static class BuildAdvice {
3432

3533
@Advice.OnMethodEnter(suppress = Throwable.class)
3634
public static void onEnter(@Advice.This GrpcServiceBuilder builder) {
37-
builder.intercept(GrpcTelemetry.create(GlobalOpenTelemetry.get()).createServerInterceptor());
35+
builder.intercept(ArmeriaGrpcSingletons.SERVER_INTERCEPTOR);
3836
}
3937
}
4038
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.armeria.grpc.v1_14;
7+
8+
import io.grpc.ServerInterceptor;
9+
import io.opentelemetry.api.GlobalOpenTelemetry;
10+
import io.opentelemetry.instrumentation.grpc.v1_6.GrpcTelemetry;
11+
import io.opentelemetry.instrumentation.grpc.v1_6.internal.Internal;
12+
13+
public final class ArmeriaGrpcSingletons {
14+
15+
public static final ServerInterceptor SERVER_INTERCEPTOR;
16+
17+
static {
18+
GrpcTelemetry telemetry = GrpcTelemetry.create(GlobalOpenTelemetry.get());
19+
SERVER_INTERCEPTOR = Internal.createServerInterceptor(telemetry);
20+
}
21+
22+
private ArmeriaGrpcSingletons() {}
23+
}

instrumentation/armeria/armeria-grpc-1.14/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/armeria/grpc/v1_14/ArmeriaGrpcTest.java

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,19 @@
88
import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldRpcSemconv;
99
import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableRpcSemconv;
1010
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
11+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
12+
import static io.opentelemetry.semconv.ClientAttributes.CLIENT_ADDRESS;
13+
import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD;
14+
import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE;
15+
import static io.opentelemetry.semconv.HttpAttributes.HTTP_ROUTE;
16+
import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_ADDRESS;
17+
import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_PORT;
18+
import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_VERSION;
1119
import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS;
1220
import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT;
21+
import static io.opentelemetry.semconv.UrlAttributes.URL_PATH;
22+
import static io.opentelemetry.semconv.UrlAttributes.URL_SCHEME;
23+
import static io.opentelemetry.semconv.UserAgentAttributes.USER_AGENT_ORIGINAL;
1324
import static io.opentelemetry.semconv.incubating.MessageIncubatingAttributes.MESSAGE_ID;
1425
import static io.opentelemetry.semconv.incubating.MessageIncubatingAttributes.MESSAGE_TYPE;
1526
import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_GRPC_STATUS_CODE;
@@ -19,6 +30,7 @@
1930
import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM;
2031
import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM_NAME;
2132
import static org.assertj.core.api.Assertions.assertThat;
33+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2234

2335
import com.linecorp.armeria.client.grpc.GrpcClients;
2436
import com.linecorp.armeria.server.ServerBuilder;
@@ -27,9 +39,12 @@
2739
import example.GreeterGrpc;
2840
import example.Helloworld;
2941
import io.grpc.Status;
42+
import io.grpc.StatusRuntimeException;
43+
import io.grpc.protobuf.services.HealthStatusManager;
3044
import io.grpc.stub.StreamObserver;
3145
import io.opentelemetry.api.trace.SpanKind;
3246
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
47+
import io.opentelemetry.sdk.trace.data.StatusData;
3348
import org.junit.jupiter.api.Test;
3449
import org.junit.jupiter.api.extension.RegisterExtension;
3550

@@ -62,6 +77,18 @@ public void sayHello(
6277
}
6378
};
6479

80+
@RegisterExtension
81+
static final ServerExtension serverWithoutGreeter =
82+
new ServerExtension() {
83+
@Override
84+
protected void configure(ServerBuilder sb) {
85+
sb.service(
86+
GrpcService.builder()
87+
.addService(new HealthStatusManager().getHealthService())
88+
.build());
89+
}
90+
};
91+
6592
@SuppressWarnings("deprecation") // using deprecated semconv
6693
@Test
6794
void grpcInstrumentation() {
@@ -108,6 +135,23 @@ void grpcInstrumentation() {
108135
.hasAttributesSatisfyingExactly(
109136
equalTo(MESSAGE_TYPE, "RECEIVED"),
110137
equalTo(MESSAGE_ID, 1L))),
138+
span ->
139+
span.hasName("POST /example.Greeter/SayHello")
140+
.hasKind(SpanKind.SERVER)
141+
.hasParent(trace.getSpan(1))
142+
.hasAttributesSatisfyingExactly(
143+
equalTo(HTTP_REQUEST_METHOD, "POST"),
144+
equalTo(HTTP_RESPONSE_STATUS_CODE, 200),
145+
equalTo(HTTP_ROUTE, "/example.Greeter/SayHello"),
146+
equalTo(URL_PATH, "/example.Greeter/SayHello"),
147+
equalTo(URL_SCHEME, "http"),
148+
equalTo(SERVER_ADDRESS, "127.0.0.1"),
149+
equalTo(SERVER_PORT, (long) server.httpPort()),
150+
equalTo(CLIENT_ADDRESS, "127.0.0.1"),
151+
equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"),
152+
satisfies(NETWORK_PEER_PORT, port -> port.isInstanceOf(Long.class)),
153+
equalTo(NETWORK_PROTOCOL_VERSION, "2"),
154+
satisfies(USER_AGENT_ORIGINAL, val -> val.startsWith("armeria/"))),
111155
span ->
112156
span.hasName("example.Greeter/SayHello")
113157
.hasKind(SpanKind.SERVER)
@@ -139,4 +183,66 @@ void grpcInstrumentation() {
139183
.hasAttributesSatisfyingExactly(
140184
equalTo(MESSAGE_TYPE, "SENT"), equalTo(MESSAGE_ID, 1L)))));
141185
}
186+
187+
@SuppressWarnings("deprecation") // using deprecated semconv
188+
@Test
189+
void unknownService() {
190+
GreeterGrpc.GreeterBlockingStub client =
191+
GrpcClients.builder(serverWithoutGreeter.httpUri())
192+
.build(GreeterGrpc.GreeterBlockingStub.class);
193+
194+
Helloworld.Request request = Helloworld.Request.newBuilder().setName("test").build();
195+
assertThatThrownBy(() -> client.sayHello(request)).isInstanceOf(StatusRuntimeException.class);
196+
197+
// Armeria uses exact-route binding for gRPC services, so calling an unregistered service
198+
// results in an HTTP-level response rather than gRPC UNIMPLEMENTED. The armeria HTTP
199+
// instrumentation captures this as an HTTP server span.
200+
testing.waitAndAssertTraces(
201+
trace ->
202+
trace.hasSpansSatisfyingExactly(
203+
span ->
204+
span.hasName("example.Greeter/SayHello")
205+
.hasKind(SpanKind.CLIENT)
206+
.hasNoParent()
207+
.hasStatus(StatusData.error())
208+
.hasAttributesSatisfyingExactly(
209+
equalTo(maybeStable(RPC_SYSTEM), "grpc"),
210+
equalTo(RPC_SERVICE, emitOldRpcSemconv() ? "example.Greeter" : null),
211+
equalTo(
212+
RPC_METHOD,
213+
emitStableRpcSemconv() ? "example.Greeter/SayHello" : "SayHello"),
214+
equalTo(
215+
RPC_GRPC_STATUS_CODE,
216+
emitOldRpcSemconv()
217+
? (long) Status.Code.UNIMPLEMENTED.value()
218+
: null),
219+
equalTo(
220+
RPC_RESPONSE_STATUS_CODE,
221+
emitStableRpcSemconv() ? Status.Code.UNIMPLEMENTED.name() : null),
222+
equalTo(SERVER_ADDRESS, "127.0.0.1"),
223+
equalTo(SERVER_PORT, (long) serverWithoutGreeter.httpPort()))
224+
.hasEventsSatisfyingExactly(
225+
event ->
226+
event
227+
.hasName("message")
228+
.hasAttributesSatisfyingExactly(
229+
equalTo(MESSAGE_TYPE, "SENT"), equalTo(MESSAGE_ID, 1L))),
230+
span ->
231+
span.hasName("POST /*")
232+
.hasKind(SpanKind.SERVER)
233+
.hasParent(trace.getSpan(0))
234+
.hasAttributesSatisfyingExactly(
235+
equalTo(HTTP_REQUEST_METHOD, "POST"),
236+
equalTo(HTTP_RESPONSE_STATUS_CODE, 404),
237+
equalTo(HTTP_ROUTE, "/*"),
238+
equalTo(URL_PATH, "/example.Greeter/SayHello"),
239+
equalTo(URL_SCHEME, "http"),
240+
equalTo(SERVER_ADDRESS, "127.0.0.1"),
241+
equalTo(SERVER_PORT, (long) serverWithoutGreeter.httpPort()),
242+
equalTo(CLIENT_ADDRESS, "127.0.0.1"),
243+
equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"),
244+
satisfies(NETWORK_PEER_PORT, port -> port.isInstanceOf(Long.class)),
245+
equalTo(NETWORK_PROTOCOL_VERSION, "2"),
246+
satisfies(USER_AGENT_ORIGINAL, val -> val.startsWith("armeria/")))));
247+
}
142248
}

instrumentation/grpc-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grpc/v1_6/GrpcServerBuilderInstrumentation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public static CallDepth onEnter(@Advice.This ServerBuilder<?> serverBuilder) {
4949
return callDepth;
5050
}
5151
if (!Boolean.TRUE.equals(SERVER_BUILDER_INSTRUMENTED.get(serverBuilder))) {
52-
serverBuilder.intercept(GrpcSingletons.SERVER_INTERCEPTOR);
52+
GrpcSingletons.configureServerBuilder(serverBuilder);
5353
SERVER_BUILDER_INSTRUMENTED.set(serverBuilder, true);
5454
}
5555
return callDepth;

instrumentation/grpc-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grpc/v1_6/GrpcSingletons.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import io.grpc.Context;
1212
import io.grpc.ManagedChannelBuilder;
1313
import io.grpc.ServerBuilder;
14-
import io.grpc.ServerInterceptor;
1514
import io.opentelemetry.api.GlobalOpenTelemetry;
1615
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
1716
import io.opentelemetry.instrumentation.api.incubator.config.internal.DeclarativeConfigUtil;
@@ -33,7 +32,7 @@ public final class GrpcSingletons {
3332

3433
public static final ClientInterceptor CLIENT_INTERCEPTOR;
3534

36-
public static final ServerInterceptor SERVER_INTERCEPTOR;
35+
private static final GrpcTelemetry GRPC_TELEMETRY;
3736

3837
private static final AtomicReference<Context.Storage> STORAGE_REFERENCE = new AtomicReference<>();
3938

@@ -56,16 +55,15 @@ public final class GrpcSingletons {
5655
.get("server")
5756
.getScalarList("request", String.class, emptyList());
5857

59-
GrpcTelemetry telemetry =
58+
GRPC_TELEMETRY =
6059
GrpcTelemetry.builder(GlobalOpenTelemetry.get())
6160
.setEmitMessageEvents(emitMessageEvents)
6261
.setCaptureExperimentalSpanAttributes(experimentalSpanAttributes)
6362
.setCapturedClientRequestMetadata(clientRequestMetadata)
6463
.setCapturedServerRequestMetadata(serverRequestMetadata)
6564
.build();
6665

67-
CLIENT_INTERCEPTOR = telemetry.createClientInterceptor();
68-
SERVER_INTERCEPTOR = telemetry.createServerInterceptor();
66+
CLIENT_INTERCEPTOR = GRPC_TELEMETRY.createClientInterceptor();
6967
}
7068

7169
public static Context.Storage getStorage() {
@@ -77,5 +75,9 @@ public static Context.Storage setStorage(Context.Storage storage) {
7775
return getStorage();
7876
}
7977

78+
public static void configureServerBuilder(ServerBuilder<?> serverBuilder) {
79+
GRPC_TELEMETRY.configureServerBuilder(serverBuilder);
80+
}
81+
8082
private GrpcSingletons() {}
8183
}

instrumentation/grpc-1.6/library/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ void configureClientInterceptor(OpenTelemetry openTelemetry, NettyChannelBuilder
3737
nettyChannelBuilder.intercept(grpcTelemetry.createClientInterceptor());
3838
}
3939

40-
// For server-side, attatch the interceptor to your service.
41-
ServerServiceDefinition configureServerInterceptor(OpenTelemetry openTelemetry, ServerServiceDefinition serviceDefinition) {
40+
// For server-side, configure the server builder.
41+
void configureServer(OpenTelemetry openTelemetry, ServerBuilder<?> serverBuilder) {
4242
GrpcTelemetry grpcTelemetry = GrpcTelemetry.create(openTelemetry);
43-
return ServerInterceptors.intercept(serviceDefinition, grpcTelemetry.createServerInterceptor());
43+
grpcTelemetry.configureServerBuilder(serverBuilder);
4444
}
4545
```

instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRequest.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212

1313
public final class GrpcRequest {
1414

15-
private final MethodDescriptor<?, ?> method;
15+
@Nullable private final MethodDescriptor<?, ?> method;
16+
private final String fullMethodName;
17+
@Nullable private final String originalFullMethodName;
1618

1719
@Nullable private volatile Metadata metadata;
1820

@@ -29,11 +31,20 @@ public final class GrpcRequest {
2931
@Nullable SocketAddress peerSocketAddress,
3032
@Nullable String authority) {
3133
this.method = method;
34+
this.fullMethodName = method.getFullMethodName();
35+
this.originalFullMethodName = null;
3236
this.metadata = metadata;
3337
this.peerSocketAddress = peerSocketAddress;
3438
setLogicalAddress(authority);
3539
}
3640

41+
GrpcRequest(String fullMethodName, @Nullable String originalFullMethodName, Metadata metadata) {
42+
this.method = null;
43+
this.fullMethodName = fullMethodName;
44+
this.originalFullMethodName = originalFullMethodName;
45+
this.metadata = metadata;
46+
}
47+
3748
private void setLogicalAddress(@Nullable String authority) {
3849
if (authority == null) {
3950
return;
@@ -51,10 +62,20 @@ private void setLogicalAddress(@Nullable String authority) {
5162
}
5263
}
5364

65+
@Nullable
5466
public MethodDescriptor<?, ?> getMethod() {
5567
return method;
5668
}
5769

70+
String getFullMethodName() {
71+
return fullMethodName;
72+
}
73+
74+
@Nullable
75+
String getOriginalFullMethodName() {
76+
return originalFullMethodName;
77+
}
78+
5879
@Nullable
5980
public Metadata getMetadata() {
6081
return metadata;

0 commit comments

Comments
 (0)