Merge changes from dd-trace-java 0.39.0
https://github.com/DataDog/dd-trace-java/releases/tag/v0.39.0 # Conflicts: # dd-java-agent/instrumentation/akka-http-10.0/src/lagomTest/groovy/LagomTest.groovy # dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy # dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpServerInstrumentationTest.groovy # dd-java-agent/instrumentation/datastax-cassandra-3/src/test/groovy/CassandraClientTest.groovy # dd-java-agent/instrumentation/dropwizard/dropwizard-views/src/test/groovy/ViewRenderTest.groovy # dd-java-agent/instrumentation/dropwizard/src/test/groovy/DropwizardTest.groovy # dd-java-agent/instrumentation/elasticsearch/transport-5.3/src/test/groovy/springdata/Elasticsearch53SpringRepositoryTest.groovy # dd-java-agent/instrumentation/glassfish/src/test/groovy/GlassFishServerTest.groovy # dd-java-agent/instrumentation/google-http-client/src/test/groovy/AbstractGoogleHttpClientTest.groovy # dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcStreamingTest.groovy # dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcTest.groovy # dd-java-agent/instrumentation/hibernate/core-3.3/src/test/groovy/CriteriaTest.groovy # dd-java-agent/instrumentation/hibernate/core-3.3/src/test/groovy/QueryTest.groovy # dd-java-agent/instrumentation/hibernate/core-3.3/src/test/groovy/SessionTest.groovy # dd-java-agent/instrumentation/hibernate/core-4.0/src/test/groovy/CriteriaTest.groovy # dd-java-agent/instrumentation/hibernate/core-4.0/src/test/groovy/QueryTest.groovy # dd-java-agent/instrumentation/hibernate/core-4.0/src/test/groovy/SessionTest.groovy # dd-java-agent/instrumentation/hibernate/core-4.3/src/test/groovy/ProcedureCallTest.groovy # dd-java-agent/instrumentation/hibernate/core-4.3/src/test/groovy/SpringJpaTest.groovy # dd-java-agent/instrumentation/hystrix-1.4/src/test/groovy/HystrixObservableChainTest.groovy # dd-java-agent/instrumentation/hystrix-1.4/src/test/groovy/HystrixObservableTest.groovy # dd-java-agent/instrumentation/hystrix-1.4/src/test/groovy/HystrixTest.groovy # dd-java-agent/instrumentation/java-concurrent/src/slickTest/groovy/SlickTest.groovy # dd-java-agent/instrumentation/jax-rs-annotations-1/src/test/groovy/JerseyTest.groovy # dd-java-agent/instrumentation/jdbc/src/test/groovy/JDBCInstrumentationTest.groovy # dd-java-agent/instrumentation/jedis-1.4/src/test/groovy/JedisClientTest.groovy # dd-java-agent/instrumentation/jetty-8/src/test/groovy/JettyHandlerTest.groovy # dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/JMS2Test.groovy # dd-java-agent/instrumentation/jms/src/test/groovy/JMS1Test.groovy # dd-java-agent/instrumentation/jsp-2.3/src/test/groovy/JSPInstrumentationBasicTests.groovy # dd-java-agent/instrumentation/jsp-2.3/src/test/groovy/JSPInstrumentationForwardTests.groovy # dd-java-agent/instrumentation/kafka-clients-0.11/src/test/groovy/KafkaClientTest.groovy # dd-java-agent/instrumentation/kafka-streams-0.11/src/test/groovy/KafkaStreamsTest.groovy # dd-java-agent/instrumentation/lettuce-5/src/test/groovy/LettuceAsyncClientTest.groovy # dd-java-agent/instrumentation/lettuce-5/src/test/groovy/LettuceReactiveClientTest.groovy # dd-java-agent/instrumentation/lettuce-5/src/test/groovy/LettuceSyncClientTest.groovy # dd-java-agent/instrumentation/netty-4.1/src/test/groovy/Netty41ClientTest.groovy # dd-java-agent/instrumentation/play-2.4/src/test/groovy/server/PlayServerTest.groovy # dd-java-agent/instrumentation/play-2.6/src/test/groovy/server/PlayServerTest.groovy # dd-java-agent/instrumentation/rabbitmq-amqp-2.7/src/test/groovy/RabbitMQTest.groovy # dd-java-agent/instrumentation/ratpack-1.4/src/test/groovy/server/RatpackHttpServerTest.groovy # dd-java-agent/instrumentation/servlet/request-2/src/test/groovy/JettyServlet2Test.groovy # dd-java-agent/instrumentation/servlet/request-3/src/test/groovy/AbstractServlet3Test.groovy # dd-java-agent/instrumentation/servlet/request-3/src/test/groovy/JettyServlet3Test.groovy # dd-java-agent/instrumentation/servlet/request-3/src/test/groovy/TomcatServlet3Test.groovy # dd-java-agent/instrumentation/sparkjava-2.3/src/test/groovy/SparkJavaBasedTest.groovy # dd-java-agent/instrumentation/spring-data-1.8/src/test/groovy/SpringJpaTest.groovy # dd-java-agent/instrumentation/spring-webflux-5/src/test/groovy/SpringWebfluxTest.groovy # dd-java-agent/instrumentation/spring-webflux-5/src/test/groovy/dd/trace/instrumentation/springwebflux/client/SpringWebfluxHttpClientTest.groovy # dd-java-agent/instrumentation/spring-webmvc-3.1/src/test/groovy/test/SpringBootBasedTest.groovy # dd-java-agent/instrumentation/spymemcached-2.12/src/test/groovy/datadog/trace/instrumentation/spymemcached/SpymemcachedTest.groovy # dd-java-agent/instrumentation/trace-annotation/src/test/groovy/ConfiguredTraceAnnotationsTest.groovy # dd-java-agent/instrumentation/trace-annotation/src/test/groovy/TraceAnnotationsTest.groovy # dd-java-agent/instrumentation/trace-annotation/src/test/groovy/TraceConfigTest.groovy # dd-java-agent/instrumentation/twilio/src/test/groovy/test/TwilioClientTest.groovy # dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpClientTest.groovy # dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy # dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/utils/TraceUtils.groovy # dd-trace-ot/src/main/java/datadog/opentracing/ContainerInfo.java # dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java # dd-trace-ot/src/main/java/datadog/trace/common/writer/DDAgentWriter.java # dd-trace-ot/src/test/groovy/datadog/opentracing/decorators/SpanDecoratorTest.groovy # dd-trace-ot/src/test/groovy/datadog/trace/api/writer/DDAgentWriterTest.groovy
This commit is contained in:
commit
92069fd498
|
@ -1,5 +1,7 @@
|
||||||
package datadog.trace.agent.tooling;
|
package datadog.trace.agent.tooling;
|
||||||
|
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.hasSignature;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -7,8 +9,10 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
|
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
|
||||||
|
import net.bytebuddy.description.method.MethodDescription;
|
||||||
import net.bytebuddy.description.type.TypeDefinition;
|
import net.bytebuddy.description.type.TypeDefinition;
|
||||||
import net.bytebuddy.description.type.TypeDescription;
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
import net.bytebuddy.description.type.TypeList;
|
||||||
import net.bytebuddy.matcher.ElementMatcher;
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
import net.bytebuddy.matcher.ElementMatchers;
|
import net.bytebuddy.matcher.ElementMatchers;
|
||||||
|
|
||||||
|
@ -296,4 +300,85 @@ public class ByteBuddyElementMatchers {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add javadoc
|
||||||
|
public static <T extends MethodDescription> ElementMatcher.Junction<T> hasSuperMethod(
|
||||||
|
final ElementMatcher<? super MethodDescription> matcher) {
|
||||||
|
return new HasSuperMethodMatcher<>(matcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add javadoc
|
||||||
|
@HashCodeAndEqualsPlugin.Enhance
|
||||||
|
public static class HasSuperMethodMatcher<T extends MethodDescription>
|
||||||
|
extends ElementMatcher.Junction.AbstractBase<T> {
|
||||||
|
|
||||||
|
private final ElementMatcher<? super MethodDescription> matcher;
|
||||||
|
|
||||||
|
public HasSuperMethodMatcher(final ElementMatcher<? super MethodDescription> matcher) {
|
||||||
|
this.matcher = matcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(final MethodDescription target) {
|
||||||
|
if (target.isConstructor()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final Junction<MethodDescription> signatureMatcher = hasSignature(target.asSignatureToken());
|
||||||
|
TypeDefinition declaringType = target.getDeclaringType();
|
||||||
|
final Set<TypeDefinition> checkedInterfaces = new HashSet<>();
|
||||||
|
|
||||||
|
while (declaringType != null) {
|
||||||
|
for (final MethodDescription methodDescription :
|
||||||
|
declaringType.getDeclaredMethods().filter(signatureMatcher)) {
|
||||||
|
if (matcher.matches(methodDescription)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matchesInterface(declaringType.getInterfaces(), signatureMatcher, checkedInterfaces)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
declaringType = safeGetSuperClass(declaringType);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matchesInterface(
|
||||||
|
final TypeList.Generic interfaces,
|
||||||
|
final Junction<MethodDescription> signatureMatcher,
|
||||||
|
final Set<TypeDefinition> checkedInterfaces) {
|
||||||
|
for (final TypeDefinition type : interfaces) {
|
||||||
|
if (!checkedInterfaces.contains(type)) {
|
||||||
|
checkedInterfaces.add(type);
|
||||||
|
for (final MethodDescription methodDescription :
|
||||||
|
type.getDeclaredMethods().filter(signatureMatcher)) {
|
||||||
|
if (matcher.matches(methodDescription)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matchesInterface(type.getInterfaces(), signatureMatcher, checkedInterfaces)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeDefinition safeGetSuperClass(final TypeDefinition typeDefinition) {
|
||||||
|
try {
|
||||||
|
return typeDefinition.getSuperClass();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.debug(
|
||||||
|
"{} trying to get super class for target {}: {}",
|
||||||
|
e.getClass().getSimpleName(),
|
||||||
|
safeTypeDefinitionName(typeDefinition),
|
||||||
|
e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "hasSuperMethodMatcher(" + matcher + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
package datadog.trace.agent.tooling;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterates over a class, its superclass, and its interfaces in the following breath-first-like
|
||||||
|
* manner:
|
||||||
|
*
|
||||||
|
* <p>1. BaseClass
|
||||||
|
*
|
||||||
|
* <p>2. BaseClass's Interfaces
|
||||||
|
*
|
||||||
|
* <p>3. BaseClass's superclass
|
||||||
|
*
|
||||||
|
* <p>4. BaseClass's Interfaces' Interfaces
|
||||||
|
*
|
||||||
|
* <p>5. Superclass's Interfaces
|
||||||
|
*
|
||||||
|
* <p>6. Superclass's superclass
|
||||||
|
*
|
||||||
|
* <p>...
|
||||||
|
*/
|
||||||
|
public class ClassHierarchyIterable implements Iterable<Class<?>> {
|
||||||
|
private final Class<?> baseClass;
|
||||||
|
|
||||||
|
public ClassHierarchyIterable(final Class baseClass) {
|
||||||
|
this.baseClass = baseClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Class<?>> iterator() {
|
||||||
|
return new ClassIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ClassIterator implements Iterator<Class<?>> {
|
||||||
|
private Class<?> next;
|
||||||
|
private final Set<Class<?>> queuedInterfaces = new HashSet<>();
|
||||||
|
private final Queue<Class<?>> classesToExpand = new ArrayDeque<>();
|
||||||
|
|
||||||
|
public ClassIterator() {
|
||||||
|
classesToExpand.add(baseClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
calculateNextIfNecessary();
|
||||||
|
|
||||||
|
return next != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> next() {
|
||||||
|
calculateNextIfNecessary();
|
||||||
|
|
||||||
|
if (next == null) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Class<?> next = this.next;
|
||||||
|
this.next = null;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
throw new UnsupportedOperationException("remove");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calculateNextIfNecessary() {
|
||||||
|
if (next == null && !classesToExpand.isEmpty()) {
|
||||||
|
next = classesToExpand.remove();
|
||||||
|
queueNewInterfaces(next.getInterfaces());
|
||||||
|
|
||||||
|
final Class<?> superClass = next.getSuperclass();
|
||||||
|
if (superClass != null) {
|
||||||
|
classesToExpand.add(next.getSuperclass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void queueNewInterfaces(final Class[] interfaces) {
|
||||||
|
for (final Class clazz : interfaces) {
|
||||||
|
if (queuedInterfaces.add(clazz)) {
|
||||||
|
classesToExpand.add(clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,6 @@ import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.security.SecureClassLoader;
|
import java.security.SecureClassLoader;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -18,6 +17,7 @@ import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.bytebuddy.agent.builder.AgentBuilder.Transformer;
|
import net.bytebuddy.agent.builder.AgentBuilder.Transformer;
|
||||||
import net.bytebuddy.description.type.TypeDescription;
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
@ -34,12 +34,11 @@ public class HelperInjector implements Transformer {
|
||||||
new SecureClassLoader(null) {};
|
new SecureClassLoader(null) {};
|
||||||
|
|
||||||
private final Set<String> helperClassNames;
|
private final Set<String> helperClassNames;
|
||||||
private Map<TypeDescription, byte[]> helperMap = null;
|
private final Map<String, byte[]> dynamicTypeMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
private final WeakMap<ClassLoader, Boolean> injectedClassLoaders = newWeakMap();
|
private final WeakMap<ClassLoader, Boolean> injectedClassLoaders = newWeakMap();
|
||||||
|
|
||||||
// Neither Module nor WeakReference implements equals or hashcode so using a list
|
private final List<WeakReference<Object>> helperModules = new CopyOnWriteArrayList<>();
|
||||||
private final List<WeakReference<Object>> helperModules = new ArrayList<>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct HelperInjector.
|
* Construct HelperInjector.
|
||||||
*
|
*
|
||||||
|
@ -55,13 +54,7 @@ public class HelperInjector implements Transformer {
|
||||||
|
|
||||||
public HelperInjector(final Map<String, byte[]> helperMap) {
|
public HelperInjector(final Map<String, byte[]> helperMap) {
|
||||||
helperClassNames = helperMap.keySet();
|
helperClassNames = helperMap.keySet();
|
||||||
this.helperMap = new LinkedHashMap<>(helperClassNames.size());
|
dynamicTypeMap.putAll(helperMap);
|
||||||
for (final String helperName : helperClassNames) {
|
|
||||||
final TypeDescription typeDesc =
|
|
||||||
new TypeDescription.Latent(
|
|
||||||
helperName, 0, null, Collections.<TypeDescription.Generic>emptyList());
|
|
||||||
this.helperMap.put(typeDesc, helperMap.get(helperName));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HelperInjector forDynamicTypes(final Collection<DynamicType.Unloaded<?>> helpers) {
|
public static HelperInjector forDynamicTypes(final Collection<DynamicType.Unloaded<?>> helpers) {
|
||||||
|
@ -72,20 +65,22 @@ public class HelperInjector implements Transformer {
|
||||||
return new HelperInjector(bytes);
|
return new HelperInjector(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized Map<TypeDescription, byte[]> getHelperMap() throws IOException {
|
private Map<String, byte[]> getHelperMap() throws IOException {
|
||||||
if (helperMap == null) {
|
if (dynamicTypeMap.isEmpty()) {
|
||||||
helperMap = new LinkedHashMap<>(helperClassNames.size());
|
final Map<String, byte[]> classnameToBytes = new LinkedHashMap<>();
|
||||||
for (final String helperName : helperClassNames) {
|
|
||||||
final ClassFileLocator locator =
|
final ClassFileLocator locator =
|
||||||
ClassFileLocator.ForClassLoader.of(Utils.getAgentClassLoader());
|
ClassFileLocator.ForClassLoader.of(Utils.getAgentClassLoader());
|
||||||
final byte[] classBytes = locator.locate(helperName).resolve();
|
|
||||||
final TypeDescription typeDesc =
|
for (final String helperClassName : helperClassNames) {
|
||||||
new TypeDescription.Latent(
|
final byte[] classBytes = locator.locate(helperClassName).resolve();
|
||||||
helperName, 0, null, Collections.<TypeDescription.Generic>emptyList());
|
classnameToBytes.put(helperClassName, classBytes);
|
||||||
helperMap.put(typeDesc, classBytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return classnameToBytes;
|
||||||
|
} else {
|
||||||
|
return dynamicTypeMap;
|
||||||
}
|
}
|
||||||
return helperMap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -95,55 +90,53 @@ public class HelperInjector implements Transformer {
|
||||||
ClassLoader classLoader,
|
ClassLoader classLoader,
|
||||||
final JavaModule module) {
|
final JavaModule module) {
|
||||||
if (!helperClassNames.isEmpty()) {
|
if (!helperClassNames.isEmpty()) {
|
||||||
synchronized (this) {
|
if (classLoader == BOOTSTRAP_CLASSLOADER) {
|
||||||
if (classLoader == BOOTSTRAP_CLASSLOADER) {
|
classLoader = BOOTSTRAP_CLASSLOADER_PLACEHOLDER;
|
||||||
classLoader = BOOTSTRAP_CLASSLOADER_PLACEHOLDER;
|
}
|
||||||
}
|
|
||||||
if (!injectedClassLoaders.containsKey(classLoader)) {
|
if (!injectedClassLoaders.containsKey(classLoader)) {
|
||||||
try {
|
try {
|
||||||
final Map<TypeDescription, byte[]> helperMap = getHelperMap();
|
log.debug("Injecting classes onto classloader {} -> {}", classLoader, helperClassNames);
|
||||||
log.debug("Injecting classes onto classloader {} -> {}", classLoader, helperClassNames);
|
|
||||||
if (classLoader == BOOTSTRAP_CLASSLOADER_PLACEHOLDER) {
|
final Map<String, byte[]> classnameToBytes = getHelperMap();
|
||||||
final Map<TypeDescription, Class<?>> injected =
|
final Map<String, Class<?>> classes;
|
||||||
ClassInjector.UsingInstrumentation.of(
|
if (classLoader == BOOTSTRAP_CLASSLOADER_PLACEHOLDER) {
|
||||||
new File(System.getProperty("java.io.tmpdir")),
|
classes =
|
||||||
ClassInjector.UsingInstrumentation.Target.BOOTSTRAP,
|
ClassInjector.UsingInstrumentation.of(
|
||||||
AgentInstaller.getInstrumentation())
|
new File(System.getProperty("java.io.tmpdir")),
|
||||||
.inject(helperMap);
|
ClassInjector.UsingInstrumentation.Target.BOOTSTRAP,
|
||||||
for (final TypeDescription desc : injected.keySet()) {
|
AgentInstaller.getInstrumentation())
|
||||||
final Class<?> injectedClass =
|
.injectRaw(classnameToBytes);
|
||||||
Class.forName(desc.getName(), false, Utils.getBootstrapProxy());
|
} else {
|
||||||
if (JavaModule.isSupported()) {
|
classes = new ClassInjector.UsingReflection(classLoader).injectRaw(classnameToBytes);
|
||||||
helperModules.add(new WeakReference<>(JavaModule.ofType(injectedClass).unwrap()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final Map<TypeDescription, Class<?>> classMap =
|
|
||||||
new ClassInjector.UsingReflection(classLoader).inject(helperMap);
|
|
||||||
if (JavaModule.isSupported()) {
|
|
||||||
for (final Class<?> injectedClass : classMap.values()) {
|
|
||||||
helperModules.add(new WeakReference<>(JavaModule.ofType(injectedClass).unwrap()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (final Exception e) {
|
|
||||||
log.error(
|
|
||||||
"Error preparing helpers for "
|
|
||||||
+ typeDescription
|
|
||||||
+ ". Failed to inject helper classes into instance "
|
|
||||||
+ classLoader
|
|
||||||
+ " of type "
|
|
||||||
+ (classLoader == BOOTSTRAP_CLASSLOADER_PLACEHOLDER
|
|
||||||
? "<bootstrap>"
|
|
||||||
: classLoader.getClass().getName()),
|
|
||||||
e);
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
injectedClassLoaders.put(classLoader, true);
|
|
||||||
|
// All datadog helper classes are in the unnamed module
|
||||||
|
// And there's exactly one unnamed module per classloader
|
||||||
|
// Use the module of the first class for convenience
|
||||||
|
if (JavaModule.isSupported()) {
|
||||||
|
final JavaModule javaModule = JavaModule.ofType(classes.values().iterator().next());
|
||||||
|
helperModules.add(new WeakReference<>(javaModule.unwrap()));
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
final String classLoaderType =
|
||||||
|
classLoader == BOOTSTRAP_CLASSLOADER_PLACEHOLDER
|
||||||
|
? "<bootstrap>"
|
||||||
|
: classLoader.getClass().getName();
|
||||||
|
|
||||||
|
log.error(
|
||||||
|
"Error preparing helpers for {}. Failed to inject helper classes into instance {} of type {}",
|
||||||
|
typeDescription,
|
||||||
|
classLoader,
|
||||||
|
classLoaderType,
|
||||||
|
e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureModuleCanReadHelperModules(module);
|
injectedClassLoaders.put(classLoader, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureModuleCanReadHelperModules(module);
|
||||||
}
|
}
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,23 @@
|
||||||
apply from: "${rootDir}/gradle/java.gradle"
|
apply from: "${rootDir}/gradle/java.gradle"
|
||||||
|
|
||||||
|
//apply plugin: 'org.unbroken-dome.test-sets'
|
||||||
|
//
|
||||||
|
//testSets {
|
||||||
|
// latestDepTest {
|
||||||
|
// dirName = 'test'
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testCompile project(':dd-java-agent:instrumentation:java-concurrent')
|
||||||
|
testCompile project(':dd-java-agent:instrumentation:jax-rs-annotations-2')
|
||||||
|
testCompile project(':dd-java-agent:instrumentation:servlet:request-3')
|
||||||
|
|
||||||
|
// 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'
|
||||||
|
|
||||||
|
// Anything 1.0+ fails with a java.lang.NoClassDefFoundError: org/eclipse/jetty/server/RequestLog
|
||||||
|
// latestDepTestCompile group: 'io.dropwizard', name: 'dropwizard-testing', version: '1.+'
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package datadog.trace.instrumentation.jaxrs1;
|
||||||
import static datadog.trace.bootstrap.WeakMap.Provider.newWeakMap;
|
import static datadog.trace.bootstrap.WeakMap.Provider.newWeakMap;
|
||||||
|
|
||||||
import datadog.trace.agent.decorator.BaseDecorator;
|
import datadog.trace.agent.decorator.BaseDecorator;
|
||||||
|
import datadog.trace.agent.tooling.ClassHierarchyIterable;
|
||||||
import datadog.trace.api.DDSpanTypes;
|
import datadog.trace.api.DDSpanTypes;
|
||||||
import datadog.trace.api.DDTags;
|
import datadog.trace.api.DDTags;
|
||||||
import datadog.trace.bootstrap.WeakMap;
|
import datadog.trace.bootstrap.WeakMap;
|
||||||
|
@ -10,8 +11,6 @@ import datadog.trace.instrumentation.api.AgentSpan;
|
||||||
import datadog.trace.instrumentation.api.Tags;
|
import datadog.trace.instrumentation.api.Tags;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import javax.ws.rs.HttpMethod;
|
import javax.ws.rs.HttpMethod;
|
||||||
|
@ -83,9 +82,31 @@ public class JaxRsAnnotationsDecorator extends BaseDecorator {
|
||||||
|
|
||||||
String resourceName = classMap.get(method);
|
String resourceName = classMap.get(method);
|
||||||
if (resourceName == null) {
|
if (resourceName == null) {
|
||||||
final String httpMethod = locateHttpMethod(method);
|
String httpMethod = null;
|
||||||
final List<Path> paths = gatherPaths(target, method);
|
Path methodPath = null;
|
||||||
resourceName = buildResourceName(httpMethod, paths);
|
final Path classPath = findClassPath(target);
|
||||||
|
for (final Class currentClass : new ClassHierarchyIterable(target)) {
|
||||||
|
final Method currentMethod;
|
||||||
|
if (currentClass.equals(target)) {
|
||||||
|
currentMethod = method;
|
||||||
|
} else {
|
||||||
|
currentMethod = findMatchingMethod(method, currentClass.getDeclaredMethods());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentMethod != null) {
|
||||||
|
if (httpMethod == null) {
|
||||||
|
httpMethod = locateHttpMethod(currentMethod);
|
||||||
|
}
|
||||||
|
if (methodPath == null) {
|
||||||
|
methodPath = findMethodPath(currentMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (httpMethod != null && methodPath != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resourceName = buildResourceName(httpMethod, classPath, methodPath);
|
||||||
classMap.put(method, resourceName);
|
classMap.put(method, resourceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,40 +123,77 @@ public class JaxRsAnnotationsDecorator extends BaseDecorator {
|
||||||
return httpMethod;
|
return httpMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Path> gatherPaths(Class<Object> target, final Method method) {
|
private Path findMethodPath(final Method method) {
|
||||||
final List<Path> paths = new ArrayList();
|
return method.getAnnotation(Path.class);
|
||||||
while (target != null && target != Object.class) {
|
|
||||||
final Path annotation = target.getAnnotation(Path.class);
|
|
||||||
if (annotation != null) {
|
|
||||||
paths.add(annotation);
|
|
||||||
break; // Annotation overridden, no need to continue.
|
|
||||||
}
|
|
||||||
target = target.getSuperclass();
|
|
||||||
}
|
|
||||||
final Path methodPath = method.getAnnotation(Path.class);
|
|
||||||
if (methodPath != null) {
|
|
||||||
paths.add(methodPath);
|
|
||||||
}
|
|
||||||
return paths;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildResourceName(final String httpMethod, final List<Path> paths) {
|
private Path findClassPath(final Class<Object> target) {
|
||||||
|
for (final Class<?> currentClass : new ClassHierarchyIterable(target)) {
|
||||||
|
final Path annotation = currentClass.getAnnotation(Path.class);
|
||||||
|
if (annotation != null) {
|
||||||
|
// Annotation overridden, no need to continue.
|
||||||
|
return annotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method findMatchingMethod(final Method baseMethod, final Method[] methods) {
|
||||||
|
nextMethod:
|
||||||
|
for (final Method method : methods) {
|
||||||
|
if (!baseMethod.getReturnType().equals(method.getReturnType())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!baseMethod.getName().equals(method.getName())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Class<?>[] baseParameterTypes = baseMethod.getParameterTypes();
|
||||||
|
final Class<?>[] parameterTypes = method.getParameterTypes();
|
||||||
|
if (baseParameterTypes.length != parameterTypes.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < baseParameterTypes.length; i++) {
|
||||||
|
if (!baseParameterTypes[i].equals(parameterTypes[i])) {
|
||||||
|
continue nextMethod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildResourceName(
|
||||||
|
final String httpMethod, final Path classPath, final Path methodPath) {
|
||||||
final String resourceName;
|
final String resourceName;
|
||||||
final StringBuilder resourceNameBuilder = new StringBuilder();
|
final StringBuilder resourceNameBuilder = new StringBuilder();
|
||||||
if (httpMethod != null) {
|
if (httpMethod != null) {
|
||||||
resourceNameBuilder.append(httpMethod);
|
resourceNameBuilder.append(httpMethod);
|
||||||
resourceNameBuilder.append(" ");
|
resourceNameBuilder.append(" ");
|
||||||
}
|
}
|
||||||
Path last = null;
|
boolean skipSlash = false;
|
||||||
for (final Path path : paths) {
|
if (classPath != null) {
|
||||||
if (path.value().startsWith("/") || (last != null && last.value().endsWith("/"))) {
|
if (!classPath.value().startsWith("/")) {
|
||||||
resourceNameBuilder.append(path.value());
|
|
||||||
} else {
|
|
||||||
resourceNameBuilder.append("/");
|
resourceNameBuilder.append("/");
|
||||||
resourceNameBuilder.append(path.value());
|
|
||||||
}
|
}
|
||||||
last = path;
|
resourceNameBuilder.append(classPath.value());
|
||||||
|
skipSlash = classPath.value().endsWith("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (methodPath != null) {
|
||||||
|
String path = methodPath.value();
|
||||||
|
if (skipSlash) {
|
||||||
|
if (path.startsWith("/")) {
|
||||||
|
path = path.length() == 1 ? "" : path.substring(1);
|
||||||
|
}
|
||||||
|
} else if (!path.startsWith("/")) {
|
||||||
|
resourceNameBuilder.append("/");
|
||||||
|
}
|
||||||
|
resourceNameBuilder.append(path);
|
||||||
|
}
|
||||||
|
|
||||||
resourceName = resourceNameBuilder.toString().trim();
|
resourceName = resourceNameBuilder.toString().trim();
|
||||||
return resourceName;
|
return resourceName;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package datadog.trace.instrumentation.jaxrs1;
|
package datadog.trace.instrumentation.jaxrs1;
|
||||||
|
|
||||||
|
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.hasSuperMethod;
|
||||||
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
|
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
|
||||||
import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClasses;
|
import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClasses;
|
||||||
import static datadog.trace.instrumentation.api.AgentTracer.activateSpan;
|
import static datadog.trace.instrumentation.api.AgentTracer.activateSpan;
|
||||||
|
@ -9,6 +10,7 @@ import static datadog.trace.instrumentation.jaxrs1.JaxRsAnnotationsDecorator.DEC
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
|
import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
|
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.not;
|
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||||
|
|
||||||
|
@ -48,21 +50,27 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default
|
||||||
@Override
|
@Override
|
||||||
public String[] helperClassNames() {
|
public String[] helperClassNames() {
|
||||||
return new String[] {
|
return new String[] {
|
||||||
"datadog.trace.agent.decorator.BaseDecorator", packageName + ".JaxRsAnnotationsDecorator",
|
"datadog.trace.agent.decorator.BaseDecorator",
|
||||||
|
"datadog.trace.agent.tooling.ClassHierarchyIterable",
|
||||||
|
"datadog.trace.agent.tooling.ClassHierarchyIterable$ClassIterator",
|
||||||
|
packageName + ".JaxRsAnnotationsDecorator",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||||
return singletonMap(
|
return singletonMap(
|
||||||
isAnnotatedWith(
|
isMethod()
|
||||||
named("javax.ws.rs.Path")
|
.and(
|
||||||
.or(named("javax.ws.rs.DELETE"))
|
hasSuperMethod(
|
||||||
.or(named("javax.ws.rs.GET"))
|
isAnnotatedWith(
|
||||||
.or(named("javax.ws.rs.HEAD"))
|
named("javax.ws.rs.Path")
|
||||||
.or(named("javax.ws.rs.OPTIONS"))
|
.or(named("javax.ws.rs.DELETE"))
|
||||||
.or(named("javax.ws.rs.POST"))
|
.or(named("javax.ws.rs.GET"))
|
||||||
.or(named("javax.ws.rs.PUT"))),
|
.or(named("javax.ws.rs.HEAD"))
|
||||||
|
.or(named("javax.ws.rs.OPTIONS"))
|
||||||
|
.or(named("javax.ws.rs.POST"))
|
||||||
|
.or(named("javax.ws.rs.PUT"))))),
|
||||||
JaxRsAnnotationsInstrumentation.class.getName() + "$JaxRsAnnotationsAdvice");
|
JaxRsAnnotationsInstrumentation.class.getName() + "$JaxRsAnnotationsAdvice");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,55 +87,55 @@ class JaxRsAnnotationsInstrumentationTest extends AgentTestRunner {
|
||||||
resourceNames.get(obj.class).size() == 1
|
resourceNames.get(obj.class).size() == 1
|
||||||
|
|
||||||
where:
|
where:
|
||||||
name | obj
|
name | obj
|
||||||
"/a" | new Jax() {
|
"/a" | new Jax() {
|
||||||
@Path("/a")
|
@Path("/a")
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"GET /b" | new Jax() {
|
"GET /b" | new Jax() {
|
||||||
@GET
|
@GET
|
||||||
@Path("/b")
|
@Path("/b")
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"POST /c" | new InterfaceWithPath() {
|
"POST /interface/c" | new InterfaceWithPath() {
|
||||||
@POST
|
@POST
|
||||||
@Path("/c")
|
@Path("/c")
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"HEAD" | new InterfaceWithPath() {
|
"HEAD /interface" | new InterfaceWithPath() {
|
||||||
@HEAD
|
@HEAD
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"POST /abstract/d" | new AbstractClassWithPath() {
|
"POST /abstract/d" | new AbstractClassWithPath() {
|
||||||
@POST
|
@POST
|
||||||
@Path("/d")
|
@Path("/d")
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"PUT /abstract" | new AbstractClassWithPath() {
|
"PUT /abstract" | new AbstractClassWithPath() {
|
||||||
@PUT
|
@PUT
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"OPTIONS /child/e" | new ChildClassWithPath() {
|
"OPTIONS /child/e" | new ChildClassWithPath() {
|
||||||
@OPTIONS
|
@OPTIONS
|
||||||
@Path("/e")
|
@Path("/e")
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"DELETE /child" | new ChildClassWithPath() {
|
"DELETE /child/call" | new ChildClassWithPath() {
|
||||||
@DELETE
|
@DELETE
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"POST /child/call" | new ChildClassWithPath()
|
"POST /child/call" | new ChildClassWithPath()
|
||||||
"GET /child/call" | new JavaInterfaces.ChildClassOnInterface()
|
"GET /child/call" | new JavaInterfaces.ChildClassOnInterface()
|
||||||
// TODO: uncomment when we drop support for Java 7
|
// TODO: uncomment when we drop support for Java 7
|
||||||
// "GET /child/invoke" | new JavaInterfaces.DefaultChildClassOnInterface()
|
// "GET /child/invoke" | new JavaInterfaces.DefaultChildClassOnInterface()
|
||||||
|
|
||||||
className = getName(obj.class)
|
className = getName(obj.class)
|
||||||
|
|
||||||
|
@ -168,18 +168,6 @@ class JaxRsAnnotationsInstrumentationTest extends AgentTestRunner {
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
} | _
|
} | _
|
||||||
new InterfaceWithPath() {
|
|
||||||
void call() {
|
|
||||||
}
|
|
||||||
} | _
|
|
||||||
new AbstractClassWithPath() {
|
|
||||||
void call() {
|
|
||||||
}
|
|
||||||
} | _
|
|
||||||
new ChildClassWithPath() {
|
|
||||||
void call() {
|
|
||||||
}
|
|
||||||
} | _
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Jax {
|
interface Jax {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import datadog.trace.agent.test.AgentTestRunner
|
import datadog.trace.agent.test.AgentTestRunner
|
||||||
|
import datadog.trace.api.DDSpanTypes
|
||||||
import datadog.trace.api.DDTags
|
import datadog.trace.api.DDTags
|
||||||
import datadog.trace.instrumentation.api.Tags
|
import datadog.trace.instrumentation.api.Tags
|
||||||
import io.dropwizard.testing.junit.ResourceTestRule
|
import io.dropwizard.testing.junit.ResourceTestRule
|
||||||
|
@ -11,23 +12,50 @@ class JerseyTest extends AgentTestRunner {
|
||||||
|
|
||||||
@Shared
|
@Shared
|
||||||
@ClassRule
|
@ClassRule
|
||||||
ResourceTestRule resources = ResourceTestRule.builder().addResource(new Resource.Test()).build()
|
ResourceTestRule resources = ResourceTestRule.builder()
|
||||||
|
.addResource(new Resource.Test1())
|
||||||
|
.addResource(new Resource.Test2())
|
||||||
|
.addResource(new Resource.Test3())
|
||||||
|
.build()
|
||||||
|
|
||||||
def "test resource"() {
|
def "test #resource"() {
|
||||||
setup:
|
when:
|
||||||
// start a trace because the test doesn't go through any servlet or other instrumentation.
|
// start a trace because the test doesn't go through any servlet or other instrumentation.
|
||||||
def response = runUnderTrace("test.span") {
|
def response = runUnderTrace("test.span") {
|
||||||
resources.client().resource("/test/hello/bob").post(String)
|
resources.client().resource(resource).post(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
expect:
|
then:
|
||||||
response == "Hello bob!"
|
response == expectedResponse
|
||||||
TEST_WRITER.waitForTraces(1)
|
|
||||||
TEST_WRITER.size() == 1
|
|
||||||
|
|
||||||
def trace = TEST_WRITER.firstTrace()
|
assertTraces(1) {
|
||||||
def span = trace[0]
|
trace(0, 2) {
|
||||||
span.tags[DDTags.RESOURCE_NAME] == "POST /test/hello/{name}"
|
span(0) {
|
||||||
span.tags[Tags.COMPONENT] == "jax-rs"
|
operationName "test.span"
|
||||||
|
tags {
|
||||||
|
"$DDTags.RESOURCE_NAME" expectedResourceName
|
||||||
|
"$Tags.COMPONENT" "jax-rs"
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span(1) {
|
||||||
|
childOf span(0)
|
||||||
|
operationName "jax-rs.request"
|
||||||
|
tags {
|
||||||
|
"$DDTags.RESOURCE_NAME" controllerName
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
|
||||||
|
"$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!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,39 @@ import javax.ws.rs.PathParam;
|
||||||
// Originally had this as a groovy class but was getting some weird errors.
|
// Originally had this as a groovy class but was getting some weird errors.
|
||||||
@Path("/ignored")
|
@Path("/ignored")
|
||||||
public interface Resource {
|
public interface Resource {
|
||||||
|
@Path("ignored")
|
||||||
|
String hello(final String name);
|
||||||
|
|
||||||
@Path("/test")
|
@Path("/test")
|
||||||
class Test implements Resource {
|
interface SubResource extends Cloneable, Resource {
|
||||||
|
@Override
|
||||||
@POST
|
@POST
|
||||||
@Path("/hello/{name}")
|
@Path("/hello/{name}")
|
||||||
public String addBook(@PathParam("name") final String name) {
|
String hello(@PathParam("name") final String name);
|
||||||
return "Hello " + name + "!";
|
}
|
||||||
|
|
||||||
|
class Test1 implements SubResource {
|
||||||
|
@Override
|
||||||
|
public String hello(final String name) {
|
||||||
|
return "Test1 " + name + "!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path("/test2")
|
||||||
|
class Test2 implements SubResource {
|
||||||
|
@Override
|
||||||
|
public String hello(final String name) {
|
||||||
|
return "Test2 " + name + "!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path("/test3")
|
||||||
|
class Test3 implements SubResource {
|
||||||
|
@Override
|
||||||
|
@POST
|
||||||
|
@Path("/hi/{name}")
|
||||||
|
public String hello(@PathParam("name") final String name) {
|
||||||
|
return "Test3 " + name + "!";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,47 @@
|
||||||
|
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 context instrumentation.
|
||||||
|
*
|
||||||
|
* <p>JAX-RS does not define a way to get the matched resource method from the <code>
|
||||||
|
* ContainerRequestContext</code>
|
||||||
|
*
|
||||||
|
* <p>In the Jersey implementation, <code>UriInfo</code> implements <code>ResourceInfo</code>. The
|
||||||
|
* matched resource method can be retrieved from that object
|
||||||
|
*/
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public class JerseyRequestContextInstrumentation extends AbstractRequestContextInstrumentation {
|
||||||
|
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(JaxRsAnnotationsDecorator.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,48 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RESTEasy specific context instrumentation.
|
||||||
|
*
|
||||||
|
* <p>JAX-RS does not define a way to get the matched resource method from the <code>
|
||||||
|
* ContainerRequestContext</code>
|
||||||
|
*
|
||||||
|
* <p>In the RESTEasy implementation, <code>ContainerRequestContext</code> is implemented by <code>
|
||||||
|
* PostMatchContainerRequestContext</code>. This class provides a way to get the matched resource
|
||||||
|
* method through <code>getResourceMethod()</code>.
|
||||||
|
*/
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public class Resteasy30RequestContextInstrumentation extends AbstractRequestContextInstrumentation {
|
||||||
|
public static class ContainerRequestContextAdvice {
|
||||||
|
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static AgentScope decorateAbortSpan(@Advice.This final ContainerRequestContext context) {
|
||||||
|
if (context.getProperty(JaxRsAnnotationsDecorator.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,47 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RESTEasy specific context instrumentation.
|
||||||
|
*
|
||||||
|
* <p>JAX-RS does not define a way to get the matched resource method from the <code>
|
||||||
|
* ContainerRequestContext</code>
|
||||||
|
*
|
||||||
|
* <p>In the RESTEasy implementation, <code>ContainerRequestContext</code> is implemented by <code>
|
||||||
|
* PostMatchContainerRequestContext</code>. This class provides a way to get the matched resource
|
||||||
|
* method through <code>getResourceMethod()</code>.
|
||||||
|
*/
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public class Resteasy31RequestContextInstrumentation extends AbstractRequestContextInstrumentation {
|
||||||
|
public static class ContainerRequestContextAdvice {
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static AgentScope decorateAbortSpan(@Advice.This final ContainerRequestContext context) {
|
||||||
|
if (context.getProperty(JaxRsAnnotationsDecorator.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,25 +13,43 @@ muzzle {
|
||||||
|
|
||||||
apply from: "${rootDir}/gradle/java.gradle"
|
apply from: "${rootDir}/gradle/java.gradle"
|
||||||
|
|
||||||
//apply plugin: 'org.unbroken-dome.test-sets'
|
apply plugin: 'org.unbroken-dome.test-sets'
|
||||||
//
|
|
||||||
//testSets {
|
testSets {
|
||||||
// latestDepTest {
|
latestDepTest {
|
||||||
// dirName = 'test'
|
dirName = 'test'
|
||||||
// }
|
}
|
||||||
//}
|
|
||||||
|
resteasy31Test {
|
||||||
|
dirName = 'test'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: '2.0'
|
compileOnly group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: '2.0'
|
||||||
|
|
||||||
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'
|
||||||
|
|
||||||
// Anything 1.0+ fails with a java.lang.NoClassDefFoundError: org/eclipse/jetty/server/RequestLog
|
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'
|
||||||
|
|
||||||
|
resteasy31TestCompile(group: 'org.jboss.resteasy', name: 'resteasy-jaxrs', version: '3.1.0.Final') {
|
||||||
|
force = true
|
||||||
|
}
|
||||||
|
|
||||||
|
latestDepTestCompile group: 'org.jboss.resteasy', name: 'resteasy-jaxrs', version: '+'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test.dependsOn resteasy31Test
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
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 AbstractRequestContextInstrumentation extends Instrumenter.Default {
|
||||||
|
public AbstractRequestContextInstrumentation() {
|
||||||
|
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",
|
||||||
|
AbstractRequestContextInstrumentation.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(JaxRsAnnotationsDecorator.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(JaxRsAnnotationsDecorator.ABORT_PARENT);
|
||||||
|
AgentSpan span = (AgentSpan) context.getProperty(JaxRsAnnotationsDecorator.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.onJaxRsSpan(span, parent, resourceClass, method);
|
||||||
|
|
||||||
|
return scope;
|
||||||
|
} else {
|
||||||
|
DECORATE.onJaxRsSpan(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,57 @@
|
||||||
|
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. The class name is used by <code>
|
||||||
|
* DefaultRequestContextInstrumentation</code>
|
||||||
|
*/
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public class ContainerRequestFilterInstrumentation extends Instrumenter.Default {
|
||||||
|
|
||||||
|
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(JaxRsAnnotationsDecorator.ABORT_FILTER_CLASS, filter.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package datadog.trace.instrumentation.jaxrs2;
|
||||||
|
|
||||||
|
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 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 javax.ws.rs.container.ContainerRequestContext;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default context instrumentation.
|
||||||
|
*
|
||||||
|
* <p>JAX-RS does not define a way to get the matched resource method from the <code>
|
||||||
|
* ContainerRequestContext</code>
|
||||||
|
*
|
||||||
|
* <p>This default instrumentation uses the class name of the filter to create the span. More
|
||||||
|
* specific instrumentations may override this value.
|
||||||
|
*/
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public class DefaultRequestContextInstrumentation extends AbstractRequestContextInstrumentation {
|
||||||
|
public static class ContainerRequestContextAdvice {
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static AgentScope createGenericSpan(@Advice.This final ContainerRequestContext context) {
|
||||||
|
|
||||||
|
if (context.getProperty(JaxRsAnnotationsDecorator.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(JaxRsAnnotationsDecorator.ABORT_PARENT, parent);
|
||||||
|
context.setProperty(JaxRsAnnotationsDecorator.ABORT_SPAN, span);
|
||||||
|
|
||||||
|
final Class filterClass =
|
||||||
|
(Class) context.getProperty(JaxRsAnnotationsDecorator.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.onJaxRsSpan(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package datadog.trace.instrumentation.jaxrs2;
|
||||||
import static datadog.trace.bootstrap.WeakMap.Provider.newWeakMap;
|
import static datadog.trace.bootstrap.WeakMap.Provider.newWeakMap;
|
||||||
|
|
||||||
import datadog.trace.agent.decorator.BaseDecorator;
|
import datadog.trace.agent.decorator.BaseDecorator;
|
||||||
|
import datadog.trace.agent.tooling.ClassHierarchyIterable;
|
||||||
import datadog.trace.api.DDSpanTypes;
|
import datadog.trace.api.DDSpanTypes;
|
||||||
import datadog.trace.api.DDTags;
|
import datadog.trace.api.DDTags;
|
||||||
import datadog.trace.bootstrap.WeakMap;
|
import datadog.trace.bootstrap.WeakMap;
|
||||||
|
@ -10,15 +11,21 @@ import datadog.trace.instrumentation.api.AgentSpan;
|
||||||
import datadog.trace.instrumentation.api.Tags;
|
import datadog.trace.instrumentation.api.Tags;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import javax.ws.rs.HttpMethod;
|
import javax.ws.rs.HttpMethod;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
|
|
||||||
public class JaxRsAnnotationsDecorator extends BaseDecorator {
|
public class JaxRsAnnotationsDecorator extends BaseDecorator {
|
||||||
public static JaxRsAnnotationsDecorator DECORATE = new JaxRsAnnotationsDecorator();
|
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 static final JaxRsAnnotationsDecorator DECORATE = new JaxRsAnnotationsDecorator();
|
||||||
|
|
||||||
private final WeakMap<Class, Map<Method, String>> resourceNames = newWeakMap();
|
private final WeakMap<Class, Map<Method, String>> resourceNames = newWeakMap();
|
||||||
|
|
||||||
|
@ -37,8 +44,9 @@ public class JaxRsAnnotationsDecorator extends BaseDecorator {
|
||||||
return "jax-rs-controller";
|
return "jax-rs-controller";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onControllerStart(
|
public void onJaxRsSpan(
|
||||||
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);
|
||||||
updateParent(parent, resourceName);
|
updateParent(parent, resourceName);
|
||||||
|
|
||||||
|
@ -49,7 +57,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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,9 +93,31 @@ public class JaxRsAnnotationsDecorator extends BaseDecorator {
|
||||||
|
|
||||||
String resourceName = classMap.get(method);
|
String resourceName = classMap.get(method);
|
||||||
if (resourceName == null) {
|
if (resourceName == null) {
|
||||||
final String httpMethod = locateHttpMethod(method);
|
String httpMethod = null;
|
||||||
final List<Path> paths = gatherPaths(target, method);
|
Path methodPath = null;
|
||||||
resourceName = buildResourceName(httpMethod, paths);
|
final Path classPath = findClassPath(target);
|
||||||
|
for (final Class currentClass : new ClassHierarchyIterable(target)) {
|
||||||
|
final Method currentMethod;
|
||||||
|
if (currentClass.equals(target)) {
|
||||||
|
currentMethod = method;
|
||||||
|
} else {
|
||||||
|
currentMethod = findMatchingMethod(method, currentClass.getDeclaredMethods());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentMethod != null) {
|
||||||
|
if (httpMethod == null) {
|
||||||
|
httpMethod = locateHttpMethod(currentMethod);
|
||||||
|
}
|
||||||
|
if (methodPath == null) {
|
||||||
|
methodPath = findMethodPath(currentMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (httpMethod != null && methodPath != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resourceName = buildResourceName(httpMethod, classPath, methodPath);
|
||||||
classMap.put(method, resourceName);
|
classMap.put(method, resourceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,40 +134,77 @@ public class JaxRsAnnotationsDecorator extends BaseDecorator {
|
||||||
return httpMethod;
|
return httpMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Path> gatherPaths(Class<Object> target, final Method method) {
|
private Path findMethodPath(final Method method) {
|
||||||
final List<Path> paths = new ArrayList();
|
return method.getAnnotation(Path.class);
|
||||||
while (target != null && target != Object.class) {
|
|
||||||
final Path annotation = target.getAnnotation(Path.class);
|
|
||||||
if (annotation != null) {
|
|
||||||
paths.add(annotation);
|
|
||||||
break; // Annotation overridden, no need to continue.
|
|
||||||
}
|
|
||||||
target = target.getSuperclass();
|
|
||||||
}
|
|
||||||
final Path methodPath = method.getAnnotation(Path.class);
|
|
||||||
if (methodPath != null) {
|
|
||||||
paths.add(methodPath);
|
|
||||||
}
|
|
||||||
return paths;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildResourceName(final String httpMethod, final List<Path> paths) {
|
private Path findClassPath(final Class<Object> target) {
|
||||||
|
for (final Class<?> currentClass : new ClassHierarchyIterable(target)) {
|
||||||
|
final Path annotation = currentClass.getAnnotation(Path.class);
|
||||||
|
if (annotation != null) {
|
||||||
|
// Annotation overridden, no need to continue.
|
||||||
|
return annotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method findMatchingMethod(final Method baseMethod, final Method[] methods) {
|
||||||
|
nextMethod:
|
||||||
|
for (final Method method : methods) {
|
||||||
|
if (!baseMethod.getReturnType().equals(method.getReturnType())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!baseMethod.getName().equals(method.getName())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Class<?>[] baseParameterTypes = baseMethod.getParameterTypes();
|
||||||
|
final Class<?>[] parameterTypes = method.getParameterTypes();
|
||||||
|
if (baseParameterTypes.length != parameterTypes.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < baseParameterTypes.length; i++) {
|
||||||
|
if (!baseParameterTypes[i].equals(parameterTypes[i])) {
|
||||||
|
continue nextMethod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildResourceName(
|
||||||
|
final String httpMethod, final Path classPath, final Path methodPath) {
|
||||||
final String resourceName;
|
final String resourceName;
|
||||||
final StringBuilder resourceNameBuilder = new StringBuilder();
|
final StringBuilder resourceNameBuilder = new StringBuilder();
|
||||||
if (httpMethod != null) {
|
if (httpMethod != null) {
|
||||||
resourceNameBuilder.append(httpMethod);
|
resourceNameBuilder.append(httpMethod);
|
||||||
resourceNameBuilder.append(" ");
|
resourceNameBuilder.append(" ");
|
||||||
}
|
}
|
||||||
Path last = null;
|
boolean skipSlash = false;
|
||||||
for (final Path path : paths) {
|
if (classPath != null) {
|
||||||
if (path.value().startsWith("/") || (last != null && last.value().endsWith("/"))) {
|
if (!classPath.value().startsWith("/")) {
|
||||||
resourceNameBuilder.append(path.value());
|
|
||||||
} else {
|
|
||||||
resourceNameBuilder.append("/");
|
resourceNameBuilder.append("/");
|
||||||
resourceNameBuilder.append(path.value());
|
|
||||||
}
|
}
|
||||||
last = path;
|
resourceNameBuilder.append(classPath.value());
|
||||||
|
skipSlash = classPath.value().endsWith("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (methodPath != null) {
|
||||||
|
String path = methodPath.value();
|
||||||
|
if (skipSlash) {
|
||||||
|
if (path.startsWith("/")) {
|
||||||
|
path = path.length() == 1 ? "" : path.substring(1);
|
||||||
|
}
|
||||||
|
} else if (!path.startsWith("/")) {
|
||||||
|
resourceNameBuilder.append("/");
|
||||||
|
}
|
||||||
|
resourceNameBuilder.append(path);
|
||||||
|
}
|
||||||
|
|
||||||
resourceName = resourceNameBuilder.toString().trim();
|
resourceName = resourceNameBuilder.toString().trim();
|
||||||
return resourceName;
|
return resourceName;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package datadog.trace.instrumentation.jaxrs2;
|
package datadog.trace.instrumentation.jaxrs2;
|
||||||
|
|
||||||
|
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.hasSuperMethod;
|
||||||
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
|
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
|
||||||
import static datadog.trace.instrumentation.api.AgentTracer.activateSpan;
|
import static datadog.trace.instrumentation.api.AgentTracer.activateSpan;
|
||||||
import static datadog.trace.instrumentation.api.AgentTracer.activeSpan;
|
import static datadog.trace.instrumentation.api.AgentTracer.activeSpan;
|
||||||
|
@ -8,6 +9,7 @@ import static datadog.trace.instrumentation.jaxrs2.JaxRsAnnotationsDecorator.DEC
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
|
import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
|
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
|
||||||
import com.google.auto.service.AutoService;
|
import com.google.auto.service.AutoService;
|
||||||
|
@ -47,21 +49,27 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default
|
||||||
@Override
|
@Override
|
||||||
public String[] helperClassNames() {
|
public String[] helperClassNames() {
|
||||||
return new String[] {
|
return new String[] {
|
||||||
"datadog.trace.agent.decorator.BaseDecorator", packageName + ".JaxRsAnnotationsDecorator",
|
"datadog.trace.agent.decorator.BaseDecorator",
|
||||||
|
"datadog.trace.agent.tooling.ClassHierarchyIterable",
|
||||||
|
"datadog.trace.agent.tooling.ClassHierarchyIterable$ClassIterator",
|
||||||
|
packageName + ".JaxRsAnnotationsDecorator",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||||
return singletonMap(
|
return singletonMap(
|
||||||
isAnnotatedWith(
|
isMethod()
|
||||||
named("javax.ws.rs.Path")
|
.and(
|
||||||
.or(named("javax.ws.rs.DELETE"))
|
hasSuperMethod(
|
||||||
.or(named("javax.ws.rs.GET"))
|
isAnnotatedWith(
|
||||||
.or(named("javax.ws.rs.HEAD"))
|
named("javax.ws.rs.Path")
|
||||||
.or(named("javax.ws.rs.OPTIONS"))
|
.or(named("javax.ws.rs.DELETE"))
|
||||||
.or(named("javax.ws.rs.POST"))
|
.or(named("javax.ws.rs.GET"))
|
||||||
.or(named("javax.ws.rs.PUT"))),
|
.or(named("javax.ws.rs.HEAD"))
|
||||||
|
.or(named("javax.ws.rs.OPTIONS"))
|
||||||
|
.or(named("javax.ws.rs.POST"))
|
||||||
|
.or(named("javax.ws.rs.PUT"))))),
|
||||||
JaxRsAnnotationsInstrumentation.class.getName() + "$JaxRsAnnotationsAdvice");
|
JaxRsAnnotationsInstrumentation.class.getName() + "$JaxRsAnnotationsAdvice");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +82,7 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default
|
||||||
final AgentSpan parent = activeSpan();
|
final AgentSpan parent = activeSpan();
|
||||||
|
|
||||||
final AgentSpan span = startSpan(JAX_ENDPOINT_OPERATION_NAME);
|
final AgentSpan span = startSpan(JAX_ENDPOINT_OPERATION_NAME);
|
||||||
DECORATE.onControllerStart(span, parent, target.getClass(), method);
|
DECORATE.onJaxRsSpan(span, parent, target.getClass(), method);
|
||||||
DECORATE.afterStart(span);
|
DECORATE.afterStart(span);
|
||||||
|
|
||||||
final AgentScope scope = activateSpan(span, false);
|
final AgentScope scope = activateSpan(span, false);
|
||||||
|
|
|
@ -41,7 +41,10 @@ public final class JaxRsAsyncResponseInstrumentation extends Instrumenter.Defaul
|
||||||
@Override
|
@Override
|
||||||
public String[] helperClassNames() {
|
public String[] helperClassNames() {
|
||||||
return new String[] {
|
return new String[] {
|
||||||
"datadog.trace.agent.decorator.BaseDecorator", packageName + ".JaxRsAnnotationsDecorator",
|
"datadog.trace.agent.decorator.BaseDecorator",
|
||||||
|
"datadog.trace.agent.tooling.ClassHierarchyIterable",
|
||||||
|
"datadog.trace.agent.tooling.ClassHierarchyIterable$ClassIterator",
|
||||||
|
packageName + ".JaxRsAnnotationsDecorator",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,53 +87,53 @@ class JaxRsAnnotationsInstrumentationTest extends AgentTestRunner {
|
||||||
resourceNames.get(obj.class).size() == 1
|
resourceNames.get(obj.class).size() == 1
|
||||||
|
|
||||||
where:
|
where:
|
||||||
name | obj
|
name | obj
|
||||||
"/a" | new Jax() {
|
"/a" | new Jax() {
|
||||||
@Path("/a")
|
@Path("/a")
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"GET /b" | new Jax() {
|
"GET /b" | new Jax() {
|
||||||
@GET
|
@GET
|
||||||
@Path("/b")
|
@Path("/b")
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"POST /c" | new InterfaceWithPath() {
|
"POST /interface/c" | new InterfaceWithPath() {
|
||||||
@POST
|
@POST
|
||||||
@Path("/c")
|
@Path("/c")
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"HEAD" | new InterfaceWithPath() {
|
"HEAD /interface" | new InterfaceWithPath() {
|
||||||
@HEAD
|
@HEAD
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"POST /abstract/d" | new AbstractClassWithPath() {
|
"POST /abstract/d" | new AbstractClassWithPath() {
|
||||||
@POST
|
@POST
|
||||||
@Path("/d")
|
@Path("/d")
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"PUT /abstract" | new AbstractClassWithPath() {
|
"PUT /abstract" | new AbstractClassWithPath() {
|
||||||
@PUT
|
@PUT
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"OPTIONS /child/e" | new ChildClassWithPath() {
|
"OPTIONS /child/e" | new ChildClassWithPath() {
|
||||||
@OPTIONS
|
@OPTIONS
|
||||||
@Path("/e")
|
@Path("/e")
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"DELETE /child" | new ChildClassWithPath() {
|
"DELETE /child/call" | new ChildClassWithPath() {
|
||||||
@DELETE
|
@DELETE
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"POST /child/call" | new ChildClassWithPath()
|
"POST /child/call" | new ChildClassWithPath()
|
||||||
"GET /child/call" | new JavaInterfaces.ChildClassOnInterface()
|
"GET /child/call" | new JavaInterfaces.ChildClassOnInterface()
|
||||||
// TODO: uncomment when we drop support for Java 7
|
// TODO: uncomment when we drop support for Java 7
|
||||||
// "GET /child/invoke" | new JavaInterfaces.DefaultChildClassOnInterface()
|
// "GET /child/invoke" | new JavaInterfaces.DefaultChildClassOnInterface()
|
||||||
|
|
||||||
|
@ -168,18 +168,6 @@ class JaxRsAnnotationsInstrumentationTest extends AgentTestRunner {
|
||||||
void call() {
|
void call() {
|
||||||
}
|
}
|
||||||
} | _
|
} | _
|
||||||
new InterfaceWithPath() {
|
|
||||||
void call() {
|
|
||||||
}
|
|
||||||
} | _
|
|
||||||
new AbstractClassWithPath() {
|
|
||||||
void call() {
|
|
||||||
}
|
|
||||||
} | _
|
|
||||||
new ChildClassWithPath() {
|
|
||||||
void call() {
|
|
||||||
}
|
|
||||||
} | _
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Jax {
|
interface Jax {
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
import datadog.trace.agent.test.AgentTestRunner
|
||||||
|
import datadog.trace.api.DDSpanTypes
|
||||||
|
import datadog.trace.api.DDTags
|
||||||
|
import datadog.trace.instrumentation.api.Tags
|
||||||
|
import io.dropwizard.testing.junit.ResourceTestRule
|
||||||
|
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 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 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"
|
||||||
|
tags {
|
||||||
|
"$DDTags.RESOURCE_NAME" parentResourceName
|
||||||
|
"$Tags.COMPONENT" "jax-rs"
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span(1) {
|
||||||
|
childOf span(0)
|
||||||
|
operationName abort ? "jax-rs.request.abort" : "jax-rs.request"
|
||||||
|
tags {
|
||||||
|
"$DDTags.RESOURCE_NAME" controllerName
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
|
||||||
|
"$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 | null | "PrematchRequestFilter.filter" | "Aborted Prematch"
|
||||||
|
"/test2/hello/bob" | false | true | null | "PrematchRequestFilter.filter" | "Aborted Prematch"
|
||||||
|
"/test3/hi/bob" | false | true | null | "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]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
|
||||||
|
// Originally had this as a groovy class but was getting some weird errors.
|
||||||
|
@Path("/ignored")
|
||||||
|
public interface Resource {
|
||||||
|
@Path("ignored")
|
||||||
|
String hello(final String name);
|
||||||
|
|
||||||
|
@Path("/test")
|
||||||
|
interface SubResource extends Cloneable, Resource {
|
||||||
|
@Override
|
||||||
|
@POST
|
||||||
|
@Path("/hello/{name}")
|
||||||
|
String hello(@PathParam("name") final String name);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Test1 implements SubResource {
|
||||||
|
@Override
|
||||||
|
public String hello(final String name) {
|
||||||
|
return "Test1 " + name + "!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path("/test2")
|
||||||
|
class Test2 implements SubResource {
|
||||||
|
@Override
|
||||||
|
public String hello(final String name) {
|
||||||
|
return "Test2 " + name + "!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path("/test3")
|
||||||
|
class Test3 implements SubResource {
|
||||||
|
@Override
|
||||||
|
@POST
|
||||||
|
@Path("/hi/{name}")
|
||||||
|
public String hello(@PathParam("name") final String name) {
|
||||||
|
return "Test3 " + name + "!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,9 @@ muzzle {
|
||||||
pass {
|
pass {
|
||||||
group = "redis.clients"
|
group = "redis.clients"
|
||||||
module = "jedis"
|
module = "jedis"
|
||||||
versions = "[1.4.0,)"
|
versions = "[1.4.0,3.0.0)"
|
||||||
assertInverse = true
|
|
||||||
}
|
}
|
||||||
|
// Muzzle doesn't detect the classLoaderMatcher, so we can't assert fail for 3.0+
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "${rootDir}/gradle/java.gradle"
|
apply from: "${rootDir}/gradle/java.gradle"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package datadog.trace.instrumentation.jedis;
|
package datadog.trace.instrumentation.jedis;
|
||||||
|
|
||||||
|
import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClasses;
|
||||||
import static datadog.trace.instrumentation.api.AgentTracer.activateSpan;
|
import static datadog.trace.instrumentation.api.AgentTracer.activateSpan;
|
||||||
import static datadog.trace.instrumentation.api.AgentTracer.startSpan;
|
import static datadog.trace.instrumentation.api.AgentTracer.startSpan;
|
||||||
import static datadog.trace.instrumentation.jedis.JedisClientDecorator.DECORATE;
|
import static datadog.trace.instrumentation.jedis.JedisClientDecorator.DECORATE;
|
||||||
|
@ -7,6 +8,7 @@ import static java.util.Collections.singletonMap;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
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.takesArgument;
|
||||||
|
|
||||||
import com.google.auto.service.AutoService;
|
import com.google.auto.service.AutoService;
|
||||||
|
@ -23,13 +25,15 @@ import redis.clients.jedis.Protocol.Command;
|
||||||
@AutoService(Instrumenter.class)
|
@AutoService(Instrumenter.class)
|
||||||
public final class JedisInstrumentation extends Instrumenter.Default {
|
public final class JedisInstrumentation extends Instrumenter.Default {
|
||||||
|
|
||||||
private static final String SERVICE_NAME = "redis";
|
|
||||||
private static final String COMPONENT_NAME = SERVICE_NAME + "-command";
|
|
||||||
|
|
||||||
public JedisInstrumentation() {
|
public JedisInstrumentation() {
|
||||||
super("jedis", "redis");
|
super("jedis", "redis");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<ClassLoader> classLoaderMatcher() {
|
||||||
|
return not(classLoaderHasClasses("redis.clients.jedis.commands.ProtocolCommand"));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||||
return named("redis.clients.jedis.Protocol");
|
return named("redis.clients.jedis.Protocol");
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import datadog.trace.agent.test.AgentTestRunner
|
import datadog.trace.agent.test.AgentTestRunner
|
||||||
|
import datadog.trace.agent.test.utils.PortUtils
|
||||||
import datadog.trace.api.Config
|
import datadog.trace.api.Config
|
||||||
import datadog.trace.api.DDSpanTypes
|
import datadog.trace.api.DDSpanTypes
|
||||||
import datadog.trace.api.DDTags
|
import datadog.trace.api.DDTags
|
||||||
|
@ -9,7 +10,8 @@ import spock.lang.Shared
|
||||||
|
|
||||||
class JedisClientTest extends AgentTestRunner {
|
class JedisClientTest extends AgentTestRunner {
|
||||||
|
|
||||||
public static final int PORT = 6399
|
@Shared
|
||||||
|
int port = PortUtils.randomOpenPort()
|
||||||
|
|
||||||
@Shared
|
@Shared
|
||||||
RedisServer redisServer = RedisServer.builder()
|
RedisServer redisServer = RedisServer.builder()
|
||||||
|
@ -17,9 +19,9 @@ class JedisClientTest extends AgentTestRunner {
|
||||||
.setting("bind 127.0.0.1")
|
.setting("bind 127.0.0.1")
|
||||||
// set max memory to avoid problems in CI
|
// set max memory to avoid problems in CI
|
||||||
.setting("maxmemory 128M")
|
.setting("maxmemory 128M")
|
||||||
.port(PORT).build()
|
.port(port).build()
|
||||||
@Shared
|
@Shared
|
||||||
Jedis jedis = new Jedis("localhost", PORT)
|
Jedis jedis = new Jedis("localhost", port)
|
||||||
|
|
||||||
def setupSpec() {
|
def setupSpec() {
|
||||||
println "Using redis: $redisServer.args"
|
println "Using redis: $redisServer.args"
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
muzzle {
|
||||||
|
fail {
|
||||||
|
group = "redis.clients"
|
||||||
|
module = "jedis"
|
||||||
|
versions = "[,3.0.0)"
|
||||||
|
}
|
||||||
|
|
||||||
|
pass {
|
||||||
|
group = "redis.clients"
|
||||||
|
module = "jedis"
|
||||||
|
versions = "[3.0.0,)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "${rootDir}/gradle/java.gradle"
|
||||||
|
|
||||||
|
apply plugin: 'org.unbroken-dome.test-sets'
|
||||||
|
|
||||||
|
testSets {
|
||||||
|
latestDepTest {
|
||||||
|
dirName = 'test'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly group: 'redis.clients', name: 'jedis', version: '3.0.0'
|
||||||
|
|
||||||
|
testCompile group: 'com.github.kstyrc', name: 'embedded-redis', version: '0.6'
|
||||||
|
testCompile group: 'redis.clients', name: 'jedis', version: '3.0.0'
|
||||||
|
// ensures jedis-1.4 instrumentation does not load with jedis 3.0+ by failing
|
||||||
|
// the tests in the event it does. The tests will end up with double spans
|
||||||
|
testCompile project(':dd-java-agent:instrumentation:jedis-1.4')
|
||||||
|
|
||||||
|
latestDepTestCompile group: 'redis.clients', name: 'jedis', version: '3.+'
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package datadog.trace.instrumentation.jedis30;
|
||||||
|
|
||||||
|
import datadog.trace.agent.decorator.DatabaseClientDecorator;
|
||||||
|
import datadog.trace.api.DDSpanTypes;
|
||||||
|
import redis.clients.jedis.commands.ProtocolCommand;
|
||||||
|
|
||||||
|
public class JedisClientDecorator extends DatabaseClientDecorator<ProtocolCommand> {
|
||||||
|
public static final JedisClientDecorator DECORATE = new JedisClientDecorator();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String[] instrumentationNames() {
|
||||||
|
return new String[] {"jedis", "redis"};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String service() {
|
||||||
|
return "redis";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String component() {
|
||||||
|
return "redis-command";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String spanType() {
|
||||||
|
return DDSpanTypes.REDIS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String dbType() {
|
||||||
|
return "redis";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String dbUser(final ProtocolCommand session) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String dbInstance(final ProtocolCommand session) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package datadog.trace.instrumentation.jedis30;
|
||||||
|
|
||||||
|
import static datadog.trace.instrumentation.api.AgentTracer.activateSpan;
|
||||||
|
import static datadog.trace.instrumentation.api.AgentTracer.startSpan;
|
||||||
|
import static datadog.trace.instrumentation.jedis30.JedisClientDecorator.DECORATE;
|
||||||
|
import static java.util.Collections.singletonMap;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||||
|
|
||||||
|
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.util.Map;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
import net.bytebuddy.description.method.MethodDescription;
|
||||||
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
|
import redis.clients.jedis.Protocol;
|
||||||
|
import redis.clients.jedis.commands.ProtocolCommand;
|
||||||
|
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public final class JedisInstrumentation extends Instrumenter.Default {
|
||||||
|
|
||||||
|
public JedisInstrumentation() {
|
||||||
|
super("jedis", "redis");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||||
|
return named("redis.clients.jedis.Protocol");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] helperClassNames() {
|
||||||
|
return new String[] {
|
||||||
|
"datadog.trace.agent.decorator.BaseDecorator",
|
||||||
|
"datadog.trace.agent.decorator.ClientDecorator",
|
||||||
|
"datadog.trace.agent.decorator.DatabaseClientDecorator",
|
||||||
|
packageName + ".JedisClientDecorator",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||||
|
return singletonMap(
|
||||||
|
isMethod()
|
||||||
|
.and(isPublic())
|
||||||
|
.and(named("sendCommand"))
|
||||||
|
.and(takesArgument(1, named("redis.clients.jedis.commands.ProtocolCommand"))),
|
||||||
|
JedisInstrumentation.class.getName() + "$JedisAdvice");
|
||||||
|
// FIXME: This instrumentation only incorporates sending the command, not processing the result.
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class JedisAdvice {
|
||||||
|
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static AgentScope onEnter(@Advice.Argument(1) final ProtocolCommand command) {
|
||||||
|
final AgentSpan span = startSpan("redis.query");
|
||||||
|
DECORATE.afterStart(span);
|
||||||
|
if (command instanceof Protocol.Command) {
|
||||||
|
DECORATE.onStatement(span, ((Protocol.Command) command).name());
|
||||||
|
} else {
|
||||||
|
// Protocol.Command is the only implementation in the Jedis lib as of 3.1 but this will save
|
||||||
|
// us if that changes
|
||||||
|
DECORATE.onStatement(span, new String(command.getRaw()));
|
||||||
|
}
|
||||||
|
return activateSpan(span, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||||
|
public static void stopSpan(
|
||||||
|
@Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
|
||||||
|
DECORATE.onError(scope.span(), throwable);
|
||||||
|
DECORATE.beforeFinish(scope.span());
|
||||||
|
scope.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
import datadog.trace.agent.test.AgentTestRunner
|
||||||
|
import datadog.trace.agent.test.utils.PortUtils
|
||||||
|
import datadog.trace.api.Config
|
||||||
|
import datadog.trace.api.DDSpanTypes
|
||||||
|
import datadog.trace.api.DDTags
|
||||||
|
import datadog.trace.instrumentation.api.Tags
|
||||||
|
import redis.clients.jedis.Jedis
|
||||||
|
import redis.embedded.RedisServer
|
||||||
|
import spock.lang.Shared
|
||||||
|
|
||||||
|
class Jedis30ClientTest extends AgentTestRunner {
|
||||||
|
|
||||||
|
@Shared
|
||||||
|
int port = PortUtils.randomOpenPort()
|
||||||
|
|
||||||
|
@Shared
|
||||||
|
RedisServer redisServer = RedisServer.builder()
|
||||||
|
// bind to localhost to avoid firewall popup
|
||||||
|
.setting("bind 127.0.0.1")
|
||||||
|
// set max memory to avoid problems in CI
|
||||||
|
.setting("maxmemory 128M")
|
||||||
|
.port(port).build()
|
||||||
|
@Shared
|
||||||
|
Jedis jedis = new Jedis("localhost", port)
|
||||||
|
|
||||||
|
def setupSpec() {
|
||||||
|
println "Using redis: $redisServer.args"
|
||||||
|
redisServer.start()
|
||||||
|
|
||||||
|
// This setting should have no effect since decorator returns null for the instance.
|
||||||
|
System.setProperty(Config.PREFIX + Config.DB_CLIENT_HOST_SPLIT_BY_INSTANCE, "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
def cleanupSpec() {
|
||||||
|
redisServer.stop()
|
||||||
|
jedis.close()
|
||||||
|
|
||||||
|
System.clearProperty(Config.PREFIX + Config.DB_CLIENT_HOST_SPLIT_BY_INSTANCE)
|
||||||
|
}
|
||||||
|
|
||||||
|
def setup() {
|
||||||
|
jedis.flushAll()
|
||||||
|
TEST_WRITER.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
def "set command"() {
|
||||||
|
when:
|
||||||
|
jedis.set("foo", "bar")
|
||||||
|
|
||||||
|
then:
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 1) {
|
||||||
|
span(0) {
|
||||||
|
operationName "redis.query"
|
||||||
|
tags {
|
||||||
|
"$DDTags.SERVICE_NAME" "redis"
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.REDIS
|
||||||
|
"$Tags.COMPONENT" "redis-command"
|
||||||
|
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||||
|
"$Tags.DB_TYPE" "redis"
|
||||||
|
"$Tags.DB_STATEMENT" "SET"
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "get command"() {
|
||||||
|
when:
|
||||||
|
jedis.set("foo", "bar")
|
||||||
|
def value = jedis.get("foo")
|
||||||
|
|
||||||
|
then:
|
||||||
|
value == "bar"
|
||||||
|
|
||||||
|
assertTraces(2) {
|
||||||
|
trace(0, 1) {
|
||||||
|
span(0) {
|
||||||
|
operationName "redis.query"
|
||||||
|
tags {
|
||||||
|
"$DDTags.SERVICE_NAME" "redis"
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.REDIS
|
||||||
|
"$Tags.COMPONENT" "redis-command"
|
||||||
|
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||||
|
"$Tags.DB_TYPE" "redis"
|
||||||
|
"$Tags.DB_STATEMENT" "SET"
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace(1, 1) {
|
||||||
|
span(0) {
|
||||||
|
operationName "redis.query"
|
||||||
|
tags {
|
||||||
|
"$DDTags.SERVICE_NAME" "redis"
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.REDIS
|
||||||
|
"$Tags.COMPONENT" "redis-command"
|
||||||
|
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||||
|
"$Tags.DB_TYPE" "redis"
|
||||||
|
"$Tags.DB_STATEMENT" "GET"
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "command with no arguments"() {
|
||||||
|
when:
|
||||||
|
jedis.set("foo", "bar")
|
||||||
|
def value = jedis.randomKey()
|
||||||
|
|
||||||
|
then:
|
||||||
|
value == "foo"
|
||||||
|
|
||||||
|
assertTraces(2) {
|
||||||
|
trace(0, 1) {
|
||||||
|
span(0) {
|
||||||
|
operationName "redis.query"
|
||||||
|
tags {
|
||||||
|
"$DDTags.SERVICE_NAME" "redis"
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.REDIS
|
||||||
|
"$Tags.COMPONENT" "redis-command"
|
||||||
|
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||||
|
"$Tags.DB_TYPE" "redis"
|
||||||
|
"$Tags.DB_STATEMENT" "SET"
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace(1, 1) {
|
||||||
|
span(0) {
|
||||||
|
operationName "redis.query"
|
||||||
|
tags {
|
||||||
|
"$DDTags.SERVICE_NAME" "redis"
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.REDIS
|
||||||
|
"$Tags.COMPONENT" "redis-command"
|
||||||
|
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||||
|
"$Tags.DB_TYPE" "redis"
|
||||||
|
"$Tags.DB_STATEMENT" "RANDOMKEY"
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,13 +17,13 @@ muzzle {
|
||||||
pass {
|
pass {
|
||||||
group = 'com.typesafe.play'
|
group = 'com.typesafe.play'
|
||||||
module = 'play_2.12'
|
module = 'play_2.12'
|
||||||
versions = '[2.6.0,)'
|
versions = '[2.6.0,2.8.0)'
|
||||||
assertInverse = true
|
assertInverse = true
|
||||||
}
|
}
|
||||||
pass {
|
pass {
|
||||||
group = 'com.typesafe.play'
|
group = 'com.typesafe.play'
|
||||||
module = 'play_2.13'
|
module = 'play_2.13'
|
||||||
versions = '[2.6.0,)'
|
versions = '[2.6.0,2.8.0)'
|
||||||
assertInverse = true
|
assertInverse = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,7 @@ dependencies {
|
||||||
exclude group: 'org.eclipse.jetty.websocket', module: 'websocket-client'
|
exclude group: 'org.eclipse.jetty.websocket', module: 'websocket-client'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: This should be changed to the latest in scala 2.13 instead of 2.11 since its ahead
|
||||||
latestDepTestCompile group: 'com.typesafe.play', name: "play-java_$scalaVersion", version: '2.+'
|
latestDepTestCompile group: 'com.typesafe.play', name: "play-java_$scalaVersion", version: '2.+'
|
||||||
latestDepTestCompile(group: 'com.typesafe.play', name: "play-test_$scalaVersion", version: '2.+') {
|
latestDepTestCompile(group: 'com.typesafe.play', name: "play-test_$scalaVersion", version: '2.+') {
|
||||||
exclude group: 'org.eclipse.jetty.websocket', module: 'websocket-client'
|
exclude group: 'org.eclipse.jetty.websocket', module: 'websocket-client'
|
||||||
|
|
|
@ -28,7 +28,14 @@ public class Servlet2Decorator
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected URI url(final HttpServletRequest httpServletRequest) throws URISyntaxException {
|
protected URI url(final HttpServletRequest httpServletRequest) throws URISyntaxException {
|
||||||
return new URI(httpServletRequest.getRequestURL().toString());
|
return new URI(
|
||||||
|
httpServletRequest.getScheme(),
|
||||||
|
null,
|
||||||
|
httpServletRequest.getServerName(),
|
||||||
|
httpServletRequest.getServerPort(),
|
||||||
|
httpServletRequest.getRequestURI(),
|
||||||
|
httpServletRequest.getQueryString(),
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -29,7 +29,14 @@ public class Servlet3Decorator
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected URI url(final HttpServletRequest httpServletRequest) throws URISyntaxException {
|
protected URI url(final HttpServletRequest httpServletRequest) throws URISyntaxException {
|
||||||
return new URI(httpServletRequest.getRequestURL().toString());
|
return new URI(
|
||||||
|
httpServletRequest.getScheme(),
|
||||||
|
null,
|
||||||
|
httpServletRequest.getServerName(),
|
||||||
|
httpServletRequest.getServerPort(),
|
||||||
|
httpServletRequest.getRequestURI(),
|
||||||
|
httpServletRequest.getQueryString(),
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -16,7 +16,7 @@ def isCI = System.getenv("CI") != null
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = 'com.datadoghq'
|
group = 'com.datadoghq'
|
||||||
version = '0.39.0-SNAPSHOT'
|
version = '0.40.0-SNAPSHOT'
|
||||||
|
|
||||||
if (isCI) {
|
if (isCI) {
|
||||||
buildDir = "${rootDir}/workspace/${projectDir.path.replace(rootDir.path, '')}/build/"
|
buildDir = "${rootDir}/workspace/${projectDir.path.replace(rootDir.path, '')}/build/"
|
||||||
|
|
|
@ -23,7 +23,7 @@ import java.lang.ref.WeakReference;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
@ -254,7 +254,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
|
||||||
private final String operationName;
|
private final String operationName;
|
||||||
|
|
||||||
// Builder attributes
|
// Builder attributes
|
||||||
private final Map<String, Object> tags = new HashMap<>();
|
private final Map<String, Object> tags = new LinkedHashMap<>();
|
||||||
private long timestampMicro;
|
private long timestampMicro;
|
||||||
private SpanContext parent;
|
private SpanContext parent;
|
||||||
private boolean errorFlag;
|
private boolean errorFlag;
|
||||||
|
|
|
@ -12,7 +12,7 @@ ext {
|
||||||
groovy : groovyVer,
|
groovy : groovyVer,
|
||||||
logback : "1.2.3",
|
logback : "1.2.3",
|
||||||
lombok : "1.18.10",
|
lombok : "1.18.10",
|
||||||
bytebuddy : "1.10.2",
|
bytebuddy : "1.10.4",
|
||||||
scala : "2.11.12", // Last version to support Java 7 (2.12+ require Java 8+)
|
scala : "2.11.12", // Last version to support Java 7 (2.12+ require Java 8+)
|
||||||
kotlin : "1.3.50",
|
kotlin : "1.3.50",
|
||||||
coroutines : "1.3.0"
|
coroutines : "1.3.0"
|
||||||
|
|
|
@ -71,6 +71,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'
|
||||||
|
@ -83,6 +86,7 @@ include ':dd-java-agent:instrumentation:java-concurrent:akka-2.5-testing'
|
||||||
include ':dd-java-agent:instrumentation:jboss-classloading'
|
include ':dd-java-agent:instrumentation:jboss-classloading'
|
||||||
include ':dd-java-agent:instrumentation:jdbc'
|
include ':dd-java-agent:instrumentation:jdbc'
|
||||||
include ':dd-java-agent:instrumentation:jedis-1.4'
|
include ':dd-java-agent:instrumentation:jedis-1.4'
|
||||||
|
include ':dd-java-agent:instrumentation:jedis-3.0'
|
||||||
include ':dd-java-agent:instrumentation:jetty-8'
|
include ':dd-java-agent:instrumentation:jetty-8'
|
||||||
include ':dd-java-agent:instrumentation:jms'
|
include ':dd-java-agent:instrumentation:jms'
|
||||||
include ':dd-java-agent:instrumentation:jsp-2.3'
|
include ':dd-java-agent:instrumentation:jsp-2.3'
|
||||||
|
|
Loading…
Reference in New Issue