diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/servlet/ServerSpanNaming.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/servlet/ServerSpanNaming.java index d3e3c0724322..3718f1f787a3 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/servlet/ServerSpanNaming.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/servlet/ServerSpanNaming.java @@ -56,7 +56,7 @@ public static void updateServerSpanName( ServerSpanNaming serverSpanNaming = context.get(CONTEXT_KEY); if (serverSpanNaming == null) { String name = serverSpanName.get(); - if (name != null) { + if (name != null && !name.isEmpty()) { serverSpan.updateName(name); } return; @@ -67,7 +67,7 @@ public static void updateServerSpanName( !source.useFirst && source.order == serverSpanNaming.updatedBySource.order; if (source.order > serverSpanNaming.updatedBySource.order || onlyIfBetterName) { String name = serverSpanName.get(); - if (name != null && (!onlyIfBetterName || serverSpanNaming.isBetterName(name))) { + if (name != null && !name.isEmpty() && (!onlyIfBetterName || serverSpanNaming.isBetterName(name))) { serverSpan.updateName(name); serverSpanNaming.updatedBySource = source; serverSpanNaming.nameLength = name.length(); diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxRsAnnotationsTracer.java b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxRsAnnotationsTracer.java index 7ca56c1bcd53..26988bd8f9bc 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxRsAnnotationsTracer.java +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxRsAnnotationsTracer.java @@ -10,6 +10,7 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming; import io.opentelemetry.instrumentation.api.servlet.ServletContextPath; import io.opentelemetry.instrumentation.api.tracer.BaseTracer; import io.opentelemetry.instrumentation.api.tracer.ServerSpan; @@ -20,6 +21,7 @@ import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; import javax.ws.rs.HttpMethod; import javax.ws.rs.Path; @@ -61,19 +63,12 @@ public Context startSpan(Context parentContext, Class target, Method method) public void updateSpanNames( Context context, Span span, Span serverSpan, Class target, Method method) { - String pathBasedSpanName = getPathSpanName(target, method); - // If path based name is empty skip prepending context path so that path based name would - // remain as an empty string for which we skip updating span name. Path base span name is - // empty when method and class don't have a jax-rs path annotation, this can happen when - // creating an "abort" span, see RequestContextHelper. - if (!pathBasedSpanName.isEmpty()) { - pathBasedSpanName = JaxrsContextPath.prepend(context, pathBasedSpanName); - pathBasedSpanName = ServletContextPath.prepend(context, pathBasedSpanName); - } + Supplier spanNameSupplier = getPathSpanNameSupplier(context, target, method); if (serverSpan == null) { - updateSpanName(span, pathBasedSpanName); + updateSpanName(span, spanNameSupplier.get()); } else { - updateSpanName(serverSpan, pathBasedSpanName); + ServerSpanNaming.updateServerSpanName( + context, ServerSpanNaming.Source.CONTROLLER, spanNameSupplier); updateSpanName(span, spanNameForMethod(target, method)); } } @@ -91,6 +86,22 @@ private void setCodeAttributes(SpanBuilder spanBuilder, Class target, Method } } + private Supplier getPathSpanNameSupplier( + Context context, Class target, Method method) { + return () -> { + String pathBasedSpanName = getPathSpanName(target, method); + // If path based name is empty skip prepending context path so that path based name would + // remain as an empty string for which we skip updating span name. Path base span name is + // empty when method and class don't have a jax-rs path annotation, this can happen when + // creating an "abort" span, see RequestContextHelper. + if (!pathBasedSpanName.isEmpty()) { + pathBasedSpanName = JaxrsContextPath.prepend(context, pathBasedSpanName); + pathBasedSpanName = ServletContextPath.prepend(context, pathBasedSpanName); + } + return pathBasedSpanName; + }; + } + /** * Returns the span name given a JaxRS annotated method. Results are cached so this method can be * called multiple times without significantly impacting performance. diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/test/groovy/CxfHttpServerTest.groovy b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/test/groovy/CxfHttpServerTest.groovy index f9a88cc33f5b..a7039cb81d5b 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/test/groovy/CxfHttpServerTest.groovy +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/test/groovy/CxfHttpServerTest.groovy @@ -39,4 +39,9 @@ class CxfHttpServerTest extends JaxRsHttpServerTest { void stopServer(Server httpServer) { httpServer.stop() } + + @Override + boolean hasFrameworkInstrumentation() { + false + } } \ No newline at end of file diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/test/groovy/CxfJettyHttpServerTest.groovy b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/test/groovy/CxfJettyHttpServerTest.groovy index 321414fae9b4..707b2077c2c5 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/test/groovy/CxfJettyHttpServerTest.groovy +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/src/test/groovy/CxfJettyHttpServerTest.groovy @@ -5,4 +5,8 @@ class CxfJettyHttpServerTest extends JaxRsJettyHttpServerTest { + @Override + boolean hasFrameworkInstrumentation() { + false + } } \ No newline at end of file diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/test/groovy/JerseyHttpServerTest.groovy b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/test/groovy/JerseyHttpServerTest.groovy index d99fe6e9f1e2..3a0ab3d5a7bf 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/test/groovy/JerseyHttpServerTest.groovy +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/test/groovy/JerseyHttpServerTest.groovy @@ -36,4 +36,9 @@ class JerseyHttpServerTest extends JaxRsHttpServerTest { boolean asyncCancelHasSendError() { true } + + @Override + boolean hasFrameworkInstrumentation() { + false + } } \ No newline at end of file diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/test/groovy/JerseyJettyHttpServerTest.groovy b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/test/groovy/JerseyJettyHttpServerTest.groovy index 6a668e5dc6b7..70ce31b721d4 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/test/groovy/JerseyJettyHttpServerTest.groovy +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/test/groovy/JerseyJettyHttpServerTest.groovy @@ -9,4 +9,9 @@ class JerseyJettyHttpServerTest extends JaxRsJettyHttpServerTest { boolean asyncCancelHasSendError() { true } + + @Override + boolean hasFrameworkInstrumentation() { + false + } } \ No newline at end of file diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/jaxrs-2.0-resteasy-3.0-javaagent.gradle b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/jaxrs-2.0-resteasy-3.0-javaagent.gradle index f5175c4efd0d..da1245d57b5d 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/jaxrs-2.0-resteasy-3.0-javaagent.gradle +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/jaxrs-2.0-resteasy-3.0-javaagent.gradle @@ -25,6 +25,7 @@ dependencies { library group: 'org.jboss.resteasy', name: 'resteasy-jaxrs', version: '3.0.0.Final' implementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-common:javaagent') + implementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-resteasy-common:javaagent') testInstrumentation project(':instrumentation:servlet:servlet-3.0:javaagent') testInstrumentation project(':instrumentation:servlet:servlet-javax-common:javaagent') diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy30InstrumentationModule.java b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy30InstrumentationModule.java index 281e5e830735..2cf16db29e2d 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy30InstrumentationModule.java +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy30InstrumentationModule.java @@ -22,6 +22,9 @@ public Resteasy30InstrumentationModule() { public List typeInstrumentations() { return asList( new Resteasy30RequestContextInstrumentation(), - new Resteasy30ServletContainerDispatcherInstrumentation()); + new ResteasyServletContainerDispatcherInstrumentation(), + new ResteasyRootNodeTypeInstrumentation(), + new ResteasyResourceMethodInvokerInstrumentation(), + new ResteasyResourceLocatorInvokerInstrumentation()); } } diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/jaxrs-2.0-resteasy-3.1-javaagent.gradle b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/jaxrs-2.0-resteasy-3.1-javaagent.gradle index f6fa5f36cf93..2d77bfe4db60 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/jaxrs-2.0-resteasy-3.1-javaagent.gradle +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/jaxrs-2.0-resteasy-3.1-javaagent.gradle @@ -25,6 +25,7 @@ dependencies { library group: 'org.jboss.resteasy', name: 'resteasy-jaxrs', version: '3.1.0.Final' implementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-common:javaagent') + implementation project(':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-resteasy-common:javaagent') testInstrumentation project(':instrumentation:servlet:servlet-3.0:javaagent') testInstrumentation project(':instrumentation:servlet:servlet-javax-common:javaagent') diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy31InstrumentationModule.java b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy31InstrumentationModule.java index 32c0729ac6c9..6fe836c9b7aa 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy31InstrumentationModule.java +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy31InstrumentationModule.java @@ -22,6 +22,9 @@ public Resteasy31InstrumentationModule() { public List typeInstrumentations() { return asList( new Resteasy31RequestContextInstrumentation(), - new Resteasy31ServletContainerDispatcherInstrumentation()); + new ResteasyServletContainerDispatcherInstrumentation(), + new ResteasyRootNodeTypeInstrumentation(), + new ResteasyResourceMethodInvokerInstrumentation(), + new ResteasyResourceLocatorInvokerInstrumentation()); } } diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/jaxrs-2.0-resteasy-common-javaagent.gradle b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/jaxrs-2.0-resteasy-common-javaagent.gradle new file mode 100644 index 000000000000..f5f899492ced --- /dev/null +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/jaxrs-2.0-resteasy-common-javaagent.gradle @@ -0,0 +1,6 @@ +apply from: "$rootDir/gradle/instrumentation.gradle" + +dependencies { + compileOnly group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: '2.0' + compileOnly group: 'org.jboss.resteasy', name: 'resteasy-jaxrs', version: '3.1.0.Final' +} \ No newline at end of file diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyResourceLocatorInvokerInstrumentation.java b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyResourceLocatorInvokerInstrumentation.java new file mode 100644 index 000000000000..4163afca2b77 --- /dev/null +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyResourceLocatorInvokerInstrumentation.java @@ -0,0 +1,72 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0; + +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext; +import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; +import io.opentelemetry.javaagent.instrumentation.api.jaxrs.JaxrsContextPath; +import io.opentelemetry.javaagent.tooling.TypeInstrumentation; +import java.util.Map; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.jboss.resteasy.core.ResourceLocatorInvoker; + +public class ResteasyResourceLocatorInvokerInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("org.jboss.resteasy.core.ResourceLocatorInvoker"); + } + + @Override + public Map, String> transformers() { + return singletonMap( + named("invokeOnTargetObject") + .and(takesArgument(0, named("org.jboss.resteasy.spi.HttpRequest"))) + .and(takesArgument(1, named("org.jboss.resteasy.spi.HttpResponse"))) + .and(takesArgument(2, Object.class)), + ResteasyResourceLocatorInvokerInstrumentation.class.getName() + + "$InvokeOnTargetObjectAdvice"); + } + + public static class InvokeOnTargetObjectAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This ResourceLocatorInvoker resourceInvoker, + @Advice.Local("otelScope") Scope scope) { + + Context currentContext = Java8BytecodeBridge.currentContext(); + + String name = + InstrumentationContext.get(ResourceLocatorInvoker.class, String.class) + .get(resourceInvoker); + ResteasyTracingUtil.updateServerSpanName(currentContext, name); + + // subresource locator returns a resources class that may have @Path annotations + // append current path to jax-rs context path so that it would be present in the final path + Context context = + JaxrsContextPath.init(currentContext, JaxrsContextPath.prepend(currentContext, name)); + if (context != null) { + scope = context.makeCurrent(); + } + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopSpan(@Advice.Local("otelScope") Scope scope) { + if (scope != null) { + scope.close(); + } + } + } +} diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyResourceMethodInvokerInstrumentation.java b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyResourceMethodInvokerInstrumentation.java new file mode 100644 index 000000000000..0c7cb773b534 --- /dev/null +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyResourceMethodInvokerInstrumentation.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0; + +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext; +import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; +import io.opentelemetry.javaagent.tooling.TypeInstrumentation; +import java.util.Map; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.jboss.resteasy.core.ResourceMethodInvoker; + +public class ResteasyResourceMethodInvokerInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("org.jboss.resteasy.core.ResourceMethodInvoker"); + } + + @Override + public Map, String> transformers() { + return singletonMap( + named("invokeOnTarget") + .and(takesArgument(0, named("org.jboss.resteasy.spi.HttpRequest"))) + .and(takesArgument(1, named("org.jboss.resteasy.spi.HttpResponse"))) + .and(takesArgument(2, Object.class)), + ResteasyResourceMethodInvokerInstrumentation.class.getName() + "$InvokeOnTargetAdvice"); + } + + public static class InvokeOnTargetAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter(@Advice.This ResourceMethodInvoker resourceInvoker) { + + String name = + InstrumentationContext.get(ResourceMethodInvoker.class, String.class) + .get(resourceInvoker); + ResteasyTracingUtil.updateServerSpanName(Java8BytecodeBridge.currentContext(), name); + } + } +} diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyRootNodeTypeInstrumentation.java b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyRootNodeTypeInstrumentation.java new file mode 100644 index 000000000000..bdacce0f25a3 --- /dev/null +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyRootNodeTypeInstrumentation.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext; +import io.opentelemetry.javaagent.tooling.TypeInstrumentation; +import java.util.HashMap; +import java.util.Map; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.bytecode.assign.Assigner; +import net.bytebuddy.matcher.ElementMatcher; +import org.jboss.resteasy.core.ResourceLocatorInvoker; +import org.jboss.resteasy.core.ResourceMethodInvoker; + +public class ResteasyRootNodeTypeInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("org.jboss.resteasy.core.registry.RootNode"); + } + + @Override + public Map, String> transformers() { + Map, String> transformers = new HashMap<>(); + transformers.put( + named("addInvoker") + .and(takesArgument(0, String.class)) + // package of ResourceInvoker was changed in reasteasy 4 + .and( + takesArgument( + 1, + named("org.jboss.resteasy.core.ResourceInvoker") + .or(named("org.jboss.resteasy.spi.ResourceInvoker")))), + ResteasyRootNodeTypeInstrumentation.class.getName() + "$AddInvokerAdvice"); + + return transformers; + } + + public static class AddInvokerAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void addInvoker( + @Advice.Argument(0) String path, + @Advice.Argument(value = 1, typing = Assigner.Typing.DYNAMIC) Object invoker) { + String normalizedPath = ResteasyTracingUtil.normalizePath(path); + if (invoker instanceof ResourceLocatorInvoker) { + ResourceLocatorInvoker resourceLocatorInvoker = (ResourceLocatorInvoker) invoker; + InstrumentationContext.get(ResourceLocatorInvoker.class, String.class) + .put(resourceLocatorInvoker, normalizedPath); + } else if (invoker instanceof ResourceMethodInvoker) { + ResourceMethodInvoker resourceMethodInvoker = (ResourceMethodInvoker) invoker; + InstrumentationContext.get(ResourceMethodInvoker.class, String.class) + .put(resourceMethodInvoker, normalizedPath); + } + } + } +} diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy31ServletContainerDispatcherInstrumentation.java b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyServletContainerDispatcherInstrumentation.java similarity index 90% rename from instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy31ServletContainerDispatcherInstrumentation.java rename to instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyServletContainerDispatcherInstrumentation.java index 1ab20019447f..e2ad8802829c 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/Resteasy31ServletContainerDispatcherInstrumentation.java +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyServletContainerDispatcherInstrumentation.java @@ -20,7 +20,7 @@ import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; -public class Resteasy31ServletContainerDispatcherInstrumentation implements TypeInstrumentation { +public class ResteasyServletContainerDispatcherInstrumentation implements TypeInstrumentation { @Override public ElementMatcher typeMatcher() { @@ -31,7 +31,7 @@ public ElementMatcher typeMatcher() { public Map, String> transformers() { return Collections.singletonMap( isMethod().and(named("service")), - Resteasy31ServletContainerDispatcherInstrumentation.class.getName() + "$ServiceAdvice"); + ResteasyServletContainerDispatcherInstrumentation.class.getName() + "$ServiceAdvice"); } public static class ServiceAdvice { diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyTracingUtil.java b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyTracingUtil.java new file mode 100644 index 000000000000..f90d796ac121 --- /dev/null +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/ResteasyTracingUtil.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jaxrs.v2_0; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming; +import io.opentelemetry.instrumentation.api.servlet.ServletContextPath; +import io.opentelemetry.instrumentation.api.tracer.ServerSpan; +import io.opentelemetry.javaagent.instrumentation.api.jaxrs.JaxrsContextPath; + +public final class ResteasyTracingUtil { + + private ResteasyTracingUtil() {} + + public static String normalizePath(String path) { + // ensure that non-empty path starts with / + if (path == null || "/".equals(path)) { + path = ""; + } else if (!path.startsWith("/")) { + path = "/" + path; + } + // remove trailing / + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + + return path; + } + + public static void updateServerSpanName(Context context, String name) { + if (name == null || name.isEmpty()) { + return; + } + + Span serverSpan = ServerSpan.fromContextOrNull(context); + if (serverSpan == null) { + return; + } + + serverSpan.updateName( + ServletContextPath.prepend(context, JaxrsContextPath.prepend(context, name))); + // mark span name as updated from controller to avoid JaxRsAnnotationsTracer updating it + ServerSpanNaming.updateSource(context, ServerSpanNaming.Source.CONTROLLER); + } +} diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-testing/src/main/groovy/JaxRsHttpServerTest.groovy b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-testing/src/main/groovy/JaxRsHttpServerTest.groovy index 4334d108e526..a0e134e77869 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-testing/src/main/groovy/JaxRsHttpServerTest.groovy +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-testing/src/main/groovy/JaxRsHttpServerTest.groovy @@ -9,6 +9,7 @@ import static io.opentelemetry.api.trace.StatusCode.ERROR import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS +import static io.opentelemetry.instrumentation.test.utils.TraceUtils.basicSpan import static java.util.concurrent.TimeUnit.SECONDS import static org.junit.Assume.assumeTrue @@ -27,6 +28,94 @@ import spock.lang.Unroll import test.JaxRsTestResource abstract class JaxRsHttpServerTest extends HttpServerTest implements AgentTestTrait { + + def "test super method without @Path"() { + assumeTrue(hasFrameworkInstrumentation()) + + given: + def url = HttpUrl.get(address.resolve("test-resource-super")).newBuilder() + .build() + def request = request(url, "GET", null).build() + def response = client.newCall(request).execute() + + expect: + response.withCloseable { + assert response.code() == SUCCESS.status + assert response.body().string() == SUCCESS.body + true + } + + assertTraces(1) { + trace(0, 2) { + span(0) { + hasNoParent() + kind SERVER + name getContextPath() + "/test-resource-super" + } + basicSpan(it, 1, "controller", span(0)) + } + } + } + + def "test interface method with @Path"() { + assumeTrue(hasFrameworkInstrumentation()) + + given: + def url = HttpUrl.get(address.resolve("test-resource-interface/call")).newBuilder() + .build() + def request = request(url, "GET", null).build() + def response = client.newCall(request).execute() + + expect: + response.withCloseable { + assert response.code() == SUCCESS.status + assert response.body().string() == SUCCESS.body + true + } + + assertTraces(1) { + trace(0, 2) { + span(0) { + hasNoParent() + kind SERVER + name getContextPath() + "/test-resource-interface/call" + } + basicSpan(it, 1, "controller", span(0)) + } + } + } + + def "test sub resource locator"() { + assumeTrue(hasFrameworkInstrumentation()) + + given: + def url = HttpUrl.get(address.resolve("test-sub-resource-locator/call/sub")).newBuilder() + .build() + def request = request(url, "GET", null).build() + def response = client.newCall(request).execute() + + expect: + response.withCloseable { + assert response.code() == SUCCESS.status + assert response.body().string() == SUCCESS.body + true + } + + assertTraces(1) { + trace(0, 5) { + span(0) { + hasNoParent() + kind SERVER + name getContextPath() + "/test-sub-resource-locator/call/sub" + } + basicSpan(it, 1,"JaxRsSubResourceLocatorTestResource.call", span(0)) + basicSpan(it, 2, "controller", span(1)) + basicSpan(it, 3, "SubResource.call", span(0)) + basicSpan(it, 4, "controller", span(3)) + } + } + } + @Unroll def "should handle #desc AsyncResponse"() { given: @@ -130,6 +219,10 @@ abstract class JaxRsHttpServerTest extends HttpServerTest implements Agent true } + boolean hasFrameworkInstrumentation() { + true + } + boolean asyncCancelHasSendError() { false } diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-testing/src/main/groovy/test/JaxRsTestResource.groovy b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-testing/src/main/groovy/test/JaxRsTestResource.groovy index 192b4c557ed9..688cd27b45ed 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-testing/src/main/groovy/test/JaxRsTestResource.groovy +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-testing/src/main/groovy/test/JaxRsTestResource.groovy @@ -147,6 +147,58 @@ class JaxRsTestResource { } } +@Path("test-resource-super") +class JaxRsSuperClassTestResource extends JaxRsSuperClassTestResourceSuper { +} + +class JaxRsSuperClassTestResourceSuper { + @GET + Object call() { + HttpServerTest.controller(SUCCESS) { + SUCCESS.body + } + } +} + +class JaxRsInterfaceClassTestResource extends JaxRsInterfaceClassTestResourceSuper implements JaxRsInterface { +} + +@Path("test-resource-interface") +interface JaxRsInterface { + @Path("call") + @GET + Object call() +} + +class JaxRsInterfaceClassTestResourceSuper { + Object call() { + HttpServerTest.controller(SUCCESS) { + SUCCESS.body + } + } +} + +@Path("test-sub-resource-locator") +class JaxRsSubResourceLocatorTestResource { + @Path("call") + Object call() { + HttpServerTest.controller(SUCCESS) { + return new SubResource() + } + } +} + +class SubResource { + @Path("sub") + @GET + String call() { + HttpServerTest.controller(SUCCESS) { + new Exception().printStackTrace() + SUCCESS.body + } + } +} + class JaxRsTestExceptionMapper implements ExceptionMapper { @Override Response toResponse(Exception exception) { @@ -161,6 +213,9 @@ class JaxRsTestApplication extends Application { Set> getClasses() { def classes = new HashSet() classes.add(JaxRsTestResource) + classes.add(JaxRsSuperClassTestResource) + classes.add(JaxRsInterfaceClassTestResource) + classes.add(JaxRsSubResourceLocatorTestResource) classes.add(JaxRsTestExceptionMapper) return classes } diff --git a/settings.gradle b/settings.gradle index f7f16ce1dc00..0721651c5506 100644 --- a/settings.gradle +++ b/settings.gradle @@ -135,6 +135,7 @@ include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-jersey-2.0:javaagent' include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-payara-testing' include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-resteasy-3.0:javaagent' include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-resteasy-3.1:javaagent' +include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-resteasy-common:javaagent' include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-testing' include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-tomee-testing' include ':instrumentation:jaxrs:jaxrs-2.0:jaxrs-2.0-wildfly-testing'