Add ContextRequestFilter instrumentation to JAX-RS

This commit is contained in:
Laplie Anderson 2019-12-10 12:28:06 -05:00
parent 307e56714e
commit 498d2bd461
14 changed files with 678 additions and 63 deletions

View File

@ -0,0 +1,17 @@
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')
}

View File

@ -0,0 +1,39 @@
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);
}
}
}

View File

@ -0,0 +1,25 @@
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')
}

View File

@ -0,0 +1,38 @@
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);
}
}
}

View File

@ -0,0 +1,19 @@
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')
}

View File

@ -0,0 +1,37 @@
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);
}
}
}

View File

@ -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: '+'
}

View File

@ -0,0 +1,107 @@
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();
}
}
}

View File

@ -0,0 +1,115 @@
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();
}
}
}

View File

@ -0,0 +1,61 @@
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());
}
}
}

View File

@ -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()));
}
}

View File

@ -0,0 +1,179 @@
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]
}
}

View File

@ -1,62 +0,0 @@
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!"
}
}

View File

@ -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'