Add ContextRequestFilter instrumentation to JAX-RS
ADD
dd-java-agent/instrumentation/jax-rs-annotations-2/filter-jersey/filter-jersey.gradle
+muzzle {
+ // Cant assert fails because muzzle assumes all instrumentations will fail
+ // Instrumentations in jax-rs-annotations-2 will pass
+ pass {
+ group = "org.glassfish.jersey.core"
+ module = "jersey-server"
+ versions = "[2.0,]"
+ }
+}
+apply from: "${rootDir}/gradle/java.gradle"
+
+dependencies {
+ compileOnly group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: '2.0'
+ compileOnly group: 'org.glassfish.jersey.core', name: 'jersey-server', version: '2.0'
+
+ compile project(':dd-java-agent:instrumentation:jax-rs-annotations-2')
+}

ADD
dd-java-agent/instrumentation/jax-rs-annotations-2/filter-jersey/src/main/java/datadog/trace/instrumentation/jaxrs2/JerseyRequestFilterInstrumentation.java
+package datadog.trace.instrumentation.jaxrs2;
+
+import com.google.auto.service.AutoService;
+import datadog.trace.agent.tooling.Instrumenter;
+import datadog.trace.instrumentation.api.AgentScope;
+import java.lang.reflect.Method;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.UriInfo;
+import net.bytebuddy.asm.Advice;
+
+/** Jersey specific filter instrumentation. */
+@AutoService(Instrumenter.class)
+public class JerseyRequestFilterInstrumentation extends AbstractRequestFilterInstrumentation {
+ public static class ContainerRequestContextAdvice {
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static AgentScope decorateAbortSpan(@Advice.This final ContainerRequestContext context) {
+ final UriInfo uriInfo = context.getUriInfo();
+
+ if (context.getProperty(ContainerRequestFilterInstrumentation.ABORT_HANDLED) == null
+ && uriInfo instanceof ResourceInfo) {
+
+ final ResourceInfo resourceInfo = (ResourceInfo) uriInfo;
+ final Method method = resourceInfo.getResourceMethod();
+ final Class resourceClass = resourceInfo.getResourceClass();
+
+ return RequestFilterHelper.createOrUpdateAbortSpan(context, resourceClass, method);
+ }
+
+ return null;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void stopSpan(
+ @Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
+ RequestFilterHelper.closeSpanAndScope(scope, throwable);
+ }
+ }
+}

ADD
dd-java-agent/instrumentation/jax-rs-annotations-2/filter-resteasy-3.0/filter-resteasy-3.0.gradle
+muzzle {
+ // Cant assert fails because muzzle assumes all instrumentations will fail
+ // Instrumentations in jax-rs-annotations-2 will pass
+
+ // Resteasy changes a class's package in 3.1.0 then moves it back in 3.5.0
+ pass {
+ group = "org.jboss.resteasy"
+ module = "resteasy-jaxrs"
+ versions = "[3.0.0.Final,3.1.0.Final)"
+ }
+
+ pass {
+ group = "org.jboss.resteasy"
+ module = "resteasy-jaxrs"
+ versions = "[3.5.0.Final,)"
+ }
+}
+apply from: "${rootDir}/gradle/java.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.0.0.Final'
+
+ compile project(':dd-java-agent:instrumentation:jax-rs-annotations-2')
+}

ADD
dd-java-agent/instrumentation/jax-rs-annotations-2/filter-resteasy-3.0/src/main/java/datadog/trace/instrumentation/jaxrs2/Resteasy30RequestFilterInstrumentation.java
+package datadog.trace.instrumentation.jaxrs2;
+
+import com.google.auto.service.AutoService;
+import datadog.trace.agent.tooling.Instrumenter;
+import datadog.trace.instrumentation.api.AgentScope;
+import java.lang.reflect.Method;
+import javax.ws.rs.container.ContainerRequestContext;
+import net.bytebuddy.asm.Advice;
+import org.jboss.resteasy.core.ResourceMethodInvoker;
+import org.jboss.resteasy.core.interception.PostMatchContainerRequestContext;
+
+@AutoService(Instrumenter.class)
+public class Resteasy30RequestFilterInstrumentation extends AbstractRequestFilterInstrumentation {
+ public static class ContainerRequestContextAdvice {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static AgentScope decorateAbortSpan(@Advice.This final ContainerRequestContext context) {
+ if (context.getProperty(ContainerRequestFilterInstrumentation.ABORT_HANDLED) == null
+ && context instanceof PostMatchContainerRequestContext) {
+
+ final ResourceMethodInvoker resourceMethodInvoker =
+ ((PostMatchContainerRequestContext) context).getResourceMethod();
+ final Method method = resourceMethodInvoker.getMethod();
+ final Class resourceClass = resourceMethodInvoker.getResourceClass();
+
+ return RequestFilterHelper.createOrUpdateAbortSpan(context, resourceClass, method);
+ }
+
+ return null;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void stopSpan(
+ @Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
+ RequestFilterHelper.closeSpanAndScope(scope, throwable);
+ }
+ }
+}

ADD
dd-java-agent/instrumentation/jax-rs-annotations-2/filter-resteasy-3.1/filter-resteasy-3.1.gradle
+muzzle {
+ // Cant assert fails because muzzle assumes all instrumentations will fail
+ // Instrumentations in jax-rs-annotations-2 will pass
+
+ // Resteasy changes a class's package in 3.1.0 then moves it back in 3.5.0
+ pass {
+ group = "org.jboss.resteasy"
+ module = "resteasy-jaxrs"
+ versions = "[3.1.0.Final,3.5.0.Final)"
+ }
+}
+apply from: "${rootDir}/gradle/java.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'
+
+ compile project(':dd-java-agent:instrumentation:jax-rs-annotations-2')
+}

ADD
dd-java-agent/instrumentation/jax-rs-annotations-2/filter-resteasy-3.1/src/main/java/datadog/trace/instrumentation/jaxrs2/Resteasy31RequestFilterInstrumentation.java
+package datadog.trace.instrumentation.jaxrs2;
+
+import com.google.auto.service.AutoService;
+import datadog.trace.agent.tooling.Instrumenter;
+import datadog.trace.instrumentation.api.AgentScope;
+import java.lang.reflect.Method;
+import javax.ws.rs.container.ContainerRequestContext;
+import net.bytebuddy.asm.Advice;
+import org.jboss.resteasy.core.ResourceMethodInvoker;
+import org.jboss.resteasy.core.interception.jaxrs.PostMatchContainerRequestContext;
+
+@AutoService(Instrumenter.class)
+public class Resteasy31RequestFilterInstrumentation extends AbstractRequestFilterInstrumentation {
+ public static class ContainerRequestContextAdvice {
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static AgentScope decorateAbortSpan(@Advice.This final ContainerRequestContext context) {
+ if (context.getProperty(ContainerRequestFilterInstrumentation.ABORT_HANDLED) == null
+ && context instanceof PostMatchContainerRequestContext) {
+
+ final ResourceMethodInvoker resourceMethodInvoker =
+ ((PostMatchContainerRequestContext) context).getResourceMethod();
+ final Method method = resourceMethodInvoker.getMethod();
+ final Class resourceClass = resourceMethodInvoker.getResourceClass();
+
+ return RequestFilterHelper.createOrUpdateAbortSpan(context, resourceClass, method);
+ }
+
+ return null;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void stopSpan(
+ @Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
+ RequestFilterHelper.closeSpanAndScope(scope, throwable);
+ }
+ }
+}

MODIFY
dd-java-agent/instrumentation/jax-rs-annotations-2/jax-rs-annotations-2.gradle
dd-java-agent/instrumentation/jax-rs-annotations-2/jax-rs-annotations-2.gradle
@@ -26,11 +26,19 @@ dependencies {
testCompile project(':dd-java-agent:instrumentation:java-concurrent')
testCompile project(':dd-java-agent:instrumentation:servlet:request-3')
+ testCompile project(':dd-java-agent:instrumentation:jax-rs-annotations-2:filter-jersey')
+ testCompile project(':dd-java-agent:instrumentation:jax-rs-annotations-2:filter-resteasy-3.0')
+ testCompile project(':dd-java-agent:instrumentation:jax-rs-annotations-2:filter-resteasy-3.1')
+ // Jersey
// First version with DropwizardTestSupport:
testCompile group: 'io.dropwizard', name: 'dropwizard-testing', version: '0.8.0'
testCompile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.2.3'
testCompile group: 'com.fasterxml.jackson.module', name: 'jackson-module-afterburner', version: '2.9.10'
latestDepTestCompile group: 'io.dropwizard', name: 'dropwizard-testing', version: '1.+'
+ // Resteasy
+ testCompile group: 'org.jboss.resteasy', name: 'resteasy-jaxrs', version: '3.0.0.Final'
+
+ latestDepTestCompile group: 'org.jboss.resteasy', name: 'resteasy-jaxrs', version: '+'
}

ADD
dd-java-agent/instrumentation/jax-rs-annotations-2/src/main/java/datadog/trace/instrumentation/jaxrs2/AbstractRequestFilterInstrumentation.java
+package datadog.trace.instrumentation.jaxrs2;
+
+import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
+import static datadog.trace.instrumentation.api.AgentTracer.activateSpan;
+import static datadog.trace.instrumentation.api.AgentTracer.activeSpan;
+import static datadog.trace.instrumentation.api.AgentTracer.startSpan;
+import static datadog.trace.instrumentation.jaxrs2.JaxRsAnnotationsDecorator.DECORATE;
+import static java.util.Collections.singletonMap;
+import static net.bytebuddy.matcher.ElementMatchers.isInterface;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+import datadog.trace.agent.tooling.Instrumenter;
+import datadog.trace.instrumentation.api.AgentScope;
+import datadog.trace.instrumentation.api.AgentSpan;
+import java.lang.reflect.Method;
+import java.util.Map;
+import javax.ws.rs.container.ContainerRequestContext;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public abstract class AbstractRequestFilterInstrumentation extends Instrumenter.Default {
+ public AbstractRequestFilterInstrumentation() {
+ super("jax-rs", "jaxrs", "jax-rs-filter");
+ }
+
+ @Override
+ public ElementMatcher<TypeDescription> typeMatcher() {
+ return not(isInterface())
+ .and(safeHasSuperType(named("javax.ws.rs.container.ContainerRequestContext")));
+ }
+
+ @Override
+ public String[] helperClassNames() {
+ return new String[] {
+ "datadog.trace.agent.decorator.BaseDecorator",
+ "datadog.trace.agent.tooling.ClassHierarchyIterable",
+ "datadog.trace.agent.tooling.ClassHierarchyIterable$ClassIterator",
+ packageName + ".JaxRsAnnotationsDecorator",
+ AbstractRequestFilterInstrumentation.class.getName() + "$RequestFilterHelper",
+ };
+ }
+
+ @Override
+ public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
+ return singletonMap(
+ isMethod()
+ .and(named("abortWith"))
+ .and(takesArguments(1))
+ .and(takesArgument(0, named("javax.ws.rs.core.Response"))),
+ getClass().getName() + "$ContainerRequestContextAdvice");
+ }
+
+ public static class RequestFilterHelper {
+ public static AgentScope createOrUpdateAbortSpan(
+ final ContainerRequestContext context, final Class resourceClass, final Method method) {
+
+ if (method != null && resourceClass != null) {
+ context.setProperty(ContainerRequestFilterInstrumentation.ABORT_HANDLED, true);
+ // The ordering of the specific and general abort instrumentation is unspecified
+ // The general instrumentation (ContainerRequestFilterInstrumentation) saves spans
+ // properties if it ran first
+ AgentSpan parent =
+ (AgentSpan) context.getProperty(ContainerRequestFilterInstrumentation.ABORT_PARENT);
+ AgentSpan span =
+ (AgentSpan) context.getProperty(ContainerRequestFilterInstrumentation.ABORT_SPAN);
+
+ if (span == null) {
+ parent = activeSpan();
+ span = startSpan("jax-rs.request.abort");
+
+ final AgentScope scope = activateSpan(span, false);
+ scope.setAsyncPropagation(true);
+
+ DECORATE.afterStart(span);
+ DECORATE.onAbort(span, parent, resourceClass, method);
+
+ return scope;
+ } else {
+ DECORATE.onAbort(span, parent, resourceClass, method);
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ public static void closeSpanAndScope(final AgentScope scope, final Throwable throwable) {
+ if (scope == null) {
+ return;
+ }
+
+ final AgentSpan span = scope.span();
+ if (throwable != null) {
+ DECORATE.onError(span, throwable);
+ }
+
+ DECORATE.beforeFinish(span);
+ span.finish();
+ scope.close();
+ }
+ }
+}

ADD
dd-java-agent/instrumentation/jax-rs-annotations-2/src/main/java/datadog/trace/instrumentation/jaxrs2/ContainerRequestContextInstrumentation.java
+package datadog.trace.instrumentation.jaxrs2;
+
+import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
+import static datadog.trace.instrumentation.api.AgentTracer.activateSpan;
+import static datadog.trace.instrumentation.api.AgentTracer.activeSpan;
+import static datadog.trace.instrumentation.api.AgentTracer.startSpan;
+import static datadog.trace.instrumentation.jaxrs2.JaxRsAnnotationsDecorator.DECORATE;
+import static java.util.Collections.singletonMap;
+import static net.bytebuddy.matcher.ElementMatchers.isInterface;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+import com.google.auto.service.AutoService;
+import datadog.trace.agent.tooling.Instrumenter;
+import datadog.trace.instrumentation.api.AgentScope;
+import datadog.trace.instrumentation.api.AgentSpan;
+import java.lang.reflect.Method;
+import java.util.Map;
+import javax.ws.rs.container.ContainerRequestContext;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+/**
+ * Create a generic jax-rs.request.abort span based on the class name of the filter Implementation
+ * specifc instrumentations can override tag values
+ */
+@AutoService(Instrumenter.class)
+public class ContainerRequestContextInstrumentation extends Instrumenter.Default {
+ public ContainerRequestContextInstrumentation() {
+ super("jax-rs", "jaxrs", "jax-rs-filter");
+ }
+
+ @Override
+ public ElementMatcher<TypeDescription> typeMatcher() {
+ return not(isInterface())
+ .and(safeHasSuperType(named("javax.ws.rs.container.ContainerRequestContext")));
+ }
+
+ @Override
+ public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
+ return singletonMap(
+ isMethod()
+ .and(named("abortWith"))
+ .and(takesArguments(1))
+ .and(takesArgument(0, named("javax.ws.rs.core.Response"))),
+ ContainerRequestContextInstrumentation.class.getName() + "$ContainerRequestContextAdvice");
+ }
+
+ @Override
+ public String[] helperClassNames() {
+ return new String[] {
+ "datadog.trace.agent.decorator.BaseDecorator",
+ "datadog.trace.agent.tooling.ClassHierarchyIterable",
+ "datadog.trace.agent.tooling.ClassHierarchyIterable$ClassIterator",
+ "datadog.trace.instrumentation.jaxrs2.JaxRsAnnotationsDecorator",
+ };
+ }
+
+ public static class ContainerRequestContextAdvice {
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static AgentScope createGenericSpan(@Advice.This final ContainerRequestContext context) {
+
+ if (context.getProperty(ContainerRequestFilterInstrumentation.ABORT_HANDLED) == null) {
+ final AgentSpan parent = activeSpan();
+ final AgentSpan span = startSpan("jax-rs.request.abort");
+
+ // Save spans so a more specific instrumentation can run later
+ context.setProperty(ContainerRequestFilterInstrumentation.ABORT_PARENT, parent);
+ context.setProperty(ContainerRequestFilterInstrumentation.ABORT_SPAN, span);
+
+ final Class filterClass =
+ (Class) context.getProperty(ContainerRequestFilterInstrumentation.ABORT_FILTER_CLASS);
+ Method method = null;
+ try {
+ method = filterClass.getMethod("filter", ContainerRequestContext.class);
+ } catch (final NoSuchMethodException e) {
+ // Unable to find the filter method. This should not be reachable because the context
+ // can only be aborted inside the filter method
+ }
+
+ final AgentScope scope = activateSpan(span, false);
+ scope.setAsyncPropagation(true);
+
+ DECORATE.afterStart(span);
+ DECORATE.onAbort(span, parent, filterClass, method);
+
+ return scope;
+ }
+
+ return null;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void stopSpan(
+ @Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
+ if (scope == null) {
+ return;
+ }
+
+ final AgentSpan span = scope.span();
+ if (throwable != null) {
+ DECORATE.onError(span, throwable);
+ }
+
+ DECORATE.beforeFinish(span);
+ span.finish();
+ scope.close();
+ }
+ }
+}

ADD
dd-java-agent/instrumentation/jax-rs-annotations-2/src/main/java/datadog/trace/instrumentation/jaxrs2/ContainerRequestFilterInstrumentation.java
+package datadog.trace.instrumentation.jaxrs2;
+
+import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
+import static java.util.Collections.singletonMap;
+import static net.bytebuddy.matcher.ElementMatchers.isInterface;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+import com.google.auto.service.AutoService;
+import datadog.trace.agent.tooling.Instrumenter;
+import java.util.Map;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+/** This adds the filter class name to the request properties */
+@AutoService(Instrumenter.class)
+public class ContainerRequestFilterInstrumentation extends Instrumenter.Default {
+ public static final String ABORT_FILTER_CLASS =
+ "datadog.trace.instrumentation.jaxrs2.filter.abort.class";
+ public static final String ABORT_HANDLED =
+ "datadog.trace.instrumentation.jaxrs2.filter.abort.handled";
+ public static final String ABORT_PARENT =
+ "datadog.trace.instrumentation.jaxrs2.filter.abort.parent";
+ public static final String ABORT_SPAN = "datadog.trace.instrumentation.jaxrs2.filter.abort.span";
+
+ public ContainerRequestFilterInstrumentation() {
+ super("jax-rs", "jaxrs", "jax-rs-filter");
+ }
+
+ @Override
+ public ElementMatcher<? super TypeDescription> typeMatcher() {
+ return not(isInterface())
+ .and(safeHasSuperType(named("javax.ws.rs.container.ContainerRequestFilter")));
+ }
+
+ @Override
+ public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
+ return singletonMap(
+ isMethod()
+ .and(named("filter"))
+ .and(takesArguments(1))
+ .and(takesArgument(0, named("javax.ws.rs.container.ContainerRequestContext"))),
+ ContainerRequestFilterInstrumentation.class.getName() + "$RequestFilterAdvice");
+ }
+
+ public static class RequestFilterAdvice {
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static void setFilterClass(
+ @Advice.This final ContainerRequestFilter filter,
+ @Advice.Argument(0) final ContainerRequestContext context) {
+ context.setProperty(ABORT_FILTER_CLASS, filter.getClass());
+ }
+ }
+}

MODIFY
dd-java-agent/instrumentation/jax-rs-annotations-2/src/main/java/datadog/trace/instrumentation/jaxrs2/JaxRsAnnotationsDecorator.java
dd-java-agent/instrumentation/jax-rs-annotations-2/src/main/java/datadog/trace/instrumentation/jaxrs2/JaxRsAnnotationsDecorator.java
@@ -39,6 +39,33 @@ public class JaxRsAnnotationsDecorator extends BaseDecorator {
public void onControllerStart(
final AgentSpan span, final AgentSpan parent, final Class target, final Method method) {
final String resourceName = getPathResourceName(target, method);
+
+ decorateJaxRsSpan(span, parent, target, method, resourceName);
+ }
+
+ public void onAbort(
+ final AgentSpan span, final AgentSpan parent, final Class target, final Method method) {
+ final String resourceName = getPathResourceName(target, method);
+
+ decorateJaxRsSpan(span, parent, target, method, resourceName);
+ }
+
+ public void onAbort(
+ final AgentSpan span,
+ final AgentSpan parent,
+ final Class target,
+ final Method method,
+ final String resourceName) {
+
+ decorateJaxRsSpan(span, parent, target, method, resourceName);
+ }
+
+ private void decorateJaxRsSpan(
+ final AgentSpan span,
+ final AgentSpan parent,
+ final Class target,
+ final Method method,
+ final String resourceName) {
updateParent(parent, resourceName);
span.setTag(DDTags.SPAN_TYPE, DDSpanTypes.HTTP_SERVER);
@@ -48,7 +75,9 @@ public class JaxRsAnnotationsDecorator extends BaseDecorator {
if (isRootScope && !resourceName.isEmpty()) {
span.setTag(DDTags.RESOURCE_NAME, resourceName);
} else {
- span.setTag(DDTags.RESOURCE_NAME, DECORATE.spanNameForClass(target) + "." + method.getName());
+ span.setTag(
+ DDTags.RESOURCE_NAME,
+ DECORATE.spanNameForClass(target) + (method == null ? "" : "." + method.getName()));
}
}

ADD
dd-java-agent/instrumentation/jax-rs-annotations-2/src/test/groovy/JaxRsFilterTest.groovy
+import datadog.trace.agent.test.AgentTestRunner
+import datadog.trace.api.DDSpanTypes
+import datadog.trace.instrumentation.api.Tags
+import io.dropwizard.testing.junit.ResourceTestRule
+import org.jboss.resteasy.core.Dispatcher
+import org.jboss.resteasy.mock.MockDispatcherFactory
+import org.jboss.resteasy.mock.MockHttpRequest
+import org.jboss.resteasy.mock.MockHttpResponse
+import org.junit.ClassRule
+import spock.lang.Shared
+import spock.lang.Unroll
+
+import javax.ws.rs.client.Entity
+import javax.ws.rs.container.ContainerRequestContext
+import javax.ws.rs.container.ContainerRequestFilter
+import javax.ws.rs.container.PreMatching
+import javax.ws.rs.core.MediaType
+import javax.ws.rs.core.Response
+import javax.ws.rs.ext.Provider
+
+import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
+
+@Unroll
+abstract class JaxRsFilterTest extends AgentTestRunner {
+
+ @Shared
+ SimpleRequestFilter simpleRequestFilter = new SimpleRequestFilter()
+
+ @Shared
+ PrematchRequestFilter prematchRequestFilter = new PrematchRequestFilter()
+
+ abstract makeRequest(String url)
+
+ def "test #resource, #abortNormal, #abortPrematch"() {
+ given:
+ simpleRequestFilter.abort = abortNormal
+ prematchRequestFilter.abort = abortPrematch
+ def abort = abortNormal || abortPrematch
+
+ when:
+ def responseText
+ def responseStatus
+
+ // start a trace because the test doesn't go through any servlet or other instrumentation.
+ runUnderTrace("test.span") {
+ (responseText, responseStatus) = makeRequest(resource)
+ }
+
+ then:
+ responseText == expectedResponse
+
+ if (abort) {
+ responseStatus == Response.Status.UNAUTHORIZED.statusCode
+ } else {
+ responseStatus == Response.Status.OK.statusCode
+ }
+
+ assertTraces(1) {
+ trace(0, 2) {
+ span(0) {
+ operationName "test.span"
+ resourceName parentResourceName
+ tags {
+ "$Tags.COMPONENT" "jax-rs"
+ defaultTags()
+ }
+ }
+ span(1) {
+ childOf span(0)
+ operationName abort ? "jax-rs.request.abort" : "jax-rs.request"
+ resourceName controllerName
+ spanType DDSpanTypes.HTTP_SERVER
+ tags {
+ "$Tags.COMPONENT" "jax-rs-controller"
+ defaultTags()
+ }
+ }
+ }
+ }
+
+ where:
+ resource | abortNormal | abortPrematch | parentResourceName | controllerName | expectedResponse
+ "/test/hello/bob" | false | false | "POST /test/hello/{name}" | "Test1.hello" | "Test1 bob!"
+ "/test2/hello/bob" | false | false | "POST /test2/hello/{name}" | "Test2.hello" | "Test2 bob!"
+ "/test3/hi/bob" | false | false | "POST /test3/hi/{name}" | "Test3.hello" | "Test3 bob!"
+
+ // Resteasy and Jersey give different resource class names for just the below case
+ // Resteasy returns "SubResource.class"
+ // Jersey returns "Test1.class
+ // "/test/hello/bob" | true | false | "POST /test/hello/{name}" | "Test1.hello" | "Aborted"
+
+ "/test2/hello/bob" | true | false | "POST /test2/hello/{name}" | "Test2.hello" | "Aborted"
+ "/test3/hi/bob" | true | false | "POST /test3/hi/{name}" | "Test3.hello" | "Aborted"
+ "/test/hello/bob" | false | true | "test.span" | "PrematchRequestFilter.filter" | "Aborted Prematch"
+ "/test2/hello/bob" | false | true | "test.span" | "PrematchRequestFilter.filter" | "Aborted Prematch"
+ "/test3/hi/bob" | false | true | "test.span" | "PrematchRequestFilter.filter" | "Aborted Prematch"
+ }
+
+ @Provider
+ class SimpleRequestFilter implements ContainerRequestFilter {
+ boolean abort = false
+
+ @Override
+ void filter(ContainerRequestContext requestContext) throws IOException {
+ if (abort) {
+ requestContext.abortWith(
+ Response.status(Response.Status.UNAUTHORIZED)
+ .entity("Aborted")
+ .type(MediaType.TEXT_PLAIN_TYPE)
+ .build())
+ }
+ }
+ }
+
+ @Provider
+ @PreMatching
+ class PrematchRequestFilter implements ContainerRequestFilter {
+ boolean abort = false
+
+ @Override
+ void filter(ContainerRequestContext requestContext) throws IOException {
+ if (abort) {
+ requestContext.abortWith(
+ Response.status(Response.Status.UNAUTHORIZED)
+ .entity("Aborted Prematch")
+ .type(MediaType.TEXT_PLAIN_TYPE)
+ .build())
+ }
+ }
+ }
+}
+
+class JerseyFilterTest extends JaxRsFilterTest {
+ @Shared
+ @ClassRule
+ ResourceTestRule resources = ResourceTestRule.builder()
+ .addResource(new Resource.Test1())
+ .addResource(new Resource.Test2())
+ .addResource(new Resource.Test3())
+ .addProvider(simpleRequestFilter)
+ .addProvider(prematchRequestFilter)
+ .build()
+
+ @Override
+ def makeRequest(String url) {
+ Response response = resources.client().target(url).request().post(Entity.text(""))
+
+ return [response.readEntity(String), response.statusInfo.statusCode]
+ }
+}
+
+class ResteasyFilterTest extends JaxRsFilterTest {
+ @Shared
+ Dispatcher dispatcher
+
+ def setupSpec() {
+ dispatcher = MockDispatcherFactory.createDispatcher()
+ def registry = dispatcher.getRegistry()
+ registry.addSingletonResource(new Resource.Test1())
+ registry.addSingletonResource(new Resource.Test2())
+ registry.addSingletonResource(new Resource.Test3())
+
+ dispatcher.getProviderFactory().register(simpleRequestFilter)
+ dispatcher.getProviderFactory().register(prematchRequestFilter)
+ }
+
+ @Override
+ def makeRequest(String url) {
+ MockHttpRequest request = MockHttpRequest.post(url)
+ request.contentType(MediaType.TEXT_PLAIN_TYPE)
+ request.content(new byte[0])
+
+ MockHttpResponse response = new MockHttpResponse()
+ dispatcher.invoke(request, response)
+
+ return [response.contentAsString, response.status]
+ }
+
+}

DELETE
dd-java-agent/instrumentation/jax-rs-annotations-2/src/test/groovy/JerseyTest.groovy
-import datadog.trace.agent.test.AgentTestRunner
-import datadog.trace.api.DDSpanTypes
-import datadog.trace.instrumentation.api.Tags
-import io.dropwizard.testing.junit.ResourceTestRule
-import org.junit.ClassRule
-import spock.lang.Shared
-
-import javax.ws.rs.client.Entity
-
-import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
-
-class JerseyTest extends AgentTestRunner {
-
- @Shared
- @ClassRule
- ResourceTestRule resources = ResourceTestRule.builder()
- .addResource(new Resource.Test1())
- .addResource(new Resource.Test2())
- .addResource(new Resource.Test3())
- .build()
-
- def "test #resource"() {
- when:
- // start a trace because the test doesn't go through any servlet or other instrumentation.
- def response = runUnderTrace("test.span") {
- resources.client().target(resource).request().post(Entity.text(""), String)
- }
-
- then:
- response == expectedResponse
-
- assertTraces(1) {
- trace(0, 2) {
- span(0) {
- operationName "test.span"
- resourceName expectedResourceName
- tags {
- "$Tags.COMPONENT" "jax-rs"
- defaultTags()
- }
- }
-
- span(1) {
- childOf span(0)
- operationName "jax-rs.request"
- resourceName controllerName
- spanType DDSpanTypes.HTTP_SERVER
- tags {
- "$Tags.COMPONENT" "jax-rs-controller"
- defaultTags()
- }
- }
- }
- }
-
- where:
- resource | expectedResourceName | controllerName | expectedResponse
- "/test/hello/bob" | "POST /test/hello/{name}" | "Test1.hello" | "Test1 bob!"
- "/test2/hello/bob" | "POST /test2/hello/{name}" | "Test2.hello" | "Test2 bob!"
- "/test3/hi/bob" | "POST /test3/hi/{name}" | "Test3.hello" | "Test3 bob!"
- }
-}

MODIFY
settings.gradle
settings.gradle
@@ -72,6 +72,9 @@ include ':dd-java-agent:instrumentation:http-url-connection'
include ':dd-java-agent:instrumentation:hystrix-1.4'
include ':dd-java-agent:instrumentation:jax-rs-annotations-1'
include ':dd-java-agent:instrumentation:jax-rs-annotations-2'
+include ':dd-java-agent:instrumentation:jax-rs-annotations-2:filter-jersey'
+include ':dd-java-agent:instrumentation:jax-rs-annotations-2:filter-resteasy-3.0'
+include ':dd-java-agent:instrumentation:jax-rs-annotations-2:filter-resteasy-3.1'
include ':dd-java-agent:instrumentation:jax-rs-client-1.1'
include ':dd-java-agent:instrumentation:jax-rs-client-2.0'
include ':dd-java-agent:instrumentation:jax-rs-client-2.0:connection-error-handling-jersey'

Add ContextRequestFilter instrumentation to JAX-RS