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;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.hasSignature;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
@ -7,8 +9,10 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDefinition;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.description.type.TypeList;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
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.lang.ref.WeakReference;
|
||||
import java.security.SecureClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -18,6 +17,7 @@ import java.util.LinkedHashSet;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.bytebuddy.agent.builder.AgentBuilder.Transformer;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
|
@ -34,12 +34,11 @@ public class HelperInjector implements Transformer {
|
|||
new SecureClassLoader(null) {};
|
||||
|
||||
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();
|
||||
|
||||
// Neither Module nor WeakReference implements equals or hashcode so using a list
|
||||
private final List<WeakReference<Object>> helperModules = new ArrayList<>();
|
||||
|
||||
private final List<WeakReference<Object>> helperModules = new CopyOnWriteArrayList<>();
|
||||
/**
|
||||
* Construct HelperInjector.
|
||||
*
|
||||
|
@ -55,13 +54,7 @@ public class HelperInjector implements Transformer {
|
|||
|
||||
public HelperInjector(final Map<String, byte[]> helperMap) {
|
||||
helperClassNames = helperMap.keySet();
|
||||
this.helperMap = new LinkedHashMap<>(helperClassNames.size());
|
||||
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));
|
||||
}
|
||||
dynamicTypeMap.putAll(helperMap);
|
||||
}
|
||||
|
||||
public static HelperInjector forDynamicTypes(final Collection<DynamicType.Unloaded<?>> helpers) {
|
||||
|
@ -72,20 +65,22 @@ public class HelperInjector implements Transformer {
|
|||
return new HelperInjector(bytes);
|
||||
}
|
||||
|
||||
private synchronized Map<TypeDescription, byte[]> getHelperMap() throws IOException {
|
||||
if (helperMap == null) {
|
||||
helperMap = new LinkedHashMap<>(helperClassNames.size());
|
||||
for (final String helperName : helperClassNames) {
|
||||
private Map<String, byte[]> getHelperMap() throws IOException {
|
||||
if (dynamicTypeMap.isEmpty()) {
|
||||
final Map<String, byte[]> classnameToBytes = new LinkedHashMap<>();
|
||||
|
||||
final ClassFileLocator locator =
|
||||
ClassFileLocator.ForClassLoader.of(Utils.getAgentClassLoader());
|
||||
final byte[] classBytes = locator.locate(helperName).resolve();
|
||||
final TypeDescription typeDesc =
|
||||
new TypeDescription.Latent(
|
||||
helperName, 0, null, Collections.<TypeDescription.Generic>emptyList());
|
||||
helperMap.put(typeDesc, classBytes);
|
||||
|
||||
for (final String helperClassName : helperClassNames) {
|
||||
final byte[] classBytes = locator.locate(helperClassName).resolve();
|
||||
classnameToBytes.put(helperClassName, classBytes);
|
||||
}
|
||||
|
||||
return classnameToBytes;
|
||||
} else {
|
||||
return dynamicTypeMap;
|
||||
}
|
||||
return helperMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -95,56 +90,54 @@ public class HelperInjector implements Transformer {
|
|||
ClassLoader classLoader,
|
||||
final JavaModule module) {
|
||||
if (!helperClassNames.isEmpty()) {
|
||||
synchronized (this) {
|
||||
if (classLoader == BOOTSTRAP_CLASSLOADER) {
|
||||
classLoader = BOOTSTRAP_CLASSLOADER_PLACEHOLDER;
|
||||
}
|
||||
|
||||
if (!injectedClassLoaders.containsKey(classLoader)) {
|
||||
try {
|
||||
final Map<TypeDescription, byte[]> helperMap = getHelperMap();
|
||||
log.debug("Injecting classes onto classloader {} -> {}", classLoader, helperClassNames);
|
||||
|
||||
final Map<String, byte[]> classnameToBytes = getHelperMap();
|
||||
final Map<String, Class<?>> classes;
|
||||
if (classLoader == BOOTSTRAP_CLASSLOADER_PLACEHOLDER) {
|
||||
final Map<TypeDescription, Class<?>> injected =
|
||||
classes =
|
||||
ClassInjector.UsingInstrumentation.of(
|
||||
new File(System.getProperty("java.io.tmpdir")),
|
||||
ClassInjector.UsingInstrumentation.Target.BOOTSTRAP,
|
||||
AgentInstaller.getInstrumentation())
|
||||
.inject(helperMap);
|
||||
for (final TypeDescription desc : injected.keySet()) {
|
||||
final Class<?> injectedClass =
|
||||
Class.forName(desc.getName(), false, Utils.getBootstrapProxy());
|
||||
if (JavaModule.isSupported()) {
|
||||
helperModules.add(new WeakReference<>(JavaModule.ofType(injectedClass).unwrap()));
|
||||
}
|
||||
}
|
||||
.injectRaw(classnameToBytes);
|
||||
} else {
|
||||
final Map<TypeDescription, Class<?>> classMap =
|
||||
new ClassInjector.UsingReflection(classLoader).inject(helperMap);
|
||||
classes = new ClassInjector.UsingReflection(classLoader).injectRaw(classnameToBytes);
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
for (final Class<?> injectedClass : classMap.values()) {
|
||||
helperModules.add(new WeakReference<>(JavaModule.ofType(injectedClass).unwrap()));
|
||||
}
|
||||
}
|
||||
final JavaModule javaModule = JavaModule.ofType(classes.values().iterator().next());
|
||||
helperModules.add(new WeakReference<>(javaModule.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
|
||||
final String classLoaderType =
|
||||
classLoader == BOOTSTRAP_CLASSLOADER_PLACEHOLDER
|
||||
? "<bootstrap>"
|
||||
: classLoader.getClass().getName()),
|
||||
: 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);
|
||||
}
|
||||
|
||||
injectedClassLoaders.put(classLoader, true);
|
||||
}
|
||||
|
||||
ensureModuleCanReadHelperModules(module);
|
||||
}
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1,23 @@
|
|||
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 datadog.trace.agent.decorator.BaseDecorator;
|
||||
import datadog.trace.agent.tooling.ClassHierarchyIterable;
|
||||
import datadog.trace.api.DDSpanTypes;
|
||||
import datadog.trace.api.DDTags;
|
||||
import datadog.trace.bootstrap.WeakMap;
|
||||
|
@ -10,8 +11,6 @@ import datadog.trace.instrumentation.api.AgentSpan;
|
|||
import datadog.trace.instrumentation.api.Tags;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import javax.ws.rs.HttpMethod;
|
||||
|
@ -83,9 +82,31 @@ public class JaxRsAnnotationsDecorator extends BaseDecorator {
|
|||
|
||||
String resourceName = classMap.get(method);
|
||||
if (resourceName == null) {
|
||||
final String httpMethod = locateHttpMethod(method);
|
||||
final List<Path> paths = gatherPaths(target, method);
|
||||
resourceName = buildResourceName(httpMethod, paths);
|
||||
String httpMethod = null;
|
||||
Path methodPath = null;
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -102,40 +123,77 @@ public class JaxRsAnnotationsDecorator extends BaseDecorator {
|
|||
return httpMethod;
|
||||
}
|
||||
|
||||
private List<Path> gatherPaths(Class<Object> target, final Method method) {
|
||||
final List<Path> paths = new ArrayList();
|
||||
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 Path findMethodPath(final Method method) {
|
||||
return method.getAnnotation(Path.class);
|
||||
}
|
||||
|
||||
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 StringBuilder resourceNameBuilder = new StringBuilder();
|
||||
if (httpMethod != null) {
|
||||
resourceNameBuilder.append(httpMethod);
|
||||
resourceNameBuilder.append(" ");
|
||||
}
|
||||
Path last = null;
|
||||
for (final Path path : paths) {
|
||||
if (path.value().startsWith("/") || (last != null && last.value().endsWith("/"))) {
|
||||
resourceNameBuilder.append(path.value());
|
||||
} else {
|
||||
boolean skipSlash = false;
|
||||
if (classPath != null) {
|
||||
if (!classPath.value().startsWith("/")) {
|
||||
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();
|
||||
return resourceName;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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.ClassLoaderMatcher.classLoaderHasClasses;
|
||||
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 net.bytebuddy.matcher.ElementMatchers.declaresMethod;
|
||||
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.not;
|
||||
|
||||
|
@ -48,13 +50,19 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default
|
|||
@Override
|
||||
public String[] helperClassNames() {
|
||||
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
|
||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||
return singletonMap(
|
||||
isMethod()
|
||||
.and(
|
||||
hasSuperMethod(
|
||||
isAnnotatedWith(
|
||||
named("javax.ws.rs.Path")
|
||||
.or(named("javax.ws.rs.DELETE"))
|
||||
|
@ -62,7 +70,7 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default
|
|||
.or(named("javax.ws.rs.HEAD"))
|
||||
.or(named("javax.ws.rs.OPTIONS"))
|
||||
.or(named("javax.ws.rs.POST"))
|
||||
.or(named("javax.ws.rs.PUT"))),
|
||||
.or(named("javax.ws.rs.PUT"))))),
|
||||
JaxRsAnnotationsInstrumentation.class.getName() + "$JaxRsAnnotationsAdvice");
|
||||
}
|
||||
|
||||
|
|
|
@ -99,13 +99,13 @@ class JaxRsAnnotationsInstrumentationTest extends AgentTestRunner {
|
|||
void call() {
|
||||
}
|
||||
}
|
||||
"POST /c" | new InterfaceWithPath() {
|
||||
"POST /interface/c" | new InterfaceWithPath() {
|
||||
@POST
|
||||
@Path("/c")
|
||||
void call() {
|
||||
}
|
||||
}
|
||||
"HEAD" | new InterfaceWithPath() {
|
||||
"HEAD /interface" | new InterfaceWithPath() {
|
||||
@HEAD
|
||||
void call() {
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ class JaxRsAnnotationsInstrumentationTest extends AgentTestRunner {
|
|||
void call() {
|
||||
}
|
||||
}
|
||||
"DELETE /child" | new ChildClassWithPath() {
|
||||
"DELETE /child/call" | new ChildClassWithPath() {
|
||||
@DELETE
|
||||
void call() {
|
||||
}
|
||||
|
@ -168,18 +168,6 @@ class JaxRsAnnotationsInstrumentationTest extends AgentTestRunner {
|
|||
void call() {
|
||||
}
|
||||
} | _
|
||||
new InterfaceWithPath() {
|
||||
void call() {
|
||||
}
|
||||
} | _
|
||||
new AbstractClassWithPath() {
|
||||
void call() {
|
||||
}
|
||||
} | _
|
||||
new ChildClassWithPath() {
|
||||
void call() {
|
||||
}
|
||||
} | _
|
||||
}
|
||||
|
||||
interface Jax {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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
|
||||
|
@ -11,23 +12,50 @@ class JerseyTest extends AgentTestRunner {
|
|||
|
||||
@Shared
|
||||
@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"() {
|
||||
setup:
|
||||
def "test #resource"() {
|
||||
when:
|
||||
// start a trace because the test doesn't go through any servlet or other instrumentation.
|
||||
def response = runUnderTrace("test.span") {
|
||||
resources.client().resource("/test/hello/bob").post(String)
|
||||
resources.client().resource(resource).post(String)
|
||||
}
|
||||
|
||||
expect:
|
||||
response == "Hello bob!"
|
||||
TEST_WRITER.waitForTraces(1)
|
||||
TEST_WRITER.size() == 1
|
||||
then:
|
||||
response == expectedResponse
|
||||
|
||||
def trace = TEST_WRITER.firstTrace()
|
||||
def span = trace[0]
|
||||
span.tags[DDTags.RESOURCE_NAME] == "POST /test/hello/{name}"
|
||||
span.tags[Tags.COMPONENT] == "jax-rs"
|
||||
assertTraces(1) {
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
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.
|
||||
@Path("/ignored")
|
||||
public interface Resource {
|
||||
@Path("ignored")
|
||||
String hello(final String name);
|
||||
|
||||
@Path("/test")
|
||||
class Test implements Resource {
|
||||
interface SubResource extends Cloneable, Resource {
|
||||
@Override
|
||||
@POST
|
||||
@Path("/hello/{name}")
|
||||
public String addBook(@PathParam("name") final String name) {
|
||||
return "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 + "!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 plugin: 'org.unbroken-dome.test-sets'
|
||||
//
|
||||
//testSets {
|
||||
// latestDepTest {
|
||||
// dirName = 'test'
|
||||
// }
|
||||
//}
|
||||
apply plugin: 'org.unbroken-dome.test-sets'
|
||||
|
||||
testSets {
|
||||
latestDepTest {
|
||||
dirName = 'test'
|
||||
}
|
||||
|
||||
resteasy31Test {
|
||||
dirName = 'test'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
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:servlet:request-3')
|
||||
testCompile project(':dd-java-agent:instrumentation:jax-rs-annotations-2:filter-jersey')
|
||||
testCompile project(':dd-java-agent:instrumentation:jax-rs-annotations-2:filter-resteasy-3.0')
|
||||
testCompile project(':dd-java-agent:instrumentation:jax-rs-annotations-2:filter-resteasy-3.1')
|
||||
|
||||
// Jersey
|
||||
// First version with DropwizardTestSupport:
|
||||
testCompile group: 'io.dropwizard', name: 'dropwizard-testing', version: '0.8.0'
|
||||
testCompile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.2.3'
|
||||
testCompile group: 'com.fasterxml.jackson.module', name: 'jackson-module-afterburner', version: '2.9.10'
|
||||
|
||||
// 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 datadog.trace.agent.decorator.BaseDecorator;
|
||||
import datadog.trace.agent.tooling.ClassHierarchyIterable;
|
||||
import datadog.trace.api.DDSpanTypes;
|
||||
import datadog.trace.api.DDTags;
|
||||
import datadog.trace.bootstrap.WeakMap;
|
||||
|
@ -10,15 +11,21 @@ import datadog.trace.instrumentation.api.AgentSpan;
|
|||
import datadog.trace.instrumentation.api.Tags;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import javax.ws.rs.HttpMethod;
|
||||
import javax.ws.rs.Path;
|
||||
|
||||
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();
|
||||
|
||||
|
@ -37,8 +44,9 @@ public class JaxRsAnnotationsDecorator extends BaseDecorator {
|
|||
return "jax-rs-controller";
|
||||
}
|
||||
|
||||
public void onControllerStart(
|
||||
public void onJaxRsSpan(
|
||||
final AgentSpan span, final AgentSpan parent, final Class target, final Method method) {
|
||||
|
||||
final String resourceName = getPathResourceName(target, method);
|
||||
updateParent(parent, resourceName);
|
||||
|
||||
|
@ -49,7 +57,9 @@ public class JaxRsAnnotationsDecorator extends BaseDecorator {
|
|||
if (isRootScope && !resourceName.isEmpty()) {
|
||||
span.setTag(DDTags.RESOURCE_NAME, resourceName);
|
||||
} else {
|
||||
span.setTag(DDTags.RESOURCE_NAME, DECORATE.spanNameForClass(target) + "." + method.getName());
|
||||
span.setTag(
|
||||
DDTags.RESOURCE_NAME,
|
||||
DECORATE.spanNameForClass(target) + (method == null ? "" : "." + method.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,9 +93,31 @@ public class JaxRsAnnotationsDecorator extends BaseDecorator {
|
|||
|
||||
String resourceName = classMap.get(method);
|
||||
if (resourceName == null) {
|
||||
final String httpMethod = locateHttpMethod(method);
|
||||
final List<Path> paths = gatherPaths(target, method);
|
||||
resourceName = buildResourceName(httpMethod, paths);
|
||||
String httpMethod = null;
|
||||
Path methodPath = null;
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -102,40 +134,77 @@ public class JaxRsAnnotationsDecorator extends BaseDecorator {
|
|||
return httpMethod;
|
||||
}
|
||||
|
||||
private List<Path> gatherPaths(Class<Object> target, final Method method) {
|
||||
final List<Path> paths = new ArrayList();
|
||||
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 Path findMethodPath(final Method method) {
|
||||
return method.getAnnotation(Path.class);
|
||||
}
|
||||
|
||||
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 StringBuilder resourceNameBuilder = new StringBuilder();
|
||||
if (httpMethod != null) {
|
||||
resourceNameBuilder.append(httpMethod);
|
||||
resourceNameBuilder.append(" ");
|
||||
}
|
||||
Path last = null;
|
||||
for (final Path path : paths) {
|
||||
if (path.value().startsWith("/") || (last != null && last.value().endsWith("/"))) {
|
||||
resourceNameBuilder.append(path.value());
|
||||
} else {
|
||||
boolean skipSlash = false;
|
||||
if (classPath != null) {
|
||||
if (!classPath.value().startsWith("/")) {
|
||||
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();
|
||||
return resourceName;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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.instrumentation.api.AgentTracer.activateSpan;
|
||||
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 net.bytebuddy.matcher.ElementMatchers.declaresMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
|
@ -47,13 +49,19 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default
|
|||
@Override
|
||||
public String[] helperClassNames() {
|
||||
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
|
||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||
return singletonMap(
|
||||
isMethod()
|
||||
.and(
|
||||
hasSuperMethod(
|
||||
isAnnotatedWith(
|
||||
named("javax.ws.rs.Path")
|
||||
.or(named("javax.ws.rs.DELETE"))
|
||||
|
@ -61,7 +69,7 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default
|
|||
.or(named("javax.ws.rs.HEAD"))
|
||||
.or(named("javax.ws.rs.OPTIONS"))
|
||||
.or(named("javax.ws.rs.POST"))
|
||||
.or(named("javax.ws.rs.PUT"))),
|
||||
.or(named("javax.ws.rs.PUT"))))),
|
||||
JaxRsAnnotationsInstrumentation.class.getName() + "$JaxRsAnnotationsAdvice");
|
||||
}
|
||||
|
||||
|
@ -74,7 +82,7 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default
|
|||
final AgentSpan parent = activeSpan();
|
||||
|
||||
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);
|
||||
|
||||
final AgentScope scope = activateSpan(span, false);
|
||||
|
|
|
@ -41,7 +41,10 @@ public final class JaxRsAsyncResponseInstrumentation extends Instrumenter.Defaul
|
|||
@Override
|
||||
public String[] helperClassNames() {
|
||||
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",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -99,13 +99,13 @@ class JaxRsAnnotationsInstrumentationTest extends AgentTestRunner {
|
|||
void call() {
|
||||
}
|
||||
}
|
||||
"POST /c" | new InterfaceWithPath() {
|
||||
"POST /interface/c" | new InterfaceWithPath() {
|
||||
@POST
|
||||
@Path("/c")
|
||||
void call() {
|
||||
}
|
||||
}
|
||||
"HEAD" | new InterfaceWithPath() {
|
||||
"HEAD /interface" | new InterfaceWithPath() {
|
||||
@HEAD
|
||||
void call() {
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ class JaxRsAnnotationsInstrumentationTest extends AgentTestRunner {
|
|||
void call() {
|
||||
}
|
||||
}
|
||||
"DELETE /child" | new ChildClassWithPath() {
|
||||
"DELETE /child/call" | new ChildClassWithPath() {
|
||||
@DELETE
|
||||
void call() {
|
||||
}
|
||||
|
@ -168,18 +168,6 @@ class JaxRsAnnotationsInstrumentationTest extends AgentTestRunner {
|
|||
void call() {
|
||||
}
|
||||
} | _
|
||||
new InterfaceWithPath() {
|
||||
void call() {
|
||||
}
|
||||
} | _
|
||||
new AbstractClassWithPath() {
|
||||
void call() {
|
||||
}
|
||||
} | _
|
||||
new ChildClassWithPath() {
|
||||
void call() {
|
||||
}
|
||||
} | _
|
||||
}
|
||||
|
||||
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 {
|
||||
group = "redis.clients"
|
||||
module = "jedis"
|
||||
versions = "[1.4.0,)"
|
||||
assertInverse = true
|
||||
versions = "[1.4.0,3.0.0)"
|
||||
}
|
||||
// Muzzle doesn't detect the classLoaderMatcher, so we can't assert fail for 3.0+
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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.startSpan;
|
||||
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.isPublic;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
|
@ -23,13 +25,15 @@ import redis.clients.jedis.Protocol.Command;
|
|||
@AutoService(Instrumenter.class)
|
||||
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() {
|
||||
super("jedis", "redis");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<ClassLoader> classLoaderMatcher() {
|
||||
return not(classLoaderHasClasses("redis.clients.jedis.commands.ProtocolCommand"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return named("redis.clients.jedis.Protocol");
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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
|
||||
|
@ -9,7 +10,8 @@ import spock.lang.Shared
|
|||
|
||||
class JedisClientTest extends AgentTestRunner {
|
||||
|
||||
public static final int PORT = 6399
|
||||
@Shared
|
||||
int port = PortUtils.randomOpenPort()
|
||||
|
||||
@Shared
|
||||
RedisServer redisServer = RedisServer.builder()
|
||||
|
@ -17,9 +19,9 @@ class JedisClientTest extends AgentTestRunner {
|
|||
.setting("bind 127.0.0.1")
|
||||
// set max memory to avoid problems in CI
|
||||
.setting("maxmemory 128M")
|
||||
.port(PORT).build()
|
||||
.port(port).build()
|
||||
@Shared
|
||||
Jedis jedis = new Jedis("localhost", PORT)
|
||||
Jedis jedis = new Jedis("localhost", port)
|
||||
|
||||
def setupSpec() {
|
||||
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 {
|
||||
group = 'com.typesafe.play'
|
||||
module = 'play_2.12'
|
||||
versions = '[2.6.0,)'
|
||||
versions = '[2.6.0,2.8.0)'
|
||||
assertInverse = true
|
||||
}
|
||||
pass {
|
||||
group = 'com.typesafe.play'
|
||||
module = 'play_2.13'
|
||||
versions = '[2.6.0,)'
|
||||
versions = '[2.6.0,2.8.0)'
|
||||
assertInverse = true
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ dependencies {
|
|||
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-test_$scalaVersion", version: '2.+') {
|
||||
exclude group: 'org.eclipse.jetty.websocket', module: 'websocket-client'
|
||||
|
|
|
@ -28,7 +28,14 @@ public class Servlet2Decorator
|
|||
|
||||
@Override
|
||||
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
|
||||
|
|
|
@ -29,7 +29,14 @@ public class Servlet3Decorator
|
|||
|
||||
@Override
|
||||
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
|
||||
|
|
|
@ -16,7 +16,7 @@ def isCI = System.getenv("CI") != null
|
|||
|
||||
allprojects {
|
||||
group = 'com.datadoghq'
|
||||
version = '0.39.0-SNAPSHOT'
|
||||
version = '0.40.0-SNAPSHOT'
|
||||
|
||||
if (isCI) {
|
||||
buildDir = "${rootDir}/workspace/${projectDir.path.replace(rootDir.path, '')}/build/"
|
||||
|
|
|
@ -23,7 +23,7 @@ import java.lang.ref.WeakReference;
|
|||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
@ -254,7 +254,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
|
|||
private final String operationName;
|
||||
|
||||
// Builder attributes
|
||||
private final Map<String, Object> tags = new HashMap<>();
|
||||
private final Map<String, Object> tags = new LinkedHashMap<>();
|
||||
private long timestampMicro;
|
||||
private SpanContext parent;
|
||||
private boolean errorFlag;
|
||||
|
|
|
@ -12,7 +12,7 @@ ext {
|
|||
groovy : groovyVer,
|
||||
logback : "1.2.3",
|
||||
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+)
|
||||
kotlin : "1.3.50",
|
||||
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:jax-rs-annotations-1'
|
||||
include ':dd-java-agent:instrumentation:jax-rs-annotations-2'
|
||||
include ':dd-java-agent:instrumentation:jax-rs-annotations-2:filter-jersey'
|
||||
include ':dd-java-agent:instrumentation:jax-rs-annotations-2:filter-resteasy-3.0'
|
||||
include ':dd-java-agent:instrumentation:jax-rs-annotations-2:filter-resteasy-3.1'
|
||||
include ':dd-java-agent:instrumentation:jax-rs-client-1.1'
|
||||
include ':dd-java-agent:instrumentation:jax-rs-client-2.0'
|
||||
include ':dd-java-agent:instrumentation:jax-rs-client-2.0:connection-error-handling-jersey'
|
||||
|
@ -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:jdbc'
|
||||
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:jms'
|
||||
include ':dd-java-agent:instrumentation:jsp-2.3'
|
||||
|
|
Loading…
Reference in New Issue