diff --git a/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/ActionCodeAttributesGetter.java b/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/ActionCodeAttributesGetter.java new file mode 100644 index 000000000000..10c7662eef30 --- /dev/null +++ b/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/ActionCodeAttributesGetter.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.play.v2_4; + +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter; +import javax.annotation.Nullable; + +public class ActionCodeAttributesGetter implements CodeAttributesGetter { + @Nullable + @Override + public Class getCodeClass(ActionData actionData) { + return actionData.codeClass(); + } + + @Nullable + @Override + public String getMethodName(ActionData actionData) { + return actionData.methodName(); + } +} diff --git a/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/ActionData.java b/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/ActionData.java new file mode 100644 index 000000000000..36e52e164715 --- /dev/null +++ b/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/ActionData.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.play.v2_4; + +import java.lang.reflect.Method; + +public class ActionData { + private final Class target; + private final Method method; + + public ActionData(Class target, Method method) { + this.target = target; + this.method = method; + } + + public Class codeClass() { + return target; + } + + public String methodName() { + return method.getName(); + } +} diff --git a/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/ActionInstrumentation.java b/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/ActionInstrumentation.java index 4d376817c6f0..f55d123ab8ae 100644 --- a/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/ActionInstrumentation.java +++ b/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/ActionInstrumentation.java @@ -8,16 +8,15 @@ import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; 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.updateSpan; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.returns; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.lang.reflect.Method; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -50,17 +49,14 @@ public void transform(TypeTransformer transformer) { public static class ApplyAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( - @Advice.Argument(0) Request req, - @Advice.Local("otelContext") Context context, - @Advice.Local("otelScope") Scope scope) { + public static ActionScope onEnter( + @Advice.This Object target, + @Advice.Origin Method method, + @Advice.Argument(0) Request req) { Context parentContext = currentContext(); - if (!instrumenter().shouldStart(parentContext, null)) { - return; - } - context = instrumenter().start(parentContext, null); - scope = context.makeCurrent(); + ActionData actionData = new ActionData(target.getClass(), method); + return ActionScope.start(parentContext, actionData); } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) @@ -69,20 +65,21 @@ public static void stopTraceOnResponse( @Advice.Thrown Throwable throwable, @Advice.Argument(0) Request req, @Advice.Return(readOnly = false) Future responseFuture, - @Advice.Local("otelContext") Context context, - @Advice.Local("otelScope") Scope scope) { - if (scope == null) { + @Advice.Enter ActionScope actionScope) { + if (actionScope == null || actionScope.getScope() == null) { return; } - scope.close(); + actionScope.closeScope(); + + updateSpan(actionScope.getContext(), req); - updateSpan(context, req); // span finished in RequestCompleteCallback if (throwable == null) { responseFuture.onComplete( - new RequestCompleteCallback(context), ((Action) thisAction).executionContext()); + new RequestCompleteCallback(actionScope.getContext()), + ((Action) thisAction).executionContext()); } else { - instrumenter().end(context, null, null, throwable); + actionScope.end(throwable); } } } diff --git a/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/ActionScope.java b/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/ActionScope.java new file mode 100644 index 000000000000..edacdb557031 --- /dev/null +++ b/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/ActionScope.java @@ -0,0 +1,53 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.play.v2_4; + +import static io.opentelemetry.javaagent.instrumentation.play.v2_4.Play24Singletons.instrumenter; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; + +/** Container used to carry state between enter and exit advices */ +public final class ActionScope { + + private final ActionData actionData; + private final Context context; + private final Scope scope; + + public ActionScope(Context context, Scope scope, ActionData actionData) { + this.actionData = actionData; + this.context = context; + this.scope = scope; + } + + public Context getContext() { + return context; + } + + public Scope getScope() { + return scope; + } + + public static ActionScope start(Context parentContext, ActionData actionData) { + if (!instrumenter().shouldStart(parentContext, actionData)) { + return null; + } + + Context context = instrumenter().start(parentContext, actionData); + return new ActionScope(context, context.makeCurrent(), actionData); + } + + public void closeScope() { + if (scope != null) { + scope.close(); + } + } + + public void end(Throwable throwable) { + closeScope(); + instrumenter().end(context, actionData, null, throwable); + } +} diff --git a/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/Play24Singletons.java b/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/Play24Singletons.java index 26c9581ddb36..c48cb0c2603f 100644 --- a/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/Play24Singletons.java +++ b/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/Play24Singletons.java @@ -8,6 +8,7 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute; import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource; @@ -16,15 +17,20 @@ import scala.Option; public final class Play24Singletons { - private static final String SPAN_NAME = "play.request"; - private static final Instrumenter INSTRUMENTER = - Instrumenter.builder( - GlobalOpenTelemetry.get(), "io.opentelemetry.play-mvc-2.4", s -> SPAN_NAME) - .setEnabled(ExperimentalConfig.get().controllerTelemetryEnabled()) - .buildInstrumenter(); + private static final Instrumenter INSTRUMENTER; + + static { + INSTRUMENTER = + Instrumenter.builder( + GlobalOpenTelemetry.get(), "io.opentelemetry.play-mvc-2.4", s -> SPAN_NAME) + .addAttributesExtractor( + CodeAttributesExtractor.create(new ActionCodeAttributesGetter())) + .setEnabled(ExperimentalConfig.get().controllerTelemetryEnabled()) + .buildInstrumenter(); + } - public static Instrumenter instrumenter() { + public static Instrumenter instrumenter() { return INSTRUMENTER; } diff --git a/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/server/PlayServerTest.java b/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/server/PlayServerTest.java index fe4da606af53..c669a21e1fa2 100644 --- a/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/server/PlayServerTest.java +++ b/instrumentation/play/play-mvc/play-mvc-2.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/play/v2_4/server/PlayServerTest.java @@ -13,7 +13,10 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.semconv.HttpAttributes.HTTP_ROUTE; +import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_FUNCTION; +import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_NAMESPACE; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; @@ -112,9 +115,15 @@ protected void configure(HttpServerTestOptions options) { } @Override + @SuppressWarnings("deprecation") // uses deprecated semconv public SpanDataAssert assertHandlerSpan( SpanDataAssert span, String method, ServerEndpoint endpoint) { - span.hasName("play.request").hasKind(INTERNAL); + span.hasName("play.request") + .hasKind(INTERNAL) + .hasAttributesSatisfyingExactly( + equalTo(CODE_NAMESPACE, "play.api.mvc.ActionBuilder$$anon$2"), + equalTo(CODE_FUNCTION, "apply")); + if (endpoint == EXCEPTION) { span.hasStatus(StatusData.error()); span.hasException(new IllegalArgumentException(EXCEPTION.getBody()));