|
8 | 8 | import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldRpcSemconv; |
9 | 9 | import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableRpcSemconv; |
10 | 10 | 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; |
11 | 19 | import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; |
12 | 20 | 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; |
13 | 24 | import static io.opentelemetry.semconv.incubating.MessageIncubatingAttributes.MESSAGE_ID; |
14 | 25 | import static io.opentelemetry.semconv.incubating.MessageIncubatingAttributes.MESSAGE_TYPE; |
15 | 26 | import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_GRPC_STATUS_CODE; |
|
19 | 30 | import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM; |
20 | 31 | import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM_NAME; |
21 | 32 | import static org.assertj.core.api.Assertions.assertThat; |
| 33 | +import static org.assertj.core.api.Assertions.assertThatThrownBy; |
22 | 34 |
|
23 | 35 | import com.linecorp.armeria.client.grpc.GrpcClients; |
24 | 36 | import com.linecorp.armeria.server.ServerBuilder; |
|
27 | 39 | import example.GreeterGrpc; |
28 | 40 | import example.Helloworld; |
29 | 41 | import io.grpc.Status; |
| 42 | +import io.grpc.StatusRuntimeException; |
| 43 | +import io.grpc.protobuf.services.HealthStatusManager; |
30 | 44 | import io.grpc.stub.StreamObserver; |
31 | 45 | import io.opentelemetry.api.trace.SpanKind; |
32 | 46 | import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; |
| 47 | +import io.opentelemetry.sdk.trace.data.StatusData; |
33 | 48 | import org.junit.jupiter.api.Test; |
34 | 49 | import org.junit.jupiter.api.extension.RegisterExtension; |
35 | 50 |
|
@@ -62,6 +77,18 @@ public void sayHello( |
62 | 77 | } |
63 | 78 | }; |
64 | 79 |
|
| 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 | + |
65 | 92 | @SuppressWarnings("deprecation") // using deprecated semconv |
66 | 93 | @Test |
67 | 94 | void grpcInstrumentation() { |
@@ -108,6 +135,23 @@ void grpcInstrumentation() { |
108 | 135 | .hasAttributesSatisfyingExactly( |
109 | 136 | equalTo(MESSAGE_TYPE, "RECEIVED"), |
110 | 137 | 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/"))), |
111 | 155 | span -> |
112 | 156 | span.hasName("example.Greeter/SayHello") |
113 | 157 | .hasKind(SpanKind.SERVER) |
@@ -139,4 +183,66 @@ void grpcInstrumentation() { |
139 | 183 | .hasAttributesSatisfyingExactly( |
140 | 184 | equalTo(MESSAGE_TYPE, "SENT"), equalTo(MESSAGE_ID, 1L))))); |
141 | 185 | } |
| 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 | + } |
142 | 248 | } |
0 commit comments