Put `http.route` attribute onto `http.server.duration` on Play framework request processing (#7801)

Basically, `akka-http` instrumenter has the responsibility to instrument
the `http.server.duration` for the Play framework application, but the
current implementation has not marked the `http.route` attribute.
ref:
8e8161cb2e/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaHttpServerAttributesGetter.java (L59)

Actually, it's hard to record that attribute by only the akka-http layer
because that library's request object doesn't hold the route
information, e.g. placeholder.

So this patch delegates that job to the `play-mvc` instrumenter and when
that has been able to get the route info, the instrumenter puts
`http.route` attribute onto `http.server.duration`.

For example, when the routes configuration of the Play is like the
following:

```
GET  /foo/:bar  controllers.HomeController.doSomething(bar: String)
```

and when it tries to access that API, then OTEL instruments like so:

```prometheus
http_server_duration_count{otel_scope_name="io.opentelemetry.akka-http-10.0",otel_scope_version="1.23.0-alpha-SNAPSHOT",http_flavor="1.1",http_method="GET",http_route="/foo/$bar<[^/]+>",http_scheme="http",http_status_code="200",net_host_name="localhost",net_host_port="9000"} 1.0 1676078079798
http_server_duration_sum{otel_scope_name="io.opentelemetry.akka-http-10.0",otel_scope_version="1.23.0-alpha-SNAPSHOT",http_flavor="1.1",http_method="GET",http_route="/foo/$bar<[^/]+>",http_scheme="http",http_status_code="200",net_host_name="localhost",net_host_port="9000"} 12183.558843 1676078079798
http_server_duration_bucket{otel_scope_name="io.opentelemetry.akka-http-10.0",otel_scope_version="1.23.0-alpha-SNAPSHOT",http_flavor="1.1",http_method="GET",http_route="/foo/$bar<[^/]+>",http_scheme="http",http_status_code="200",net_host_name="localhost",net_host_port="9000",le="0.0"} 0.0 1676078079798
...
http_server_duration_bucket{otel_scope_name="io.opentelemetry.akka-http-10.0",otel_scope_version="1.23.0-alpha-SNAPSHOT",http_flavor="1.1",http_method="GET",http_route="/foo/$bar<[^/]+>",http_scheme="http",http_status_code="200",net_host_name="localhost",net_host_port="9000",le="+Inf"} 1.0 1676078079798
```

Rel: #1415

---------

Signed-off-by: moznion <moznion@mail.moznion.net>
This commit is contained in:
moznion 2023-02-14 04:05:59 -08:00 committed by GitHub
parent d847bfcad5
commit 7e8d76a83b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 16 additions and 19 deletions

View File

@ -91,7 +91,7 @@ These are the supported libraries and frameworks:
| [OkHttp](https://github.com/square/okhttp/) | 2.2+ | [opentelemetry-okhttp-3.0](../instrumentation/okhttp/okhttp-3.0/library) | [HTTP Client Spans], [HTTP Client Metrics] |
| [Oracle UCP](https://docs.oracle.com/database/121/JJUCP/) | 11.2+ | [opentelemetry-oracle-ucp-11.2](../instrumentation/oracle-ucp-11.2/library) | [Database Pool Metrics] |
| [OSHI](https://github.com/oshi/oshi/) | 5.3.1+ | [opentelemetry-oshi](../instrumentation/oshi/library) | [System Metrics] (partial support) |
| [Play](https://github.com/playframework/playframework) | 2.4+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
| [Play](https://github.com/playframework/playframework) | 2.4+ | N/A | [HTTP Client Spans], [HTTP Client Metrics], Provides `http.route` [2] |
| [Play WS](https://github.com/playframework/play-ws) | 1.0+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
| [Quartz](https://www.quartz-scheduler.org/) | 2.0+ | [opentelemetry-quartz-2.0](../instrumentation/quartz-2.0/library) | none |
| [RabbitMQ Client](https://github.com/rabbitmq/rabbitmq-java-client) | 2.7+ | N/A | [Messaging Spans] |

View File

@ -9,7 +9,7 @@ import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentCo
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static io.opentelemetry.javaagent.instrumentation.play.v2_4.Play24Singletons.instrumenter;
import static io.opentelemetry.javaagent.instrumentation.play.v2_4.Play24Singletons.updateSpanNames;
import static io.opentelemetry.javaagent.instrumentation.play.v2_4.Play24Singletons.updateSpan;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
@ -73,7 +73,7 @@ public class ActionInstrumentation implements TypeInstrumentation {
@Advice.Local("otelScope") Scope scope) {
scope.close();
updateSpanNames(context, req);
updateSpan(context, req);
// span finished in RequestCompleteCallback
if (throwable == null) {
responseFuture.onComplete(

View File

@ -9,7 +9,8 @@ import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteHolder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource;
import play.api.mvc.Request;
import scala.Option;
@ -25,18 +26,14 @@ public final class Play24Singletons {
return INSTRUMENTER;
}
public static void updateSpanNames(Context context, Request<?> request) {
public static void updateSpan(Context context, Request<?> request) {
String route = getRoute(request);
if (route == null) {
return;
}
Span.fromContext(context).updateName(route);
// set the span name on the upstream akka/netty span
Span serverSpan = LocalRootSpan.fromContextOrNull(context);
if (serverSpan != null) {
serverSpan.updateName(route);
}
HttpRouteHolder.updateHttpRoute(context, HttpRouteSource.CONTROLLER, route);
}
private static String getRoute(Request<?> request) {

View File

@ -9,7 +9,7 @@ import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentCo
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static io.opentelemetry.javaagent.instrumentation.play.v2_6.Play26Singletons.instrumenter;
import static io.opentelemetry.javaagent.instrumentation.play.v2_6.Play26Singletons.updateSpanNames;
import static io.opentelemetry.javaagent.instrumentation.play.v2_6.Play26Singletons.updateSpan;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
@ -73,7 +73,7 @@ public class ActionInstrumentation implements TypeInstrumentation {
@Advice.Local("otelScope") Scope scope) {
scope.close();
updateSpanNames(context, req);
updateSpan(context, req);
if (throwable == null) {
// span is finished when future completes
// not using responseFuture.onComplete() because that doesn't guarantee this handler span

View File

@ -9,7 +9,8 @@ import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteHolder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.annotation.Nullable;
@ -52,18 +53,14 @@ public final class Play26Singletons {
return INSTRUMENTER;
}
public static void updateSpanNames(Context context, Request<?> request) {
public static void updateSpan(Context context, Request<?> request) {
String route = getRoute(request);
if (route == null) {
return;
}
Span.fromContext(context).updateName(route);
// set the span name on the upstream akka/netty span
Span serverSpan = LocalRootSpan.fromContextOrNull(context);
if (serverSpan != null) {
serverSpan.updateName(route);
}
HttpRouteHolder.updateHttpRoute(context, HttpRouteSource.CONTROLLER, route);
}
private static String getRoute(Request<?> request) {

View File

@ -6,6 +6,7 @@
package io.opentelemetry.smoketest
import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
import spock.lang.IgnoreIf
import java.time.Duration
@ -37,6 +38,8 @@ class PlaySmokeTest extends SmokeTest {
//One internal, one SERVER
countSpansByName(traces, '/welcome') == 2
new TraceInspector(traces).countFilteredAttributes(SemanticAttributes.HTTP_ROUTE.key, "/welcome") == 1
cleanup:
stopTarget()