Add ContextRequestFilter instrumentation to JAX-RS
This commit is contained in:
parent
307e56714e
commit
498d2bd461
|
@ -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')
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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')
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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')
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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: '+'
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
||||
}
|
|
@ -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!"
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue