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:java-concurrent')
|
||||||
testCompile project(':dd-java-agent:instrumentation:servlet:request-3')
|
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:
|
// First version with DropwizardTestSupport:
|
||||||
testCompile group: 'io.dropwizard', name: 'dropwizard-testing', version: '0.8.0'
|
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: 'javax.xml.bind', name: 'jaxb-api', version: '2.2.3'
|
||||||
testCompile group: 'com.fasterxml.jackson.module', name: 'jackson-module-afterburner', version: '2.9.10'
|
testCompile group: 'com.fasterxml.jackson.module', name: 'jackson-module-afterburner', version: '2.9.10'
|
||||||
|
|
||||||
latestDepTestCompile group: 'io.dropwizard', name: 'dropwizard-testing', version: '1.+'
|
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(
|
public void onControllerStart(
|
||||||
final AgentSpan span, final AgentSpan parent, final Class target, final Method method) {
|
final AgentSpan span, final AgentSpan parent, final Class target, final Method method) {
|
||||||
final String resourceName = getPathResourceName(target, 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);
|
updateParent(parent, resourceName);
|
||||||
|
|
||||||
span.setTag(DDTags.SPAN_TYPE, DDSpanTypes.HTTP_SERVER);
|
span.setTag(DDTags.SPAN_TYPE, DDSpanTypes.HTTP_SERVER);
|
||||||
|
@ -48,7 +75,9 @@ public class JaxRsAnnotationsDecorator extends BaseDecorator {
|
||||||
if (isRootScope && !resourceName.isEmpty()) {
|
if (isRootScope && !resourceName.isEmpty()) {
|
||||||
span.setTag(DDTags.RESOURCE_NAME, resourceName);
|
span.setTag(DDTags.RESOURCE_NAME, resourceName);
|
||||||
} else {
|
} 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:hystrix-1.4'
|
||||||
include ':dd-java-agent:instrumentation:jax-rs-annotations-1'
|
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'
|
||||||
|
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-1.1'
|
||||||
include ':dd-java-agent:instrumentation:jax-rs-client-2.0'
|
include ':dd-java-agent:instrumentation:jax-rs-client-2.0'
|
||||||
include ':dd-java-agent:instrumentation:jax-rs-client-2.0:connection-error-handling-jersey'
|
include ':dd-java-agent:instrumentation:jax-rs-client-2.0:connection-error-handling-jersey'
|
||||||
|
|
Loading…
Reference in New Issue