Skip to content

Commit ae2b85e

Browse files
authored
Merge pull request #168 from DataDog/ark/spring-boot-classloaders
Enhance Support for spring-boot classloader
2 parents 6fa779a + 42832e7 commit ae2b85e

File tree

23 files changed

+219
-85
lines changed

23 files changed

+219
-85
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.datadoghq.agent;
2+
3+
import com.datadoghq.trace.Trace;
4+
import java.io.File;
5+
import java.io.FileOutputStream;
6+
import java.io.IOException;
7+
import java.io.InputStream;
8+
import java.lang.reflect.Method;
9+
import java.net.URL;
10+
import java.net.URLClassLoader;
11+
import java.util.UUID;
12+
import java.util.jar.JarEntry;
13+
import java.util.jar.JarOutputStream;
14+
import java.util.jar.Manifest;
15+
import org.junit.Assert;
16+
import org.junit.Test;
17+
18+
public class ClassLoaderTest {
19+
20+
/** Assert that we can instrument classloaders which cannot resolve agent advice classes. */
21+
@Test
22+
public void instrumentClassLoadersWithoutAgentClasses() throws Exception {
23+
URL[] classpath = new URL[] {createJarWithClasses(ClassToInstrument.class, Trace.class)};
24+
URLClassLoader loader = new URLClassLoader(classpath, null);
25+
26+
try {
27+
loader.loadClass("com.datadoghq.agent.TracingAgent");
28+
Assert.fail("loader should not see agent classes.");
29+
} catch (ClassNotFoundException cnfe) {
30+
// Good. loader can't see agent classes.
31+
}
32+
33+
Class<?> instrumentedClass = loader.loadClass(ClassToInstrument.class.getName());
34+
Assert.assertEquals(
35+
"Class must be loaded by loader.", loader, instrumentedClass.getClassLoader());
36+
37+
final Class<?> rulesManagerClass =
38+
Class.forName(
39+
"com.datadoghq.agent.InstrumentationRulesManager",
40+
true,
41+
ClassLoader.getSystemClassLoader());
42+
Method isRegisteredMethod = rulesManagerClass.getMethod("isRegistered", Object.class);
43+
Assert.assertTrue(
44+
"Agent did not initialized loader.", (boolean) isRegisteredMethod.invoke(null, loader));
45+
loader.close();
46+
}
47+
48+
/** com.foo.Bar -> com/foo/Bar.class */
49+
public static String getResourceName(Class<?> clazz) {
50+
return clazz.getName().replace('.', '/') + ".class";
51+
}
52+
53+
/**
54+
* Create a temporary jar on the filesystem with the bytes of the given classes.
55+
*
56+
* <p>The jar file will be removed when the jvm exits.
57+
*
58+
* @param classes classes to package into the jar.
59+
* @return the location of the newly created jar.
60+
* @throws IOException
61+
*/
62+
public static URL createJarWithClasses(Class<?>... classes) throws IOException {
63+
final File tmpJar = File.createTempFile(UUID.randomUUID() + "", ".jar");
64+
tmpJar.deleteOnExit();
65+
66+
final Manifest manifest = new Manifest();
67+
JarOutputStream target = new JarOutputStream(new FileOutputStream(tmpJar), manifest);
68+
for (Class<?> clazz : classes) {
69+
addToJar(clazz, target);
70+
}
71+
target.close();
72+
73+
return tmpJar.toURI().toURL();
74+
}
75+
76+
private static void addToJar(Class<?> clazz, JarOutputStream jarOutputStream) throws IOException {
77+
InputStream inputStream = null;
78+
try {
79+
JarEntry entry = new JarEntry(getResourceName(clazz));
80+
jarOutputStream.putNextEntry(entry);
81+
inputStream = clazz.getClassLoader().getResourceAsStream(getResourceName(clazz));
82+
83+
byte[] buffer = new byte[1024];
84+
while (true) {
85+
int count = inputStream.read(buffer);
86+
if (count == -1) {
87+
break;
88+
}
89+
jarOutputStream.write(buffer, 0, count);
90+
}
91+
jarOutputStream.closeEntry();
92+
} finally {
93+
if (inputStream != null) {
94+
inputStream.close();
95+
}
96+
}
97+
}
98+
99+
public static class ClassToInstrument {
100+
@Trace
101+
public static void someMethod() {}
102+
}
103+
}

dd-java-agent/integrations/apache-httpclient-4.3/src/main/java/dd/inst/apachehttpclient/ApacheHttpClientInstrumentation.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package dd.inst.apachehttpclient;
22

33
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
4-
import static dd.trace.ExceptionHandlers.defaultExceptionHandler;
54
import static net.bytebuddy.matcher.ElementMatchers.*;
65

76
import com.datadoghq.agent.integration.DDTracingClientExec;
87
import com.google.auto.service.AutoService;
8+
import dd.trace.DDAdvice;
99
import dd.trace.Instrumenter;
1010
import io.opentracing.util.GlobalTracer;
1111
import net.bytebuddy.agent.builder.AgentBuilder;
@@ -32,11 +32,10 @@ public AgentBuilder instrument(AgentBuilder agentBuilder) {
3232
"org.apache.http.conn.routing.HttpRoute",
3333
"org.apache.http.impl.execchain.ClientExecChain"))
3434
.transform(
35-
new AgentBuilder.Transformer.ForAdvice()
35+
DDAdvice.create()
3636
.advice(
3737
isMethod().and(named("decorateProtocolExec")),
38-
ApacheHttpClientAdvice.class.getName())
39-
.withExceptionHandler(defaultExceptionHandler()))
38+
ApacheHttpClientAdvice.class.getName()))
4039
.asDecorator();
4140
}
4241

dd-java-agent/integrations/aws-sdk/src/main/java/dd/inst/aws/AWSClientInstrumentation.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package dd.inst.aws;
22

33
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
4-
import static dd.trace.ExceptionHandlers.defaultExceptionHandler;
54
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
65
import static net.bytebuddy.matcher.ElementMatchers.named;
76
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
87

98
import com.amazonaws.client.builder.AwsClientBuilder;
109
import com.amazonaws.handlers.RequestHandler2;
1110
import com.google.auto.service.AutoService;
11+
import dd.trace.DDAdvice;
1212
import dd.trace.Instrumenter;
1313
import io.opentracing.contrib.aws.TracingRequestHandler;
1414
import io.opentracing.util.GlobalTracer;
@@ -30,11 +30,10 @@ public AgentBuilder instrument(final AgentBuilder agentBuilder) {
3030
"com.amazonaws.http.client.HttpClientFactory",
3131
"com.amazonaws.http.apache.utils.ApacheUtils"))
3232
.transform(
33-
new AgentBuilder.Transformer.ForAdvice()
33+
DDAdvice.create()
3434
.advice(
3535
named("build").and(takesArguments(0)).and(isPublic()),
36-
AWSClientAdvice.class.getName())
37-
.withExceptionHandler(defaultExceptionHandler()))
36+
AWSClientAdvice.class.getName()))
3837
.asDecorator();
3938
}
4039

dd-java-agent/integrations/datastax-cassandra-3.2/src/main/java/dd/inst/datastax/cassandra/CassandraClientInstrumentation.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package dd.inst.datastax.cassandra;
22

33
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
4-
import static dd.trace.ExceptionHandlers.defaultExceptionHandler;
54
import static net.bytebuddy.matcher.ElementMatchers.*;
65

76
import com.datastax.driver.core.Session;
87
import com.google.auto.service.AutoService;
8+
import dd.trace.DDAdvice;
99
import dd.trace.Instrumenter;
1010
import io.opentracing.Tracer;
1111
import io.opentracing.util.GlobalTracer;
@@ -36,11 +36,10 @@ public AgentBuilder instrument(AgentBuilder agentBuilder) {
3636
"com.google.common.util.concurrent.Futures",
3737
"com.google.common.util.concurrent.ListenableFuture"))
3838
.transform(
39-
new AgentBuilder.Transformer.ForAdvice()
39+
DDAdvice.create()
4040
.advice(
4141
isMethod().and(isPrivate()).and(named("newSession")).and(takesArguments(0)),
42-
CassandraClientAdvice.class.getName())
43-
.withExceptionHandler(defaultExceptionHandler()))
42+
CassandraClientAdvice.class.getName()))
4443
.asDecorator();
4544
}
4645

dd-java-agent/integrations/jms-1/src/main/java/dd/inst/jms1/JMS1MessageConsumerInstrumentation.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import static com.datadoghq.agent.integration.JmsUtil.toResourceName;
44
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
5-
import static dd.trace.ExceptionHandlers.defaultExceptionHandler;
65
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
76
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
87
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
@@ -13,6 +12,7 @@
1312
import com.datadoghq.agent.integration.MessagePropertyTextMap;
1413
import com.datadoghq.trace.DDTags;
1514
import com.google.auto.service.AutoService;
15+
import dd.trace.DDAdvice;
1616
import dd.trace.Instrumenter;
1717
import io.opentracing.ActiveSpan;
1818
import io.opentracing.SpanContext;
@@ -36,14 +36,13 @@ public AgentBuilder instrument(final AgentBuilder agentBuilder) {
3636
not(isInterface()).and(hasSuperType(named("javax.jms.MessageConsumer"))),
3737
not(classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener")))
3838
.transform(
39-
new AgentBuilder.Transformer.ForAdvice()
39+
DDAdvice.create()
4040
.advice(
4141
named("receive").and(takesArguments(0)).and(isPublic()),
4242
ConsumerAdvice.class.getName())
4343
.advice(
4444
named("receiveNoWait").and(takesArguments(0)).and(isPublic()),
45-
ConsumerAdvice.class.getName())
46-
.withExceptionHandler(defaultExceptionHandler()))
45+
ConsumerAdvice.class.getName()))
4746
.asDecorator();
4847
}
4948

dd-java-agent/integrations/jms-1/src/main/java/dd/inst/jms1/JMS1MessageListenerInstrumentation.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import static com.datadoghq.agent.integration.JmsUtil.toResourceName;
44
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
5-
import static dd.trace.ExceptionHandlers.defaultExceptionHandler;
65
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
76
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
87
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
@@ -13,6 +12,7 @@
1312
import com.datadoghq.agent.integration.MessagePropertyTextMap;
1413
import com.datadoghq.trace.DDTags;
1514
import com.google.auto.service.AutoService;
15+
import dd.trace.DDAdvice;
1616
import dd.trace.Instrumenter;
1717
import io.opentracing.ActiveSpan;
1818
import io.opentracing.SpanContext;
@@ -35,13 +35,12 @@ public AgentBuilder instrument(final AgentBuilder agentBuilder) {
3535
not(isInterface()).and(hasSuperType(named("javax.jms.MessageListener"))),
3636
not(classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener")))
3737
.transform(
38-
new AgentBuilder.Transformer.ForAdvice()
38+
DDAdvice.create()
3939
.advice(
4040
named("onMessage")
4141
.and(takesArgument(0, named("javax.jms.Message")))
4242
.and(isPublic()),
43-
MessageListenerAdvice.class.getName())
44-
.withExceptionHandler(defaultExceptionHandler()))
43+
MessageListenerAdvice.class.getName()))
4544
.asDecorator();
4645
}
4746

dd-java-agent/integrations/jms-1/src/main/java/dd/inst/jms1/JMS1MessageProducerInstrumentation.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import static com.datadoghq.agent.integration.JmsUtil.toResourceName;
44
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
5-
import static dd.trace.ExceptionHandlers.defaultExceptionHandler;
65
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
76
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
87
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
@@ -13,6 +12,7 @@
1312
import com.datadoghq.agent.integration.MessagePropertyTextMap;
1413
import com.datadoghq.trace.DDTags;
1514
import com.google.auto.service.AutoService;
15+
import dd.trace.DDAdvice;
1616
import dd.trace.Instrumenter;
1717
import io.opentracing.ActiveSpan;
1818
import io.opentracing.propagation.Format;
@@ -36,7 +36,7 @@ public AgentBuilder instrument(final AgentBuilder agentBuilder) {
3636
not(isInterface()).and(hasSuperType(named("javax.jms.MessageProducer"))),
3737
not(classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener")))
3838
.transform(
39-
new AgentBuilder.Transformer.ForAdvice()
39+
DDAdvice.create()
4040
.advice(
4141
named("send").and(takesArgument(0, named("javax.jms.Message"))).and(isPublic()),
4242
ProducerAdvice.class.getName())
@@ -45,8 +45,7 @@ public AgentBuilder instrument(final AgentBuilder agentBuilder) {
4545
.and(takesArgument(0, named("javax.jms.Destination")))
4646
.and(takesArgument(1, named("javax.jms.Message")))
4747
.and(isPublic()),
48-
ProducerWithDestinationAdvice.class.getName())
49-
.withExceptionHandler(defaultExceptionHandler()))
48+
ProducerWithDestinationAdvice.class.getName()))
5049
.asDecorator();
5150
}
5251

dd-java-agent/integrations/jms-2/src/main/java/dd/inst/jms2/JMS2MessageConsumerInstrumentation.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import static com.datadoghq.agent.integration.JmsUtil.toResourceName;
44
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
5-
import static dd.trace.ExceptionHandlers.defaultExceptionHandler;
65
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
76
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
87
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
@@ -13,6 +12,7 @@
1312
import com.datadoghq.agent.integration.MessagePropertyTextMap;
1413
import com.datadoghq.trace.DDTags;
1514
import com.google.auto.service.AutoService;
15+
import dd.trace.DDAdvice;
1616
import dd.trace.Instrumenter;
1717
import io.opentracing.ActiveSpan;
1818
import io.opentracing.SpanContext;
@@ -36,14 +36,13 @@ public AgentBuilder instrument(final AgentBuilder agentBuilder) {
3636
not(isInterface()).and(hasSuperType(named("javax.jms.MessageConsumer"))),
3737
classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener"))
3838
.transform(
39-
new AgentBuilder.Transformer.ForAdvice()
39+
DDAdvice.create()
4040
.advice(
4141
named("receive").and(takesArguments(0)).and(isPublic()),
4242
ConsumerAdvice.class.getName())
4343
.advice(
4444
named("receiveNoWait").and(takesArguments(0)).and(isPublic()),
45-
ConsumerAdvice.class.getName())
46-
.withExceptionHandler(defaultExceptionHandler()))
45+
ConsumerAdvice.class.getName()))
4746
.asDecorator();
4847
}
4948

dd-java-agent/integrations/jms-2/src/main/java/dd/inst/jms2/JMS2MessageListenerInstrumentation.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import static com.datadoghq.agent.integration.JmsUtil.toResourceName;
44
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
5-
import static dd.trace.ExceptionHandlers.defaultExceptionHandler;
65
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
76
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
87
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
@@ -13,6 +12,7 @@
1312
import com.datadoghq.agent.integration.MessagePropertyTextMap;
1413
import com.datadoghq.trace.DDTags;
1514
import com.google.auto.service.AutoService;
15+
import dd.trace.DDAdvice;
1616
import dd.trace.Instrumenter;
1717
import io.opentracing.ActiveSpan;
1818
import io.opentracing.SpanContext;
@@ -35,13 +35,12 @@ public AgentBuilder instrument(final AgentBuilder agentBuilder) {
3535
not(isInterface()).and(hasSuperType(named("javax.jms.MessageListener"))),
3636
classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener"))
3737
.transform(
38-
new AgentBuilder.Transformer.ForAdvice()
38+
DDAdvice.create()
3939
.advice(
4040
named("onMessage")
4141
.and(takesArgument(0, named("javax.jms.Message")))
4242
.and(isPublic()),
43-
MessageListenerAdvice.class.getName())
44-
.withExceptionHandler(defaultExceptionHandler()))
43+
MessageListenerAdvice.class.getName()))
4544
.asDecorator();
4645
}
4746

dd-java-agent/integrations/jms-2/src/main/java/dd/inst/jms2/JMS2MessageProducerInstrumentation.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import static com.datadoghq.agent.integration.JmsUtil.toResourceName;
44
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
5-
import static dd.trace.ExceptionHandlers.defaultExceptionHandler;
65
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
76
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
87
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
@@ -13,6 +12,7 @@
1312
import com.datadoghq.agent.integration.MessagePropertyTextMap;
1413
import com.datadoghq.trace.DDTags;
1514
import com.google.auto.service.AutoService;
15+
import dd.trace.DDAdvice;
1616
import dd.trace.Instrumenter;
1717
import io.opentracing.ActiveSpan;
1818
import io.opentracing.propagation.Format;
@@ -36,7 +36,7 @@ public AgentBuilder instrument(final AgentBuilder agentBuilder) {
3636
not(isInterface()).and(hasSuperType(named("javax.jms.MessageProducer"))),
3737
classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener"))
3838
.transform(
39-
new AgentBuilder.Transformer.ForAdvice()
39+
DDAdvice.create()
4040
.advice(
4141
named("send").and(takesArgument(0, named("javax.jms.Message"))).and(isPublic()),
4242
ProducerAdvice.class.getName())
@@ -45,8 +45,7 @@ public AgentBuilder instrument(final AgentBuilder agentBuilder) {
4545
.and(takesArgument(0, named("javax.jms.Destination")))
4646
.and(takesArgument(1, named("javax.jms.Message")))
4747
.and(isPublic()),
48-
ProducerWithDestinationAdvice.class.getName())
49-
.withExceptionHandler(defaultExceptionHandler()))
48+
ProducerWithDestinationAdvice.class.getName()))
5049
.asDecorator();
5150
}
5251

0 commit comments

Comments
 (0)