diff --git a/dd-java-agent-ittests/src/test/java/com/datadoghq/agent/ClassLoaderTest.java b/dd-java-agent-ittests/src/test/java/com/datadoghq/agent/ClassLoaderTest.java new file mode 100644 index 0000000000..1a8c02e26d --- /dev/null +++ b/dd-java-agent-ittests/src/test/java/com/datadoghq/agent/ClassLoaderTest.java @@ -0,0 +1,103 @@ +package com.datadoghq.agent; + +import com.datadoghq.trace.Trace; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.UUID; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import org.junit.Assert; +import org.junit.Test; + +public class ClassLoaderTest { + + /** Assert that we can instrument classloaders which cannot resolve agent advice classes. */ + @Test + public void instrumentClassLoadersWithoutAgentClasses() throws Exception { + URL[] classpath = new URL[] {createJarWithClasses(ClassToInstrument.class, Trace.class)}; + URLClassLoader loader = new URLClassLoader(classpath, null); + + try { + loader.loadClass("com.datadoghq.agent.TracingAgent"); + Assert.fail("loader should not see agent classes."); + } catch (ClassNotFoundException cnfe) { + // Good. loader can't see agent classes. + } + + Class instrumentedClass = loader.loadClass(ClassToInstrument.class.getName()); + Assert.assertEquals( + "Class must be loaded by loader.", loader, instrumentedClass.getClassLoader()); + + final Class rulesManagerClass = + Class.forName( + "com.datadoghq.agent.InstrumentationRulesManager", + true, + ClassLoader.getSystemClassLoader()); + Method isRegisteredMethod = rulesManagerClass.getMethod("isRegistered", Object.class); + Assert.assertTrue( + "Agent did not initialized loader.", (boolean) isRegisteredMethod.invoke(null, loader)); + loader.close(); + } + + /** com.foo.Bar -> com/foo/Bar.class */ + public static String getResourceName(Class clazz) { + return clazz.getName().replace('.', '/') + ".class"; + } + + /** + * Create a temporary jar on the filesystem with the bytes of the given classes. + * + *

The jar file will be removed when the jvm exits. + * + * @param classes classes to package into the jar. + * @return the location of the newly created jar. + * @throws IOException + */ + public static URL createJarWithClasses(Class... classes) throws IOException { + final File tmpJar = File.createTempFile(UUID.randomUUID() + "", ".jar"); + tmpJar.deleteOnExit(); + + final Manifest manifest = new Manifest(); + JarOutputStream target = new JarOutputStream(new FileOutputStream(tmpJar), manifest); + for (Class clazz : classes) { + addToJar(clazz, target); + } + target.close(); + + return tmpJar.toURI().toURL(); + } + + private static void addToJar(Class clazz, JarOutputStream jarOutputStream) throws IOException { + InputStream inputStream = null; + try { + JarEntry entry = new JarEntry(getResourceName(clazz)); + jarOutputStream.putNextEntry(entry); + inputStream = clazz.getClassLoader().getResourceAsStream(getResourceName(clazz)); + + byte[] buffer = new byte[1024]; + while (true) { + int count = inputStream.read(buffer); + if (count == -1) { + break; + } + jarOutputStream.write(buffer, 0, count); + } + jarOutputStream.closeEntry(); + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } + + public static class ClassToInstrument { + @Trace + public static void someMethod() {} + } +} diff --git a/dd-java-agent/integrations/apache-httpclient-4.3/src/main/java/dd/inst/apachehttpclient/ApacheHttpClientInstrumentation.java b/dd-java-agent/integrations/apache-httpclient-4.3/src/main/java/dd/inst/apachehttpclient/ApacheHttpClientInstrumentation.java index 6b9d1e53cf..bb476dd9e6 100644 --- a/dd-java-agent/integrations/apache-httpclient-4.3/src/main/java/dd/inst/apachehttpclient/ApacheHttpClientInstrumentation.java +++ b/dd-java-agent/integrations/apache-httpclient-4.3/src/main/java/dd/inst/apachehttpclient/ApacheHttpClientInstrumentation.java @@ -1,11 +1,11 @@ package dd.inst.apachehttpclient; import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.*; import com.datadoghq.agent.integration.DDTracingClientExec; import com.google.auto.service.AutoService; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.util.GlobalTracer; import net.bytebuddy.agent.builder.AgentBuilder; @@ -32,11 +32,10 @@ public class ApacheHttpClientInstrumentation implements Instrumenter { "org.apache.http.conn.routing.HttpRoute", "org.apache.http.impl.execchain.ClientExecChain")) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( isMethod().and(named("decorateProtocolExec")), - ApacheHttpClientAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + ApacheHttpClientAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/integrations/aws-sdk/src/main/java/dd/inst/aws/AWSClientInstrumentation.java b/dd-java-agent/integrations/aws-sdk/src/main/java/dd/inst/aws/AWSClientInstrumentation.java index 897f9cc11e..aba180e597 100644 --- a/dd-java-agent/integrations/aws-sdk/src/main/java/dd/inst/aws/AWSClientInstrumentation.java +++ b/dd-java-agent/integrations/aws-sdk/src/main/java/dd/inst/aws/AWSClientInstrumentation.java @@ -1,7 +1,6 @@ package dd.inst.aws; import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; @@ -9,6 +8,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import com.amazonaws.client.builder.AwsClientBuilder; import com.amazonaws.handlers.RequestHandler2; import com.google.auto.service.AutoService; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.contrib.aws.TracingRequestHandler; import io.opentracing.util.GlobalTracer; @@ -30,11 +30,10 @@ public final class AWSClientInstrumentation implements Instrumenter { "com.amazonaws.http.client.HttpClientFactory", "com.amazonaws.http.apache.utils.ApacheUtils")) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( named("build").and(takesArguments(0)).and(isPublic()), - AWSClientAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + AWSClientAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/integrations/datastax-cassandra-3.2/src/main/java/dd/inst/datastax/cassandra/CassandraClientInstrumentation.java b/dd-java-agent/integrations/datastax-cassandra-3.2/src/main/java/dd/inst/datastax/cassandra/CassandraClientInstrumentation.java index e9c7359bd5..9e4d912b64 100644 --- a/dd-java-agent/integrations/datastax-cassandra-3.2/src/main/java/dd/inst/datastax/cassandra/CassandraClientInstrumentation.java +++ b/dd-java-agent/integrations/datastax-cassandra-3.2/src/main/java/dd/inst/datastax/cassandra/CassandraClientInstrumentation.java @@ -1,11 +1,11 @@ package dd.inst.datastax.cassandra; import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.*; import com.datastax.driver.core.Session; import com.google.auto.service.AutoService; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.Tracer; import io.opentracing.util.GlobalTracer; @@ -36,11 +36,10 @@ public class CassandraClientInstrumentation implements Instrumenter { "com.google.common.util.concurrent.Futures", "com.google.common.util.concurrent.ListenableFuture")) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( isMethod().and(isPrivate()).and(named("newSession")).and(takesArguments(0)), - CassandraClientAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + CassandraClientAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/integrations/jms-1/src/main/java/dd/inst/jms1/JMS1MessageConsumerInstrumentation.java b/dd-java-agent/integrations/jms-1/src/main/java/dd/inst/jms1/JMS1MessageConsumerInstrumentation.java index df762f78e4..01db8cc0d4 100644 --- a/dd-java-agent/integrations/jms-1/src/main/java/dd/inst/jms1/JMS1MessageConsumerInstrumentation.java +++ b/dd-java-agent/integrations/jms-1/src/main/java/dd/inst/jms1/JMS1MessageConsumerInstrumentation.java @@ -2,7 +2,6 @@ package dd.inst.jms1; import static com.datadoghq.agent.integration.JmsUtil.toResourceName; import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.isPublic; @@ -13,6 +12,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import com.datadoghq.agent.integration.MessagePropertyTextMap; import com.datadoghq.trace.DDTags; import com.google.auto.service.AutoService; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.ActiveSpan; import io.opentracing.SpanContext; @@ -36,14 +36,13 @@ public final class JMS1MessageConsumerInstrumentation implements Instrumenter { not(isInterface()).and(hasSuperType(named("javax.jms.MessageConsumer"))), not(classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener"))) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( named("receive").and(takesArguments(0)).and(isPublic()), ConsumerAdvice.class.getName()) .advice( named("receiveNoWait").and(takesArguments(0)).and(isPublic()), - ConsumerAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + ConsumerAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/integrations/jms-1/src/main/java/dd/inst/jms1/JMS1MessageListenerInstrumentation.java b/dd-java-agent/integrations/jms-1/src/main/java/dd/inst/jms1/JMS1MessageListenerInstrumentation.java index f365ad2a8d..58ddb0aa47 100644 --- a/dd-java-agent/integrations/jms-1/src/main/java/dd/inst/jms1/JMS1MessageListenerInstrumentation.java +++ b/dd-java-agent/integrations/jms-1/src/main/java/dd/inst/jms1/JMS1MessageListenerInstrumentation.java @@ -2,7 +2,6 @@ package dd.inst.jms1; import static com.datadoghq.agent.integration.JmsUtil.toResourceName; import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.isPublic; @@ -13,6 +12,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import com.datadoghq.agent.integration.MessagePropertyTextMap; import com.datadoghq.trace.DDTags; import com.google.auto.service.AutoService; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.ActiveSpan; import io.opentracing.SpanContext; @@ -35,13 +35,12 @@ public final class JMS1MessageListenerInstrumentation implements Instrumenter { not(isInterface()).and(hasSuperType(named("javax.jms.MessageListener"))), not(classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener"))) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( named("onMessage") .and(takesArgument(0, named("javax.jms.Message"))) .and(isPublic()), - MessageListenerAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + MessageListenerAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/integrations/jms-1/src/main/java/dd/inst/jms1/JMS1MessageProducerInstrumentation.java b/dd-java-agent/integrations/jms-1/src/main/java/dd/inst/jms1/JMS1MessageProducerInstrumentation.java index a93558b00d..afe7c09141 100644 --- a/dd-java-agent/integrations/jms-1/src/main/java/dd/inst/jms1/JMS1MessageProducerInstrumentation.java +++ b/dd-java-agent/integrations/jms-1/src/main/java/dd/inst/jms1/JMS1MessageProducerInstrumentation.java @@ -2,7 +2,6 @@ package dd.inst.jms1; import static com.datadoghq.agent.integration.JmsUtil.toResourceName; import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.isPublic; @@ -13,6 +12,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import com.datadoghq.agent.integration.MessagePropertyTextMap; import com.datadoghq.trace.DDTags; import com.google.auto.service.AutoService; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.ActiveSpan; import io.opentracing.propagation.Format; @@ -36,7 +36,7 @@ public final class JMS1MessageProducerInstrumentation implements Instrumenter { not(isInterface()).and(hasSuperType(named("javax.jms.MessageProducer"))), not(classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener"))) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( named("send").and(takesArgument(0, named("javax.jms.Message"))).and(isPublic()), ProducerAdvice.class.getName()) @@ -45,8 +45,7 @@ public final class JMS1MessageProducerInstrumentation implements Instrumenter { .and(takesArgument(0, named("javax.jms.Destination"))) .and(takesArgument(1, named("javax.jms.Message"))) .and(isPublic()), - ProducerWithDestinationAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + ProducerWithDestinationAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/integrations/jms-2/src/main/java/dd/inst/jms2/JMS2MessageConsumerInstrumentation.java b/dd-java-agent/integrations/jms-2/src/main/java/dd/inst/jms2/JMS2MessageConsumerInstrumentation.java index b86b3878fb..762f70aaa9 100644 --- a/dd-java-agent/integrations/jms-2/src/main/java/dd/inst/jms2/JMS2MessageConsumerInstrumentation.java +++ b/dd-java-agent/integrations/jms-2/src/main/java/dd/inst/jms2/JMS2MessageConsumerInstrumentation.java @@ -2,7 +2,6 @@ package dd.inst.jms2; import static com.datadoghq.agent.integration.JmsUtil.toResourceName; import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.isPublic; @@ -13,6 +12,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import com.datadoghq.agent.integration.MessagePropertyTextMap; import com.datadoghq.trace.DDTags; import com.google.auto.service.AutoService; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.ActiveSpan; import io.opentracing.SpanContext; @@ -36,14 +36,13 @@ public final class JMS2MessageConsumerInstrumentation implements Instrumenter { not(isInterface()).and(hasSuperType(named("javax.jms.MessageConsumer"))), classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener")) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( named("receive").and(takesArguments(0)).and(isPublic()), ConsumerAdvice.class.getName()) .advice( named("receiveNoWait").and(takesArguments(0)).and(isPublic()), - ConsumerAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + ConsumerAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/integrations/jms-2/src/main/java/dd/inst/jms2/JMS2MessageListenerInstrumentation.java b/dd-java-agent/integrations/jms-2/src/main/java/dd/inst/jms2/JMS2MessageListenerInstrumentation.java index 6837c93f31..ef40c77f1e 100644 --- a/dd-java-agent/integrations/jms-2/src/main/java/dd/inst/jms2/JMS2MessageListenerInstrumentation.java +++ b/dd-java-agent/integrations/jms-2/src/main/java/dd/inst/jms2/JMS2MessageListenerInstrumentation.java @@ -2,7 +2,6 @@ package dd.inst.jms2; import static com.datadoghq.agent.integration.JmsUtil.toResourceName; import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.isPublic; @@ -13,6 +12,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import com.datadoghq.agent.integration.MessagePropertyTextMap; import com.datadoghq.trace.DDTags; import com.google.auto.service.AutoService; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.ActiveSpan; import io.opentracing.SpanContext; @@ -35,13 +35,12 @@ public final class JMS2MessageListenerInstrumentation implements Instrumenter { not(isInterface()).and(hasSuperType(named("javax.jms.MessageListener"))), classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener")) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( named("onMessage") .and(takesArgument(0, named("javax.jms.Message"))) .and(isPublic()), - MessageListenerAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + MessageListenerAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/integrations/jms-2/src/main/java/dd/inst/jms2/JMS2MessageProducerInstrumentation.java b/dd-java-agent/integrations/jms-2/src/main/java/dd/inst/jms2/JMS2MessageProducerInstrumentation.java index 40ecb475ef..4e82ec2074 100644 --- a/dd-java-agent/integrations/jms-2/src/main/java/dd/inst/jms2/JMS2MessageProducerInstrumentation.java +++ b/dd-java-agent/integrations/jms-2/src/main/java/dd/inst/jms2/JMS2MessageProducerInstrumentation.java @@ -2,7 +2,6 @@ package dd.inst.jms2; import static com.datadoghq.agent.integration.JmsUtil.toResourceName; import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.isPublic; @@ -13,6 +12,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import com.datadoghq.agent.integration.MessagePropertyTextMap; import com.datadoghq.trace.DDTags; import com.google.auto.service.AutoService; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.ActiveSpan; import io.opentracing.propagation.Format; @@ -36,7 +36,7 @@ public final class JMS2MessageProducerInstrumentation implements Instrumenter { not(isInterface()).and(hasSuperType(named("javax.jms.MessageProducer"))), classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener")) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( named("send").and(takesArgument(0, named("javax.jms.Message"))).and(isPublic()), ProducerAdvice.class.getName()) @@ -45,8 +45,7 @@ public final class JMS2MessageProducerInstrumentation implements Instrumenter { .and(takesArgument(0, named("javax.jms.Destination"))) .and(takesArgument(1, named("javax.jms.Message"))) .and(isPublic()), - ProducerWithDestinationAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + ProducerWithDestinationAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/integrations/mongo-3.1/src/main/java/dd/inst/mongo/MongoClientInstrumentation.java b/dd-java-agent/integrations/mongo-3.1/src/main/java/dd/inst/mongo/MongoClientInstrumentation.java index 77ed17c347..c84700d584 100644 --- a/dd-java-agent/integrations/mongo-3.1/src/main/java/dd/inst/mongo/MongoClientInstrumentation.java +++ b/dd-java-agent/integrations/mongo-3.1/src/main/java/dd/inst/mongo/MongoClientInstrumentation.java @@ -1,11 +1,11 @@ package dd.inst.mongo; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.*; import com.datadoghq.agent.integration.DDTracingCommandListener; import com.google.auto.service.AutoService; import com.mongodb.MongoClientOptions; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.util.GlobalTracer; import java.lang.reflect.Modifier; @@ -33,11 +33,10 @@ public final class MongoClientInstrumentation implements Instrumenter { null, new TypeDescription.Generic[] {})))))) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( isMethod().and(isPublic()).and(named("build")).and(takesArguments(0)), - MongoClientAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + MongoClientAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/integrations/mongo-async-3.3/src/main/java/dd/inst/mongo/MongoAsyncClientInstrumentation.java b/dd-java-agent/integrations/mongo-async-3.3/src/main/java/dd/inst/mongo/MongoAsyncClientInstrumentation.java index ed99a031ff..a0f7988cf5 100644 --- a/dd-java-agent/integrations/mongo-async-3.3/src/main/java/dd/inst/mongo/MongoAsyncClientInstrumentation.java +++ b/dd-java-agent/integrations/mongo-async-3.3/src/main/java/dd/inst/mongo/MongoAsyncClientInstrumentation.java @@ -1,11 +1,11 @@ package dd.inst.mongo; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.*; import com.datadoghq.agent.integration.DDTracingCommandListener; import com.google.auto.service.AutoService; import com.mongodb.async.client.MongoClientSettings; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.util.GlobalTracer; import java.lang.reflect.Modifier; @@ -33,11 +33,10 @@ public final class MongoAsyncClientInstrumentation implements Instrumenter { null, new TypeDescription.Generic[] {})))))) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( isMethod().and(isPublic()).and(named("build")).and(takesArguments(0)), - MongoAsyncClientAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + MongoAsyncClientAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/integrations/servlet-2/src/main/java/dd/inst/servlet2/HttpServlet2Instrumentation.java b/dd-java-agent/integrations/servlet-2/src/main/java/dd/inst/servlet2/HttpServlet2Instrumentation.java index 3d69fcf400..6fe6a44bbe 100644 --- a/dd-java-agent/integrations/servlet-2/src/main/java/dd/inst/servlet2/HttpServlet2Instrumentation.java +++ b/dd-java-agent/integrations/servlet-2/src/main/java/dd/inst/servlet2/HttpServlet2Instrumentation.java @@ -1,13 +1,13 @@ package dd.inst.servlet2; import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.isProtected; 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; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.ActiveSpan; import io.opentracing.SpanContext; @@ -36,14 +36,13 @@ public final class HttpServlet2Instrumentation implements Instrumenter { classLoaderHasClasses( "javax.servlet.ServletContextEvent", "javax.servlet.FilterChain"))) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( named("service") .and(takesArgument(0, named("javax.servlet.http.HttpServletRequest"))) .and(takesArgument(1, named("javax.servlet.http.HttpServletResponse"))) .and(isProtected()), - HttpServlet2Advice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + HttpServlet2Advice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/integrations/servlet-3/src/main/java/dd/inst/servlet3/HttpServlet3Instrumentation.java b/dd-java-agent/integrations/servlet-3/src/main/java/dd/inst/servlet3/HttpServlet3Instrumentation.java index 9a7c8676c7..1d7fefcadb 100644 --- a/dd-java-agent/integrations/servlet-3/src/main/java/dd/inst/servlet3/HttpServlet3Instrumentation.java +++ b/dd-java-agent/integrations/servlet-3/src/main/java/dd/inst/servlet3/HttpServlet3Instrumentation.java @@ -1,12 +1,12 @@ package dd.inst.servlet3; import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.isProtected; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import com.google.auto.service.AutoService; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.ActiveSpan; import io.opentracing.SpanContext; @@ -36,14 +36,13 @@ public final class HttpServlet3Instrumentation implements Instrumenter { named("javax.servlet.http.HttpServlet"), classLoaderHasClasses("javax.servlet.AsyncEvent", "javax.servlet.AsyncListener")) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( named("service") .and(takesArgument(0, named("javax.servlet.http.HttpServletRequest"))) .and(takesArgument(1, named("javax.servlet.http.HttpServletResponse"))) .and(isProtected()), - HttpServlet3Advice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + HttpServlet3Advice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/integrations/spring-web/src/main/java/dd/inst/springweb/SpringWebInstrumentation.java b/dd-java-agent/integrations/spring-web/src/main/java/dd/inst/springweb/SpringWebInstrumentation.java index eacc4a34ae..c9dda67d97 100644 --- a/dd-java-agent/integrations/spring-web/src/main/java/dd/inst/springweb/SpringWebInstrumentation.java +++ b/dd-java-agent/integrations/spring-web/src/main/java/dd/inst/springweb/SpringWebInstrumentation.java @@ -1,7 +1,6 @@ package dd.inst.springweb; import static dd.trace.ClassLoaderMatcher.classLoaderHasClassWithField; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.isMethod; @@ -13,6 +12,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import com.datadoghq.trace.DDTags; import com.google.auto.service.AutoService; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.ActiveSpan; import io.opentracing.util.GlobalTracer; @@ -38,14 +38,13 @@ public final class SpringWebInstrumentation implements Instrumenter { "org.springframework.web.servlet.HandlerMapping", "BEST_MATCHING_PATTERN_ATTRIBUTE")) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( isMethod() .and(isPublic()) .and(nameStartsWith("handle")) .and(takesArgument(0, named("javax.servlet.http.HttpServletRequest"))), - SpringWebAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + SpringWebAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/src/main/java/com/datadoghq/agent/InstrumentationRulesManager.java b/dd-java-agent/src/main/java/com/datadoghq/agent/InstrumentationRulesManager.java index 2a1c114a24..10075ef423 100644 --- a/dd-java-agent/src/main/java/com/datadoghq/agent/InstrumentationRulesManager.java +++ b/dd-java-agent/src/main/java/com/datadoghq/agent/InstrumentationRulesManager.java @@ -87,6 +87,23 @@ public class InstrumentationRulesManager { AgentRulesManager.INSTANCE.instrumentationRulesManager.initialize(cl); } + /** True if this object's classload has been registered. */ + public static boolean isRegistered(final Object obj) { + if (AgentRulesManager.INSTANCE == null) { + return false; + } + final ClassLoader cl; + if (obj instanceof ClassLoader) { + cl = (ClassLoader) obj; + } else { + cl = obj.getClass().getClassLoader(); + } + synchronized (cl) { + return AgentRulesManager.INSTANCE.instrumentationRulesManager.initializedClassloaders + .contains(cl); + } + } + /** * This method is separated out from initialize to allow Spring Boot's LaunchedURLClassLoader to * call it once it is loaded. diff --git a/dd-java-agent/src/main/java/com/datadoghq/agent/TracingAgent.java b/dd-java-agent/src/main/java/com/datadoghq/agent/TracingAgent.java index dd0866e5f6..5ced172430 100644 --- a/dd-java-agent/src/main/java/com/datadoghq/agent/TracingAgent.java +++ b/dd-java-agent/src/main/java/com/datadoghq/agent/TracingAgent.java @@ -25,7 +25,6 @@ import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; import dd.trace.Instrumenter; import java.lang.instrument.Instrumentation; -import java.lang.reflect.Method; import java.util.Collections; import java.util.ServiceLoader; import java.util.Set; @@ -40,7 +39,12 @@ import net.bytebuddy.utility.JavaModule; @Slf4j public class TracingAgent { - public static void premain(final String agentArgs, final Instrumentation inst) throws Exception { + /** Return the classloader the core agent is running on. */ + public static ClassLoader getAgentClassLoader() { + return TracingAgent.class.getClassLoader(); + } + + public static void premain(String agentArgs, final Instrumentation inst) throws Exception { log.debug("Using premain for loading {}", TracingAgent.class.getSimpleName()); addByteBuddy(inst); AgentRulesManager.initialize(); @@ -111,7 +115,7 @@ public class TracingAgent { final JavaModule module, final boolean loaded, final DynamicType dynamicType) { - log.debug("Transformed {}", typeDescription); + log.debug("Transformed {} -- {}", typeDescription, classLoader); if (classLoader == null) { return; @@ -123,13 +127,9 @@ public class TracingAgent { initializedClassloaders.add(classLoader); try { - final Class rulesManager = - Class.forName("com.datadoghq.agent.InstrumentationRulesManager", true, classLoader); - final Method registerClassLoad = - rulesManager.getDeclaredMethod("registerClassLoad", Object.class); - registerClassLoad.invoke(null, classLoader); + InstrumentationRulesManager.registerClassLoad(classLoader); } catch (final Throwable e) { - log.info("ClassLoad Registration for target " + classLoader, e); + log.error("Failed ClassLoad Registration for target " + classLoader, e); } } } diff --git a/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/annotation/TraceAnnotationInstrumentation.java b/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/annotation/TraceAnnotationInstrumentation.java index a0723f0540..dfd8fe6069 100644 --- a/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/annotation/TraceAnnotationInstrumentation.java +++ b/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/annotation/TraceAnnotationInstrumentation.java @@ -1,12 +1,12 @@ package com.datadoghq.agent.instrumentation.annotation; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.declaresMethod; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith; import com.datadoghq.trace.Trace; import com.google.auto.service.AutoService; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.ActiveSpan; import io.opentracing.tag.Tags; @@ -28,9 +28,7 @@ public final class TraceAnnotationInstrumentation implements Instrumenter { return agentBuilder .type(hasSuperType(declaresMethod(isAnnotatedWith(Trace.class)))) .transform( - new AgentBuilder.Transformer.ForAdvice() - .advice(isAnnotatedWith(Trace.class), TraceAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + DDAdvice.create().advice(isAnnotatedWith(Trace.class), TraceAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/ConnectionInstrumentation.java b/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/ConnectionInstrumentation.java index 73542a1962..769c330987 100644 --- a/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/ConnectionInstrumentation.java +++ b/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/ConnectionInstrumentation.java @@ -1,6 +1,5 @@ package com.datadoghq.agent.instrumentation.jdbc; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; @@ -10,6 +9,7 @@ import static net.bytebuddy.matcher.ElementMatchers.returns; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import com.google.auto.service.AutoService; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import java.sql.Connection; import java.sql.PreparedStatement; @@ -27,13 +27,12 @@ public final class ConnectionInstrumentation implements Instrumenter { return agentBuilder .type(not(isInterface()).and(hasSuperType(named(Connection.class.getName())))) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( nameStartsWith("prepare") .and(takesArgument(0, String.class)) .and(returns(PreparedStatement.class)), - ConnectionAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + ConnectionAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/DriverInstrumentation.java b/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/DriverInstrumentation.java index 2d8c4f8393..dde447676d 100644 --- a/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/DriverInstrumentation.java +++ b/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/DriverInstrumentation.java @@ -1,6 +1,5 @@ package com.datadoghq.agent.instrumentation.jdbc; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.named; @@ -8,6 +7,7 @@ import static net.bytebuddy.matcher.ElementMatchers.not; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import com.google.auto.service.AutoService; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import java.sql.Connection; import java.sql.Driver; @@ -27,11 +27,10 @@ public final class DriverInstrumentation implements Instrumenter { return agentBuilder .type(not(isInterface()).and(hasSuperType(named(Driver.class.getName())))) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( named("connect").and(takesArguments(String.class, Properties.class)), - DriverAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + DriverAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/PreparedStatementInstrumentation.java b/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/PreparedStatementInstrumentation.java index 231a9cbb84..29a0f9daee 100644 --- a/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/PreparedStatementInstrumentation.java +++ b/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/PreparedStatementInstrumentation.java @@ -1,6 +1,5 @@ package com.datadoghq.agent.instrumentation.jdbc; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.isPublic; @@ -11,6 +10,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import com.datadoghq.trace.DDTags; import com.google.auto.service.AutoService; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.ActiveSpan; import io.opentracing.NoopActiveSpanSource; @@ -31,11 +31,10 @@ public final class PreparedStatementInstrumentation implements Instrumenter { return agentBuilder .type(not(isInterface()).and(hasSuperType(named(PreparedStatement.class.getName())))) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( nameStartsWith("execute").and(takesArguments(0)).and(isPublic()), - PreparedStatementAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + PreparedStatementAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/StatementInstrumentation.java b/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/StatementInstrumentation.java index d567022c51..b6279a3121 100644 --- a/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/StatementInstrumentation.java +++ b/dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/StatementInstrumentation.java @@ -1,6 +1,5 @@ package com.datadoghq.agent.instrumentation.jdbc; -import static dd.trace.ExceptionHandlers.defaultExceptionHandler; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.isPublic; @@ -11,6 +10,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import com.datadoghq.trace.DDTags; import com.google.auto.service.AutoService; +import dd.trace.DDAdvice; import dd.trace.Instrumenter; import io.opentracing.ActiveSpan; import io.opentracing.NoopActiveSpanSource; @@ -30,11 +30,10 @@ public final class StatementInstrumentation implements Instrumenter { return agentBuilder .type(not(isInterface()).and(hasSuperType(named(Statement.class.getName())))) .transform( - new AgentBuilder.Transformer.ForAdvice() + DDAdvice.create() .advice( nameStartsWith("execute").and(takesArgument(0, String.class)).and(isPublic()), - StatementAdvice.class.getName()) - .withExceptionHandler(defaultExceptionHandler())) + StatementAdvice.class.getName())) .asDecorator(); } diff --git a/dd-java-agent/tooling/src/main/java/dd/trace/DDAdvice.java b/dd-java-agent/tooling/src/main/java/dd/trace/DDAdvice.java new file mode 100644 index 0000000000..bcb43d2e11 --- /dev/null +++ b/dd-java-agent/tooling/src/main/java/dd/trace/DDAdvice.java @@ -0,0 +1,34 @@ +package dd.trace; + +import java.lang.reflect.Method; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.dynamic.ClassFileLocator; + +/** A bytebuddy advice builder with default DataDog settings. */ +@Slf4j +public class DDAdvice extends AgentBuilder.Transformer.ForAdvice { + private static ClassLoader AGENT_CLASSLOADER; + + static { + try { + Class agentClass = + DDAdvice.class.getClassLoader().loadClass("com.datadoghq.agent.TracingAgent"); + Method getAgentClassloaderMethod = agentClass.getMethod("getAgentClassLoader"); + AGENT_CLASSLOADER = (ClassLoader) getAgentClassloaderMethod.invoke(null); + } catch (Throwable t) { + log.error("Unable to locate agent classloader. Falling back to System Classloader"); + AGENT_CLASSLOADER = ClassLoader.getSystemClassLoader(); + } + } + + public static AgentBuilder.Transformer.ForAdvice create() { + return new DDAdvice() + .with( + new AgentBuilder.LocationStrategy.Simple( + ClassFileLocator.ForClassLoader.of(AGENT_CLASSLOADER))) + .withExceptionHandler(ExceptionHandlers.defaultExceptionHandler()); + } + + private DDAdvice() {} +}