Merge pull request #174 from DataDog/ark/instrumentation_modules
Move Helper Classes into their specific instrumentation modules
This commit is contained in:
commit
5acd650efe
|
@ -1,17 +1,10 @@
|
|||
package com.datadoghq.agent;
|
||||
|
||||
import static dd.test.TestUtils.createJarWithClasses;
|
||||
|
||||
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;
|
||||
|
||||
|
@ -34,68 +27,9 @@ public class ClassLoaderTest {
|
|||
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.
|
||||
*
|
||||
* <p>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() {}
|
||||
|
|
|
@ -20,9 +20,6 @@ public class TraceAnnotationsTest {
|
|||
|
||||
@Before
|
||||
public void beforeTest() throws Exception {
|
||||
Class.forName("com.datadoghq.agent.InstrumentationRulesManager")
|
||||
.getMethod("registerClassLoad")
|
||||
.invoke(null);
|
||||
TestUtils.registerOrReplaceGlobalTracer(tracer);
|
||||
|
||||
writer.start();
|
||||
|
|
|
@ -31,12 +31,8 @@ public class ClassRetransformingBenchmark {
|
|||
|
||||
@Setup
|
||||
public void initializeInstrumentation() {
|
||||
try {
|
||||
final Class<?> manager = Class.forName("com.datadoghq.agent.InstrumentationRulesManager");
|
||||
final Method registerClassLoad = manager.getMethod("registerClassLoad");
|
||||
registerClassLoad.invoke(null);
|
||||
} catch (final Exception e) {
|
||||
}
|
||||
// loading TracedClass will initialize helper injection
|
||||
TracedClass.class.getName();
|
||||
}
|
||||
|
||||
@TearDown
|
||||
|
|
|
@ -21,40 +21,6 @@ dependencies {
|
|||
compile project(':dd-java-agent:tooling')
|
||||
compile project(':dd-trace-annotations')
|
||||
|
||||
compile(project(':dd-java-agent:integrations:apache-httpclient-4.3')) {
|
||||
transitive = false
|
||||
}
|
||||
compile(project(':dd-java-agent:integrations:aws-sdk')) {
|
||||
transitive = false
|
||||
}
|
||||
compile(project(':dd-java-agent:integrations:datastax-cassandra-3.2')) {
|
||||
transitive = false
|
||||
}
|
||||
compile(project(':dd-java-agent:integrations:jms-1')) {
|
||||
transitive = false
|
||||
}
|
||||
compile(project(':dd-java-agent:integrations:jms-2')) {
|
||||
transitive = false
|
||||
}
|
||||
compile(project(':dd-java-agent:integrations:mongo-3.1')) {
|
||||
transitive = false
|
||||
}
|
||||
compile(project(':dd-java-agent:integrations:mongo-async-3.3')) {
|
||||
transitive = false
|
||||
}
|
||||
compile(project(':dd-java-agent:integrations:okhttp-3')) {
|
||||
transitive = false
|
||||
}
|
||||
compile(project(':dd-java-agent:integrations:servlet-2')) {
|
||||
transitive = false
|
||||
}
|
||||
compile(project(':dd-java-agent:integrations:servlet-3')) {
|
||||
transitive = false
|
||||
}
|
||||
compile(project(':dd-java-agent:integrations:spring-web')) {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
compile deps.bytebuddy
|
||||
|
||||
compile deps.autoservice
|
||||
|
@ -64,22 +30,15 @@ dependencies {
|
|||
// ^ Generally a bad idea for libraries, but we're shadowing.
|
||||
|
||||
testCompile deps.opentracingMock
|
||||
|
||||
testCompile(project(path: ':dd-java-agent:integrations:helpers')) {
|
||||
transitive = false
|
||||
}
|
||||
}
|
||||
|
||||
project(':dd-java-agent:integrations:helpers').afterEvaluate { helperProject ->
|
||||
project.processResources {
|
||||
from(helperProject.tasks.shadowJar)
|
||||
rename {
|
||||
it.startsWith("helpers") && it.endsWith(".jar") ?
|
||||
"helpers.jar.zip" :
|
||||
it
|
||||
// add all subprojects under 'integrations' to the agent's dependencies
|
||||
Project java_agent_project = project
|
||||
subprojects { subProj ->
|
||||
if (subProj.getPath().startsWith(java_agent_project.getPath() + ':integrations:')) {
|
||||
java_agent_project.dependencies {
|
||||
compile(project(subProj.getPath()))
|
||||
}
|
||||
}
|
||||
project.processResources.dependsOn helperProject.tasks.shadowJar
|
||||
}
|
||||
|
||||
jar {
|
||||
|
|
|
@ -25,10 +25,9 @@ versionScan {
|
|||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
||||
dependencies {
|
||||
compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3'
|
||||
compileOnly group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3'
|
||||
|
||||
compile project(':dd-trace')
|
||||
compile project(':dd-java-agent:integrations:helpers')
|
||||
compile project(':dd-java-agent:tooling')
|
||||
|
||||
compile deps.bytebuddy
|
||||
|
|
|
@ -3,9 +3,9 @@ package dd.inst.apachehttpclient;
|
|||
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
|
||||
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.HelperInjector;
|
||||
import dd.trace.Instrumenter;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||
|
@ -31,6 +31,7 @@ public class ApacheHttpClientInstrumentation implements Instrumenter {
|
|||
"org.apache.http.client.protocol.HttpClientContext",
|
||||
"org.apache.http.conn.routing.HttpRoute",
|
||||
"org.apache.http.impl.execchain.ClientExecChain"))
|
||||
.transform(new HelperInjector("dd.inst.apachehttpclient.DDTracingClientExec"))
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package com.datadoghq.agent.integration;
|
||||
package dd.inst.apachehttpclient;
|
||||
|
||||
import io.opentracing.ActiveSpan;
|
||||
import io.opentracing.Tracer;
|
|
@ -17,6 +17,9 @@ apply from: "${rootDir}/gradle/java.gradle"
|
|||
|
||||
dependencies {
|
||||
compileOnly group: 'com.amazonaws', name: 'aws-java-sdk-core', version: '1.11.119'
|
||||
compile('io.opentracing.contrib:opentracing-aws-sdk:0.0.2') {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
compile project(':dd-trace')
|
||||
compile project(':dd-java-agent:tooling')
|
||||
|
@ -24,6 +27,4 @@ dependencies {
|
|||
compile deps.bytebuddy
|
||||
compile deps.opentracing
|
||||
compile deps.autoservice
|
||||
|
||||
compile group: 'io.opentracing.contrib', name: 'opentracing-aws-sdk', version: '0.0.2'
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.amazonaws.client.builder.AwsClientBuilder;
|
|||
import com.amazonaws.handlers.RequestHandler2;
|
||||
import com.google.auto.service.AutoService;
|
||||
import dd.trace.DDAdvice;
|
||||
import dd.trace.HelperInjector;
|
||||
import dd.trace.Instrumenter;
|
||||
import io.opentracing.contrib.aws.TracingRequestHandler;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
|
@ -29,6 +30,10 @@ public final class AWSClientInstrumentation implements Instrumenter {
|
|||
classLoaderHasClasses(
|
||||
"com.amazonaws.http.client.HttpClientFactory",
|
||||
"com.amazonaws.http.apache.utils.ApacheUtils"))
|
||||
.transform(
|
||||
new HelperInjector(
|
||||
"io.opentracing.contrib.aws.TracingRequestHandler",
|
||||
"io.opentracing.contrib.aws.SpanDecorator"))
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
|
|
|
@ -32,11 +32,12 @@ versionScan {
|
|||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
||||
dependencies {
|
||||
compile group: 'com.datastax.cassandra', name: 'cassandra-driver-core', version: '3.2.0'
|
||||
compileOnly group: 'com.datastax.cassandra', name: 'cassandra-driver-core', version: '3.2.0'
|
||||
compile('io.opentracing.contrib:opentracing-cassandra-driver:0.0.2') {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
compile project(':dd-trace')
|
||||
// include helpers to pick up opentracing-cassandra-driver helper
|
||||
compile project(':dd-java-agent:integrations:helpers')
|
||||
compile project(':dd-java-agent:tooling')
|
||||
|
||||
compile deps.bytebuddy
|
||||
|
|
|
@ -6,6 +6,7 @@ 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.HelperInjector;
|
||||
import dd.trace.Instrumenter;
|
||||
import io.opentracing.Tracer;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
|
@ -35,6 +36,13 @@ public class CassandraClientInstrumentation implements Instrumenter {
|
|||
"com.google.common.base.Function",
|
||||
"com.google.common.util.concurrent.Futures",
|
||||
"com.google.common.util.concurrent.ListenableFuture"))
|
||||
.transform(
|
||||
new HelperInjector(
|
||||
"io.opentracing.contrib.cassandra.TracingSession",
|
||||
"io.opentracing.contrib.cassandra.TracingSession$1",
|
||||
"io.opentracing.contrib.cassandra.TracingSession$2",
|
||||
"io.opentracing.contrib.cassandra.TracingCluster",
|
||||
"io.opentracing.contrib.cassandra.TracingCluster$1"))
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
apply plugin: "com.github.johnrengelman.shadow"
|
||||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
||||
// We want to keep this jar as lean as possible. Only helpers and OT contrib classes.
|
||||
configurations.compile {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly project(':dd-trace')
|
||||
compileOnly project(':dd-trace-annotations')
|
||||
compileOnly deps.slf4j
|
||||
|
||||
compile group: 'io.opentracing.contrib', name: 'opentracing-web-servlet-filter', version: '0.0.9'
|
||||
compile group: 'io.opentracing.contrib', name: 'opentracing-okhttp3', version: '0.0.5'
|
||||
compile group: 'io.opentracing.contrib', name: 'opentracing-aws-sdk', version: '0.0.2'
|
||||
compile group: 'io.opentracing.contrib', name: 'opentracing-cassandra-driver', version: '0.0.2'
|
||||
|
||||
compileOnly group: 'org.mongodb', name: 'mongo-java-driver', version: '3.4.2'
|
||||
compileOnly group: 'org.mongodb', name: 'mongodb-driver-async', version: '3.4.2'
|
||||
compileOnly group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.6.0'
|
||||
compileOnly group: 'javax.jms', name: 'javax.jms-api', version: '2.0.1'
|
||||
compileOnly group: 'com.datastax.cassandra', name: 'cassandra-driver-core', version: '3.2.0'
|
||||
compileOnly group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3'
|
||||
}
|
||||
|
||||
jar {
|
||||
classifier = 'unbundled'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
classifier null
|
||||
configurations = [project.configurations.compile]
|
||||
|
||||
relocate 'org.slf4j', 'dd.slf4j' // Prevents conflict with other SLF4J instances. Important for premain.
|
||||
|
||||
if (!project.hasProperty("disableShadowRelocate") || !disableShadowRelocate) {
|
||||
// This is needed to align with the relocation of TraceResolver in dd-java-agent.
|
||||
relocate 'io.opentracing.contrib', 'dd.opentracing.contrib'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
exclude(dependency('org.projectlombok:lombok:1.16.18'))
|
||||
}
|
||||
}
|
|
@ -15,7 +15,6 @@ apply from: "${rootDir}/gradle/java.gradle"
|
|||
|
||||
dependencies {
|
||||
compileOnly group: 'javax.jms', name: 'jms-api', version: '1.1-rev-1'
|
||||
compileOnly project(':dd-java-agent:integrations:helpers')
|
||||
|
||||
compile deps.bytebuddy
|
||||
compile deps.opentracing
|
||||
|
@ -24,7 +23,6 @@ dependencies {
|
|||
compile project(':dd-trace')
|
||||
compile project(':dd-java-agent:tooling')
|
||||
testCompile project(':dd-java-agent:tooling').sourceSets.test.output
|
||||
testCompile project(':dd-java-agent:integrations:helpers')
|
||||
|
||||
testCompile group: 'org.apache.activemq.tooling', name: 'activemq-junit', version: '5.14.5'
|
||||
testCompile group: 'org.apache.activemq', name: 'activemq-pool', version: '5.14.5'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package com.datadoghq.agent.integration;
|
||||
package dd.inst.jms.util;
|
||||
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.Message;
|
|
@ -1,4 +1,4 @@
|
|||
package com.datadoghq.agent.integration;
|
||||
package dd.inst.jms.util;
|
||||
|
||||
import io.opentracing.propagation.TextMap;
|
||||
import java.util.Enumeration;
|
||||
|
@ -21,7 +21,7 @@ public class MessagePropertyTextMap implements TextMap {
|
|||
public Iterator<Map.Entry<String, String>> iterator() {
|
||||
final Map<String, String> map = new HashMap<>();
|
||||
try {
|
||||
final Enumeration enumeration = message.getPropertyNames();
|
||||
final Enumeration<?> enumeration = message.getPropertyNames();
|
||||
if (enumeration != null) {
|
||||
while (enumeration.hasMoreElements()) {
|
||||
final String key = (String) enumeration.nextElement();
|
|
@ -1,6 +1,6 @@
|
|||
package dd.inst.jms1;
|
||||
|
||||
import static com.datadoghq.agent.integration.JmsUtil.toResourceName;
|
||||
import static dd.inst.jms.util.JmsUtil.toResourceName;
|
||||
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
|
||||
|
@ -9,10 +9,11 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
|
|||
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||
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.inst.jms.util.MessagePropertyTextMap;
|
||||
import dd.trace.DDAdvice;
|
||||
import dd.trace.HelperInjector;
|
||||
import dd.trace.Instrumenter;
|
||||
import io.opentracing.ActiveSpan;
|
||||
import io.opentracing.SpanContext;
|
||||
|
@ -28,6 +29,8 @@ import net.bytebuddy.asm.Advice;
|
|||
|
||||
@AutoService(Instrumenter.class)
|
||||
public final class JMS1MessageConsumerInstrumentation implements Instrumenter {
|
||||
public static final HelperInjector JMS1_HELPER_INJECTOR =
|
||||
new HelperInjector("dd.inst.jms.util.JmsUtil", "dd.inst.jms.util.MessagePropertyTextMap");
|
||||
|
||||
@Override
|
||||
public AgentBuilder instrument(final AgentBuilder agentBuilder) {
|
||||
|
@ -35,6 +38,7 @@ public final class JMS1MessageConsumerInstrumentation implements Instrumenter {
|
|||
.type(
|
||||
not(isInterface()).and(hasSuperType(named("javax.jms.MessageConsumer"))),
|
||||
not(classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener")))
|
||||
.transform(JMS1_HELPER_INJECTOR)
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package dd.inst.jms1;
|
||||
|
||||
import static com.datadoghq.agent.integration.JmsUtil.toResourceName;
|
||||
import static dd.inst.jms.util.JmsUtil.toResourceName;
|
||||
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
|
||||
|
@ -9,9 +9,9 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
|
|||
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||
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.inst.jms.util.MessagePropertyTextMap;
|
||||
import dd.trace.DDAdvice;
|
||||
import dd.trace.Instrumenter;
|
||||
import io.opentracing.ActiveSpan;
|
||||
|
@ -34,6 +34,7 @@ public final class JMS1MessageListenerInstrumentation implements Instrumenter {
|
|||
.type(
|
||||
not(isInterface()).and(hasSuperType(named("javax.jms.MessageListener"))),
|
||||
not(classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener")))
|
||||
.transform(JMS1MessageConsumerInstrumentation.JMS1_HELPER_INJECTOR)
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package dd.inst.jms1;
|
||||
|
||||
import static com.datadoghq.agent.integration.JmsUtil.toResourceName;
|
||||
import static dd.inst.jms.util.JmsUtil.toResourceName;
|
||||
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
|
||||
|
@ -9,9 +9,9 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
|
|||
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||
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.inst.jms.util.MessagePropertyTextMap;
|
||||
import dd.trace.DDAdvice;
|
||||
import dd.trace.Instrumenter;
|
||||
import io.opentracing.ActiveSpan;
|
||||
|
@ -35,6 +35,7 @@ public final class JMS1MessageProducerInstrumentation implements Instrumenter {
|
|||
.type(
|
||||
not(isInterface()).and(hasSuperType(named("javax.jms.MessageProducer"))),
|
||||
not(classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener")))
|
||||
.transform(JMS1MessageConsumerInstrumentation.JMS1_HELPER_INJECTOR)
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
|
|
|
@ -14,8 +14,11 @@ versionScan {
|
|||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
||||
dependencies {
|
||||
// use jms1 helpers
|
||||
compile(project(':dd-java-agent:integrations:jms-1')) {
|
||||
transitive = false
|
||||
}
|
||||
compileOnly group: 'javax.jms', name: 'javax.jms-api', version: '2.0.1'
|
||||
compileOnly project(':dd-java-agent:integrations:helpers')
|
||||
|
||||
compile deps.bytebuddy
|
||||
compile deps.opentracing
|
||||
|
@ -24,7 +27,6 @@ dependencies {
|
|||
compile project(':dd-trace')
|
||||
compile project(':dd-java-agent:tooling')
|
||||
testCompile project(':dd-java-agent:tooling').sourceSets.test.output
|
||||
testCompile project(':dd-java-agent:integrations:helpers')
|
||||
|
||||
testCompile group: 'org.hornetq', name: 'hornetq-jms-client', version: '2.4.7.Final'
|
||||
testCompile group: 'org.hornetq', name: 'hornetq-jms-server', version: '2.4.7.Final'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package dd.inst.jms2;
|
||||
|
||||
import static com.datadoghq.agent.integration.JmsUtil.toResourceName;
|
||||
import static dd.inst.jms.util.JmsUtil.toResourceName;
|
||||
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
|
||||
|
@ -9,10 +9,11 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
|
|||
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||
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.inst.jms.util.MessagePropertyTextMap;
|
||||
import dd.trace.DDAdvice;
|
||||
import dd.trace.HelperInjector;
|
||||
import dd.trace.Instrumenter;
|
||||
import io.opentracing.ActiveSpan;
|
||||
import io.opentracing.SpanContext;
|
||||
|
@ -28,6 +29,8 @@ import net.bytebuddy.asm.Advice;
|
|||
|
||||
@AutoService(Instrumenter.class)
|
||||
public final class JMS2MessageConsumerInstrumentation implements Instrumenter {
|
||||
public static final HelperInjector JMS2_HELPER_INJECTOR =
|
||||
new HelperInjector("dd.inst.jms.util.JmsUtil", "dd.inst.jms.util.MessagePropertyTextMap");
|
||||
|
||||
@Override
|
||||
public AgentBuilder instrument(final AgentBuilder agentBuilder) {
|
||||
|
@ -35,6 +38,7 @@ public final class JMS2MessageConsumerInstrumentation implements Instrumenter {
|
|||
.type(
|
||||
not(isInterface()).and(hasSuperType(named("javax.jms.MessageConsumer"))),
|
||||
classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener"))
|
||||
.transform(JMS2_HELPER_INJECTOR)
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package dd.inst.jms2;
|
||||
|
||||
import static com.datadoghq.agent.integration.JmsUtil.toResourceName;
|
||||
import static dd.inst.jms.util.JmsUtil.toResourceName;
|
||||
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
|
||||
|
@ -9,9 +9,9 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
|
|||
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||
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.inst.jms.util.MessagePropertyTextMap;
|
||||
import dd.trace.DDAdvice;
|
||||
import dd.trace.Instrumenter;
|
||||
import io.opentracing.ActiveSpan;
|
||||
|
@ -34,6 +34,7 @@ public final class JMS2MessageListenerInstrumentation implements Instrumenter {
|
|||
.type(
|
||||
not(isInterface()).and(hasSuperType(named("javax.jms.MessageListener"))),
|
||||
classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener"))
|
||||
.transform(JMS2MessageConsumerInstrumentation.JMS2_HELPER_INJECTOR)
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package dd.inst.jms2;
|
||||
|
||||
import static com.datadoghq.agent.integration.JmsUtil.toResourceName;
|
||||
import static dd.inst.jms.util.JmsUtil.toResourceName;
|
||||
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
|
||||
|
@ -9,9 +9,9 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
|
|||
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||
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.inst.jms.util.MessagePropertyTextMap;
|
||||
import dd.trace.DDAdvice;
|
||||
import dd.trace.Instrumenter;
|
||||
import io.opentracing.ActiveSpan;
|
||||
|
@ -35,6 +35,7 @@ public final class JMS2MessageProducerInstrumentation implements Instrumenter {
|
|||
.type(
|
||||
not(isInterface()).and(hasSuperType(named("javax.jms.MessageProducer"))),
|
||||
classLoaderHasClasses("javax.jms.JMSContext", "javax.jms.CompletionListener"))
|
||||
.transform(JMS2MessageConsumerInstrumentation.JMS2_HELPER_INJECTOR)
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
|
|
|
@ -13,12 +13,13 @@ versionScan {
|
|||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
||||
dependencies {
|
||||
compile group: 'org.mongodb', name: 'mongo-java-driver', version: '3.4.2'
|
||||
compileOnly group: 'org.mongodb', name: 'mongo-java-driver', version: '3.4.2'
|
||||
|
||||
compile project(':dd-trace')
|
||||
compile project(':dd-java-agent:integrations:helpers')
|
||||
compile project(':dd-java-agent:tooling')
|
||||
|
||||
compile deps.bytebuddy
|
||||
compile deps.opentracing
|
||||
|
||||
testCompile group: 'org.mongodb', name: 'mongo-java-driver', version: '3.4.2'
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package com.datadoghq.agent.integration;
|
||||
package dd.inst.mongo;
|
||||
|
||||
import com.datadoghq.trace.DDTags;
|
||||
import com.mongodb.event.CommandFailedEvent;
|
|
@ -2,10 +2,10 @@ package dd.inst.mongo;
|
|||
|
||||
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.HelperInjector;
|
||||
import dd.trace.Instrumenter;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
@ -15,6 +15,8 @@ import net.bytebuddy.description.type.TypeDescription;
|
|||
|
||||
@AutoService(Instrumenter.class)
|
||||
public final class MongoClientInstrumentation implements Instrumenter {
|
||||
public static final HelperInjector MONGO_HELPER_INJECTOR =
|
||||
new HelperInjector("dd.inst.mongo.DDTracingCommandListener");
|
||||
|
||||
@Override
|
||||
public AgentBuilder instrument(AgentBuilder agentBuilder) {
|
||||
|
@ -32,6 +34,7 @@ public final class MongoClientInstrumentation implements Instrumenter {
|
|||
null,
|
||||
new TypeDescription.Generic[] {})))
|
||||
.and(isPublic()))))
|
||||
.transform(MONGO_HELPER_INJECTOR)
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
|
|
|
@ -2,7 +2,6 @@ package dd.inst.mongo;
|
|||
|
||||
import static org.assertj.core.api.Java6Assertions.assertThat;
|
||||
|
||||
import com.datadoghq.agent.integration.DDTracingCommandListener;
|
||||
import com.datadoghq.trace.DDSpan;
|
||||
import com.datadoghq.trace.DDTracer;
|
||||
import com.mongodb.ServerAddress;
|
||||
|
|
|
@ -13,12 +13,15 @@ versionScan {
|
|||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
||||
dependencies {
|
||||
compile group: 'org.mongodb', name: 'mongodb-driver-async', version: '3.4.2'
|
||||
// use mongo listener
|
||||
compile(project(':dd-java-agent:integrations:mongo-3.1')) {
|
||||
transitive = false
|
||||
}
|
||||
compileOnly group: 'org.mongodb', name: 'mongodb-driver-async', version: '3.4.2'
|
||||
|
||||
compile project(':dd-trace')
|
||||
compile project(':dd-java-agent:integrations:helpers')
|
||||
compile project(':dd-java-agent:tooling')
|
||||
|
||||
compile deps.bytebuddy
|
||||
compile deps.opentracing
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package dd.inst.mongo;
|
|||
|
||||
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;
|
||||
|
@ -32,6 +31,7 @@ public final class MongoAsyncClientInstrumentation implements Instrumenter {
|
|||
null,
|
||||
new TypeDescription.Generic[] {})))
|
||||
.and(isPublic()))))
|
||||
.transform(MongoClientInstrumentation.MONGO_HELPER_INJECTOR)
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
|
|
|
@ -15,14 +15,17 @@ versionScan {
|
|||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
||||
dependencies {
|
||||
compile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.0.0'
|
||||
compileOnly group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.0.0'
|
||||
compile('io.opentracing.contrib:opentracing-okhttp3:0.0.5') {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
compile project(':dd-trace')
|
||||
compile project(':dd-java-agent:integrations:helpers')
|
||||
compile project(':dd-java-agent:tooling')
|
||||
|
||||
compile deps.bytebuddy
|
||||
compile deps.opentracing
|
||||
|
||||
testCompile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.0.0'
|
||||
testCompile project(':dd-java-agent:tooling').sourceSets.test.output
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
package dd.inst.okhttp3;
|
||||
|
||||
import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
|
||||
import static dd.trace.ExceptionHandlers.defaultExceptionHandler;
|
||||
import static io.opentracing.contrib.okhttp3.OkHttpClientSpanDecorator.STANDARD_TAGS;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
|
||||
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.HelperInjector;
|
||||
import dd.trace.Instrumenter;
|
||||
import io.opentracing.contrib.okhttp3.TracingInterceptor;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
|
@ -27,11 +28,23 @@ public class OkHttp3Instrumentation implements Instrumenter {
|
|||
named("okhttp3.OkHttpClient"),
|
||||
classLoaderHasClasses("okhttp3.Cookie", "okhttp3.ConnectionPool", "okhttp3.Headers"))
|
||||
.transform(
|
||||
new AgentBuilder.Transformer.ForAdvice()
|
||||
new HelperInjector(
|
||||
"io.opentracing.contrib.okhttp3.OkHttpClientSpanDecorator",
|
||||
"io.opentracing.contrib.okhttp3.OkHttpClientSpanDecorator$1",
|
||||
"io.opentracing.contrib.okhttp3.TagWrapper",
|
||||
"io.opentracing.contrib.okhttp3.TracingInterceptor",
|
||||
"io.opentracing.contrib.okhttp3.RequestBuilderInjectAdapter",
|
||||
"io.opentracing.contrib.okhttp3.TracingCallFactory",
|
||||
"io.opentracing.contrib.okhttp3.TracingCallFactory$NetworkInterceptor",
|
||||
"io.opentracing.contrib.okhttp3.TracingCallFactory$1",
|
||||
"io.opentracing.contrib.okhttp3.concurrent.TracingExecutorService",
|
||||
"io.opentracing.contrib.okhttp3.concurrent.TracedCallable",
|
||||
"io.opentracing.contrib.okhttp3.concurrent.TracedRunnable"))
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
isConstructor().and(takesArgument(0, named("okhttp3.OkHttpClient$Builder"))),
|
||||
OkHttp3Advice.class.getName())
|
||||
.withExceptionHandler(defaultExceptionHandler()))
|
||||
OkHttp3Advice.class.getName()))
|
||||
.asDecorator();
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,9 @@ apply from: "${rootDir}/gradle/java.gradle"
|
|||
|
||||
dependencies {
|
||||
compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.3'
|
||||
compileOnly group: 'io.opentracing.contrib', name: 'opentracing-web-servlet-filter', version: '0.0.9'
|
||||
compile('io.opentracing.contrib:opentracing-web-servlet-filter:0.0.9') {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
compile project(':dd-trace')
|
||||
compile project(':dd-java-agent:tooling')
|
||||
|
|
|
@ -10,6 +10,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
|||
|
||||
import com.google.auto.service.AutoService;
|
||||
import dd.trace.DDAdvice;
|
||||
import dd.trace.HelperInjector;
|
||||
import dd.trace.Instrumenter;
|
||||
import io.opentracing.ActiveSpan;
|
||||
import io.opentracing.SpanContext;
|
||||
|
@ -37,6 +38,14 @@ public final class HttpServlet2Instrumentation implements Instrumenter {
|
|||
.and(
|
||||
classLoaderHasClasses(
|
||||
"javax.servlet.ServletContextEvent", "javax.servlet.FilterChain")))
|
||||
.transform(
|
||||
new HelperInjector(
|
||||
"io.opentracing.contrib.web.servlet.filter.HttpServletRequestExtractAdapter",
|
||||
"io.opentracing.contrib.web.servlet.filter.HttpServletRequestExtractAdapter$MultivaluedMapFlatIterator",
|
||||
"io.opentracing.contrib.web.servlet.filter.ServletFilterSpanDecorator",
|
||||
"io.opentracing.contrib.web.servlet.filter.ServletFilterSpanDecorator$1",
|
||||
"io.opentracing.contrib.web.servlet.filter.TracingFilter",
|
||||
"io.opentracing.contrib.web.servlet.filter.TracingFilter$1"))
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
|
|
|
@ -15,7 +15,9 @@ apply from: "${rootDir}/gradle/java.gradle"
|
|||
|
||||
dependencies {
|
||||
compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.0.1'
|
||||
compileOnly group: 'io.opentracing.contrib', name: 'opentracing-web-servlet-filter', version: '0.0.9'
|
||||
compile('io.opentracing.contrib:opentracing-web-servlet-filter:0.0.9') {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
compile project(':dd-trace')
|
||||
compile project(':dd-java-agent:tooling')
|
||||
|
|
|
@ -10,6 +10,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
|||
|
||||
import com.google.auto.service.AutoService;
|
||||
import dd.trace.DDAdvice;
|
||||
import dd.trace.HelperInjector;
|
||||
import dd.trace.Instrumenter;
|
||||
import io.opentracing.ActiveSpan;
|
||||
import io.opentracing.SpanContext;
|
||||
|
@ -38,6 +39,14 @@ public final class HttpServlet3Instrumentation implements Instrumenter {
|
|||
.type(
|
||||
not(isInterface()).and(hasSuperType(named("javax.servlet.http.HttpServlet"))),
|
||||
classLoaderHasClasses("javax.servlet.AsyncEvent", "javax.servlet.AsyncListener"))
|
||||
.transform(
|
||||
new HelperInjector(
|
||||
"io.opentracing.contrib.web.servlet.filter.HttpServletRequestExtractAdapter",
|
||||
"io.opentracing.contrib.web.servlet.filter.HttpServletRequestExtractAdapter$MultivaluedMapFlatIterator",
|
||||
"io.opentracing.contrib.web.servlet.filter.ServletFilterSpanDecorator",
|
||||
"io.opentracing.contrib.web.servlet.filter.ServletFilterSpanDecorator$1",
|
||||
"io.opentracing.contrib.web.servlet.filter.TracingFilter",
|
||||
"io.opentracing.contrib.web.servlet.filter.TracingFilter$1"))
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
package com.datadoghq.agent;
|
||||
|
||||
import com.datadoghq.trace.DDTraceAnnotationsInfo;
|
||||
import com.datadoghq.trace.DDTraceInfo;
|
||||
import com.datadoghq.trace.resolver.DDTracerFactory;
|
||||
import com.datadoghq.trace.resolver.FactoryUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class AgentRulesManager {
|
||||
|
||||
// Initialize the info classes so they print out their version info:
|
||||
private static final String ddJavaAgentVersion = DDJavaAgentInfo.VERSION;
|
||||
private static final String ddTraceVersion = DDTraceInfo.VERSION;
|
||||
private static final String ddTraceAnnotationsVersion = DDTraceAnnotationsInfo.VERSION;
|
||||
|
||||
private static final String INITIALIZER_RULES = "initializer-rules.btm";
|
||||
|
||||
protected static volatile AgentRulesManager INSTANCE;
|
||||
|
||||
protected final TracingAgentConfig agentTracerConfig;
|
||||
protected final InstrumentationRulesManager instrumentationRulesManager;
|
||||
|
||||
public AgentRulesManager(final TracingAgentConfig config) {
|
||||
agentTracerConfig = config;
|
||||
instrumentationRulesManager = new InstrumentationRulesManager(config, this);
|
||||
instrumentationRulesManager.initTracer();
|
||||
}
|
||||
|
||||
/** This method initializes the manager. */
|
||||
public static void initialize() {
|
||||
log.debug("Initializing {}", AgentRulesManager.class.getSimpleName());
|
||||
|
||||
final TracingAgentConfig config =
|
||||
FactoryUtils.loadConfigFromFilePropertyOrResource(
|
||||
DDTracerFactory.SYSTEM_PROPERTY_CONFIG_PATH,
|
||||
DDTracerFactory.CONFIG_PATH,
|
||||
TracingAgentConfig.class);
|
||||
|
||||
log.debug("Configuration: {}", config.toString());
|
||||
|
||||
final AgentRulesManager manager = new AgentRulesManager(config);
|
||||
|
||||
INSTANCE = manager;
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
package com.datadoghq.agent;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
// TODO: consider augmenting with com.sun.xml.internal.bind.v2.runtime.reflect.opt.Injector
|
||||
public class ClassLoaderIntegrationInjector {
|
||||
private final Map<ZipEntry, byte[]> entries;
|
||||
private final Map<ClassLoader, Method> invocationPoints = Maps.newConcurrentMap();
|
||||
|
||||
public ClassLoaderIntegrationInjector(final Map<ZipEntry, byte[]> allEntries) {
|
||||
this.entries = Maps.newHashMap(allEntries);
|
||||
for (final Iterator<Map.Entry<ZipEntry, byte[]>> it = entries.entrySet().iterator();
|
||||
it.hasNext();
|
||||
) {
|
||||
final Map.Entry<ZipEntry, byte[]> entry = it.next();
|
||||
if (!entry.getKey().getName().endsWith(".class")) {
|
||||
// remove all non-class files
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void inject(final ClassLoader cl) {
|
||||
try {
|
||||
final Method inovcationPoint = getInovcationPoint(cl);
|
||||
final Map<ZipEntry, byte[]> toInject = Maps.newHashMap(entries);
|
||||
final Map<ZipEntry, byte[]> injectedEntries = Maps.newHashMap();
|
||||
final List<Throwable> lastErrors = new LinkedList<>();
|
||||
boolean successfulyAdded = true;
|
||||
while (!toInject.isEmpty() && successfulyAdded) {
|
||||
log.debug("Attempting to inject {} entries into {}", toInject.size(), cl);
|
||||
successfulyAdded = false;
|
||||
lastErrors.clear();
|
||||
for (final Map.Entry<ZipEntry, byte[]> entry : toInject.entrySet()) {
|
||||
final byte[] bytes = entry.getValue();
|
||||
try {
|
||||
inovcationPoint.invoke(cl, bytes, 0, bytes.length);
|
||||
injectedEntries.put(entry.getKey(), entry.getValue());
|
||||
successfulyAdded = true;
|
||||
} catch (final InvocationTargetException e) {
|
||||
lastErrors.add(e);
|
||||
}
|
||||
}
|
||||
toInject.keySet().removeAll(injectedEntries.keySet());
|
||||
}
|
||||
log.info("Successfully injected {} classes into {}", injectedEntries.size(), cl);
|
||||
log.info("Failed injecting {} classes into {}", toInject.size(), cl);
|
||||
log.debug("\nSuccesses: {}", injectedEntries);
|
||||
log.debug("\nFailures: {}", toInject);
|
||||
for (final Throwable error : lastErrors) {
|
||||
log.debug("Injection error", error.getCause());
|
||||
}
|
||||
|
||||
} catch (final NoSuchMethodException e) {
|
||||
log.error("Error getting 'defineClass' method from {}", cl);
|
||||
} catch (final IllegalAccessException e) {
|
||||
log.error("Error accessing 'defineClass' method on {}", cl);
|
||||
}
|
||||
}
|
||||
|
||||
private Method getInovcationPoint(final ClassLoader cl) throws NoSuchMethodException {
|
||||
if (invocationPoints.containsKey(invocationPoints)) {
|
||||
return invocationPoints.get(invocationPoints);
|
||||
}
|
||||
Class<?> clazz = cl.getClass();
|
||||
NoSuchMethodException firstException = null;
|
||||
while (clazz != null) {
|
||||
try {
|
||||
// defineClass is protected so we may need to check up the class hierarchy.
|
||||
final Method invocationPoint =
|
||||
clazz.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
invocationPoint.setAccessible(true);
|
||||
invocationPoints.put(cl, invocationPoint);
|
||||
return invocationPoint;
|
||||
} catch (final NoSuchMethodException e) {
|
||||
if (firstException == null) {
|
||||
firstException = e;
|
||||
}
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw firstException;
|
||||
}
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
package com.datadoghq.agent;
|
||||
|
||||
import com.datadoghq.trace.resolver.FactoryUtils;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Utility class to check the validity of the classpath concerning the java automated
|
||||
* instrumentations
|
||||
*/
|
||||
@Slf4j
|
||||
public class InstrumentationChecker {
|
||||
|
||||
private static final String CONFIG_FILE = "dd-trace-supported-framework";
|
||||
|
||||
private final Map<String, List<ArtifactSupport>> rules;
|
||||
|
||||
/* For testing purpose */
|
||||
InstrumentationChecker(
|
||||
final Map<String, List<ArtifactSupport>> rules, final Map<String, String> frameworks) {
|
||||
this.rules = rules;
|
||||
}
|
||||
|
||||
public InstrumentationChecker() {
|
||||
rules =
|
||||
FactoryUtils.loadConfigFromResource(
|
||||
CONFIG_FILE, new TypeReference<Map<String, List<ArtifactSupport>>>() {});
|
||||
}
|
||||
|
||||
public List<String> getUnsupportedRules(final ClassLoader classLoader) {
|
||||
log.debug("Checking rule compatibility on classloader {}", classLoader);
|
||||
|
||||
final List<String> unsupportedRules = new ArrayList<>();
|
||||
for (final String rule : rules.keySet()) {
|
||||
|
||||
// Check rules
|
||||
boolean supported = false;
|
||||
for (final ArtifactSupport check : rules.get(rule)) {
|
||||
log.debug("Checking rule {}", check);
|
||||
|
||||
boolean matched =
|
||||
(check.identifyingPresentClasses != null
|
||||
&& !check.identifyingPresentClasses.entrySet().isEmpty())
|
||||
|| (check.identifyingMissingClasses != null
|
||||
&& !check.identifyingMissingClasses.isEmpty());
|
||||
if (check.identifyingPresentClasses != null) {
|
||||
for (final Map.Entry<String, String> identifier :
|
||||
check.identifyingPresentClasses.entrySet()) {
|
||||
final boolean classPresent = isClassPresent(identifier.getKey(), classLoader);
|
||||
if (!classPresent) {
|
||||
log.debug(
|
||||
"Instrumentation {} not applied due to missing class {}.", rule, identifier);
|
||||
} else {
|
||||
final String identifyingMethod = identifier.getValue();
|
||||
if (identifyingMethod != null && !identifyingMethod.isEmpty()) {
|
||||
final Class clazz = getClassIfPresent(identifier.getKey(), classLoader);
|
||||
// already confirmed above the class is there.
|
||||
final Method[] declaredMethods = clazz.getDeclaredMethods();
|
||||
boolean methodFound = false;
|
||||
for (final Method m : declaredMethods) {
|
||||
if (m.getName().equals(identifyingMethod)) {
|
||||
methodFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!methodFound) {
|
||||
log.debug(
|
||||
"Instrumentation {} not applied due to missing method {}.{}",
|
||||
rule,
|
||||
identifier.getKey(),
|
||||
identifyingMethod);
|
||||
matched = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
matched &= classPresent;
|
||||
}
|
||||
}
|
||||
if (check.identifyingMissingClasses != null) {
|
||||
for (final String identifyingClass : check.identifyingMissingClasses) {
|
||||
final boolean classMissing = !isClassPresent(identifyingClass, classLoader);
|
||||
if (!classMissing) {
|
||||
log.debug(
|
||||
"Instrumentation {} not applied due to present class {}.",
|
||||
rule,
|
||||
identifyingClass);
|
||||
}
|
||||
matched &= classMissing;
|
||||
}
|
||||
}
|
||||
|
||||
supported |= matched;
|
||||
if (supported) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!supported) {
|
||||
log.info("Instrumentation rule={} is not supported", rule);
|
||||
unsupportedRules.add(rule);
|
||||
}
|
||||
}
|
||||
|
||||
return unsupportedRules;
|
||||
}
|
||||
|
||||
static boolean isClassPresent(
|
||||
final String identifyingPresentClass, final ClassLoader classLoader) {
|
||||
return getClassIfPresent(identifyingPresentClass, classLoader) != null;
|
||||
}
|
||||
|
||||
static Class getClassIfPresent(
|
||||
final String identifyingPresentClass, final ClassLoader classLoader) {
|
||||
try {
|
||||
return Class.forName(identifyingPresentClass, false, classLoader);
|
||||
} catch (final Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
static class ArtifactSupport {
|
||||
private String artifact;
|
||||
|
||||
@JsonProperty("supported_version")
|
||||
private String supportedVersion;
|
||||
|
||||
@JsonProperty("identifying_present_classes")
|
||||
private Map<String, String> identifyingPresentClasses = Collections.emptyMap();
|
||||
|
||||
@JsonProperty("identifying_missing_classes")
|
||||
private List<String> identifyingMissingClasses = Collections.emptyList();
|
||||
}
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
package com.datadoghq.agent;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import io.opentracing.Tracer;
|
||||
import io.opentracing.contrib.tracerresolver.TracerResolver;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* This manager is loaded at pre-main. It loads all the scripts contained in all the 'oatrules.btm'
|
||||
* resource files.
|
||||
*/
|
||||
@Slf4j
|
||||
public class InstrumentationRulesManager {
|
||||
|
||||
private static final String INTEGRATION_RULES = "integration-rules.btm";
|
||||
private static final String HELPERS_NAME = "/helpers.jar.zip";
|
||||
|
||||
private static final Object SYNC = new Object();
|
||||
|
||||
private final TracingAgentConfig config;
|
||||
private final AgentRulesManager agentRulesManager;
|
||||
private final ClassLoaderIntegrationInjector injector;
|
||||
private final InstrumentationChecker checker = new InstrumentationChecker();
|
||||
|
||||
private final Set<ClassLoader> initializedClassloaders =
|
||||
Collections.newSetFromMap(new WeakHashMap<ClassLoader, Boolean>());
|
||||
|
||||
public InstrumentationRulesManager(
|
||||
final TracingAgentConfig config, final AgentRulesManager agentRulesManager) {
|
||||
this.config = config;
|
||||
this.agentRulesManager = agentRulesManager;
|
||||
final InputStream helpersStream = this.getClass().getResourceAsStream(HELPERS_NAME);
|
||||
final ZipInputStream stream = new ZipInputStream(helpersStream);
|
||||
final Map<ZipEntry, byte[]> helperEntries = Maps.newHashMap();
|
||||
try {
|
||||
ZipEntry entry = stream.getNextEntry();
|
||||
while (entry != null) {
|
||||
if (entry.isDirectory()) {
|
||||
entry = stream.getNextEntry();
|
||||
continue;
|
||||
}
|
||||
// this is a buffer, so the long->int truncation is not an issue.
|
||||
final ByteArrayOutputStream os = new ByteArrayOutputStream((int) entry.getSize());
|
||||
|
||||
int n;
|
||||
final byte[] buf = new byte[1024];
|
||||
while ((n = stream.read(buf, 0, 1024)) > -1) {
|
||||
os.write(buf, 0, n);
|
||||
}
|
||||
helperEntries.put(entry, os.toByteArray());
|
||||
entry = stream.getNextEntry();
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
log.error("Error extracting helpers", e);
|
||||
}
|
||||
injector = new ClassLoaderIntegrationInjector(helperEntries);
|
||||
}
|
||||
|
||||
public static void registerClassLoad() {
|
||||
log.debug("Register called by class initializer.");
|
||||
registerClassLoad(Thread.currentThread().getContextClassLoader());
|
||||
}
|
||||
|
||||
public static void registerClassLoad(final Object obj) {
|
||||
if (AgentRulesManager.INSTANCE == null) {
|
||||
return;
|
||||
}
|
||||
final ClassLoader cl;
|
||||
if (obj instanceof ClassLoader) {
|
||||
cl = (ClassLoader) obj;
|
||||
log.debug("Calling initialize with {}", cl);
|
||||
} else {
|
||||
cl = obj.getClass().getClassLoader();
|
||||
log.debug("Calling initialize with {} and classloader {}", obj, cl);
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @param classLoader
|
||||
*/
|
||||
public void initialize(final ClassLoader classLoader) {
|
||||
synchronized (classLoader) {
|
||||
if (initializedClassloaders.contains(classLoader)) {
|
||||
return;
|
||||
}
|
||||
initializedClassloaders.add(classLoader);
|
||||
}
|
||||
log.info("Initializing on classloader {}", classLoader);
|
||||
|
||||
injector.inject(classLoader);
|
||||
}
|
||||
|
||||
void initTracer() {
|
||||
synchronized (SYNC) {
|
||||
if (!GlobalTracer.isRegistered()) {
|
||||
// Try to obtain a tracer using the TracerResolver
|
||||
final Tracer resolved = TracerResolver.resolveTracer();
|
||||
if (resolved != null) {
|
||||
try {
|
||||
GlobalTracer.register(resolved);
|
||||
} catch (final RuntimeException re) {
|
||||
log.warn("Failed to register tracer '" + resolved + "'", re);
|
||||
}
|
||||
} else {
|
||||
log.warn("Failed to resolve dd tracer");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,12 +24,14 @@ import static net.bytebuddy.matcher.ElementMatchers.nameContains;
|
|||
import static net.bytebuddy.matcher.ElementMatchers.nameMatches;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
|
||||
|
||||
import com.datadoghq.trace.DDTraceAnnotationsInfo;
|
||||
import com.datadoghq.trace.DDTraceInfo;
|
||||
import dd.trace.Instrumenter;
|
||||
import io.opentracing.Tracer;
|
||||
import io.opentracing.contrib.tracerresolver.TracerResolver;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.util.Collections;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
|
@ -48,14 +50,36 @@ public class TracingAgent {
|
|||
public static void premain(String agentArgs, final Instrumentation inst) throws Exception {
|
||||
log.debug("Using premain for loading {}", TracingAgent.class.getSimpleName());
|
||||
addByteBuddy(inst);
|
||||
AgentRulesManager.initialize();
|
||||
initializeGlobalTracer();
|
||||
}
|
||||
|
||||
public static void agentmain(final String agentArgs, final Instrumentation inst)
|
||||
throws Exception {
|
||||
log.debug("Using agentmain for loading {}", TracingAgent.class.getSimpleName());
|
||||
addByteBuddy(inst);
|
||||
AgentRulesManager.initialize();
|
||||
initializeGlobalTracer();
|
||||
}
|
||||
|
||||
private static synchronized void initializeGlobalTracer() {
|
||||
// version classes log important info
|
||||
// in static initializers
|
||||
DDJavaAgentInfo.VERSION.toString();
|
||||
DDTraceInfo.VERSION.toString();
|
||||
DDTraceAnnotationsInfo.VERSION.toString();
|
||||
|
||||
if (!GlobalTracer.isRegistered()) {
|
||||
// Try to obtain a tracer using the TracerResolver
|
||||
final Tracer resolved = TracerResolver.resolveTracer();
|
||||
if (resolved != null) {
|
||||
try {
|
||||
GlobalTracer.register(resolved);
|
||||
} catch (final RuntimeException re) {
|
||||
log.warn("Failed to register tracer '" + resolved + "'", re);
|
||||
}
|
||||
} else {
|
||||
log.warn("Failed to resolve dd tracer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void addByteBuddy(final Instrumentation inst) {
|
||||
|
@ -97,9 +121,6 @@ public class TracingAgent {
|
|||
@Slf4j
|
||||
static class Listener implements AgentBuilder.Listener {
|
||||
|
||||
private final Set<ClassLoader> initializedClassloaders =
|
||||
Collections.newSetFromMap(new WeakHashMap<ClassLoader, Boolean>());
|
||||
|
||||
@Override
|
||||
public void onError(
|
||||
final String typeName,
|
||||
|
@ -118,22 +139,6 @@ public class TracingAgent {
|
|||
final boolean loaded,
|
||||
final DynamicType dynamicType) {
|
||||
log.debug("Transformed {} -- {}", typeDescription, classLoader);
|
||||
|
||||
if (classLoader == null) {
|
||||
return;
|
||||
}
|
||||
synchronized (classLoader) {
|
||||
if (initializedClassloaders.contains(classLoader)) {
|
||||
return;
|
||||
}
|
||||
initializedClassloaders.add(classLoader);
|
||||
|
||||
try {
|
||||
InstrumentationRulesManager.registerClassLoad(classLoader);
|
||||
} catch (final Throwable e) {
|
||||
log.error("Failed ClassLoad Registration for target " + classLoader, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
package com.datadoghq.agent;
|
||||
|
||||
import com.datadoghq.trace.resolver.TracerConfig;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Configuration POJO for the agent */
|
||||
public class TracingAgentConfig extends TracerConfig {
|
||||
|
||||
private List<String> disabledInstrumentations = new ArrayList<>();
|
||||
|
||||
private String[] enableCustomAnnotationTracingOver = {};
|
||||
|
||||
public String[] getEnableCustomAnnotationTracingOver() {
|
||||
return enableCustomAnnotationTracingOver;
|
||||
}
|
||||
|
||||
public void setEnableCustomAnnotationTracingOver(
|
||||
final String[] enableCustomAnnotationTracingOver) {
|
||||
this.enableCustomAnnotationTracingOver = enableCustomAnnotationTracingOver;
|
||||
}
|
||||
|
||||
public List<String> getDisabledInstrumentations() {
|
||||
return disabledInstrumentations;
|
||||
}
|
||||
|
||||
public void setDisabledInstrumentations(final List<String> uninstallContributions) {
|
||||
this.disabledInstrumentations = uninstallContributions;
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package com.datadoghq.agent
|
||||
|
||||
import com.datadoghq.trace.resolver.FactoryUtils
|
||||
import com.fasterxml.jackson.core.type.TypeReference
|
||||
import spock.lang.Specification
|
||||
|
||||
class InstrumentationCheckerTest extends Specification {
|
||||
Map<String, List<Map<String, String>>> rules =
|
||||
FactoryUtils.loadConfigFromResource("supported-version-test", new TypeReference<Map<String, List<InstrumentationChecker.ArtifactSupport>>>() {
|
||||
})
|
||||
Map<String, String> frameworks = [
|
||||
"artifact-1": "1.2.3.1232",
|
||||
"artifact-2": "4.y.z",
|
||||
"artifact-3": "5.123-1"
|
||||
]
|
||||
|
||||
def checker = new InstrumentationChecker(rules, frameworks)
|
||||
|
||||
def "test rules"() {
|
||||
setup:
|
||||
def rules = checker.getUnsupportedRules(getClass().getClassLoader())
|
||||
|
||||
expect:
|
||||
rules.sort() == ["unsupportedRuleOne", "unsupportedRuleThree", "unsupportedRuleTwo"]
|
||||
}
|
||||
|
||||
static class DemoClass1 {
|
||||
void testMethod(String arg) {}
|
||||
}
|
||||
|
||||
static class DemoClass2 {}
|
||||
|
||||
static class DemoClass3 {}
|
||||
}
|
|
@ -9,6 +9,7 @@ import net.bytebuddy.dynamic.ClassFileLocator;
|
|||
@Slf4j
|
||||
public class DDAdvice extends AgentBuilder.Transformer.ForAdvice {
|
||||
private static ClassLoader AGENT_CLASSLOADER;
|
||||
private static ClassFileLocator AGENT_CLASS_LOCATOR;
|
||||
|
||||
static {
|
||||
try {
|
||||
|
@ -17,16 +18,29 @@ public class DDAdvice extends AgentBuilder.Transformer.ForAdvice {
|
|||
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();
|
||||
AGENT_CLASSLOADER = DDAdvice.class.getClassLoader();
|
||||
log.error(
|
||||
"Failed to locate agent classloader. Falling back to "
|
||||
+ AGENT_CLASSLOADER
|
||||
+ " -- "
|
||||
+ t.getMessage());
|
||||
}
|
||||
AGENT_CLASS_LOCATOR = ClassFileLocator.ForClassLoader.of(AGENT_CLASSLOADER);
|
||||
}
|
||||
|
||||
/** Return the classloader the datadog agent is running on. */
|
||||
public static ClassLoader getAgentClassLoader() {
|
||||
return AGENT_CLASSLOADER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create bytebuddy advice with default datadog settings.
|
||||
*
|
||||
* @return the bytebuddy advice
|
||||
*/
|
||||
public static AgentBuilder.Transformer.ForAdvice create() {
|
||||
return new DDAdvice()
|
||||
.with(
|
||||
new AgentBuilder.LocationStrategy.Simple(
|
||||
ClassFileLocator.ForClassLoader.of(AGENT_CLASSLOADER)))
|
||||
.with(new AgentBuilder.LocationStrategy.Simple(AGENT_CLASS_LOCATOR))
|
||||
.withExceptionHandler(ExceptionHandlers.defaultExceptionHandler());
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
package dd.trace;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.bytebuddy.agent.builder.AgentBuilder.Transformer;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.dynamic.ClassFileLocator;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import net.bytebuddy.dynamic.loading.ClassInjector;
|
||||
import net.bytebuddy.utility.JavaModule;
|
||||
|
||||
/** Injects instrumentation helper classes into the user's classloader. */
|
||||
@Slf4j
|
||||
public class HelperInjector implements Transformer {
|
||||
private final Set<String> helperClassNames;
|
||||
private Map<TypeDescription, byte[]> helperMap = null;
|
||||
private final Set<ClassLoader> injectedClassLoaders = new HashSet<ClassLoader>();
|
||||
|
||||
/**
|
||||
* Construct HelperInjector.
|
||||
*
|
||||
* @param helperClassNames binary names of the helper classes to inject. These class names must be
|
||||
* resolvable by the classloader returned by dd.trace.DDAdvice#getAgentClassLoader()
|
||||
*/
|
||||
public HelperInjector(String... helperClassNames) {
|
||||
this.helperClassNames = new HashSet<String>(Arrays.asList(helperClassNames));
|
||||
}
|
||||
|
||||
private synchronized Map<TypeDescription, byte[]> getHelperMap() throws IOException {
|
||||
if (helperMap == null) {
|
||||
helperMap = new HashMap<TypeDescription, byte[]>(helperClassNames.size());
|
||||
for (String helperName : helperClassNames) {
|
||||
final ClassFileLocator locator =
|
||||
ClassFileLocator.ForClassLoader.of(DDAdvice.getAgentClassLoader());
|
||||
final byte[] classBytes = locator.locate(helperName).resolve();
|
||||
final TypeDescription typeDesc = new TypeDescription.Latent(helperName, 0, null);
|
||||
helperMap.put(typeDesc, classBytes);
|
||||
}
|
||||
}
|
||||
return helperMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DynamicType.Builder<?> transform(
|
||||
DynamicType.Builder<?> builder,
|
||||
TypeDescription typeDescription,
|
||||
ClassLoader classLoader,
|
||||
JavaModule module) {
|
||||
if (helperClassNames.size() > 0 && classLoader != null) {
|
||||
synchronized (this) {
|
||||
if (!injectedClassLoaders.contains(classLoader)) {
|
||||
try {
|
||||
new ClassInjector.UsingReflection(classLoader).inject(getHelperMap());
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to inject helper classes into " + classLoader, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
injectedClassLoaders.add(classLoader);
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package dd.test
|
||||
|
||||
import static dd.test.TestUtils.createJarWithClasses
|
||||
|
||||
import dd.trace.DDAdvice
|
||||
import dd.trace.HelperInjector
|
||||
import java.lang.reflect.Method
|
||||
import net.bytebuddy.agent.ByteBuddyAgent
|
||||
import net.bytebuddy.agent.builder.AgentBuilder
|
||||
import spock.lang.Specification
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.*
|
||||
|
||||
class HelperInjectionTest extends Specification {
|
||||
def setupSpec() {
|
||||
AgentBuilder builder =
|
||||
new AgentBuilder.Default()
|
||||
.disableClassFormatChanges()
|
||||
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
|
||||
.ignore(nameStartsWith("dd.inst"))
|
||||
|
||||
builder = new TestInstrumentation().instrument(builder)
|
||||
|
||||
builder.installOn(ByteBuddyAgent.install())
|
||||
}
|
||||
|
||||
def "helpers injected to non-delegating classloader"() {
|
||||
setup:
|
||||
String helperClassName = TestInstrumentation.getName() + '$HelperClass'
|
||||
String instrumentationClassName = TestInstrumentation.getName() + '$ClassToInstrument'
|
||||
HelperInjector injector = new HelperInjector(TestInstrumentation.getName() + '$HelperClass')
|
||||
URLClassLoader emptyLoader = new URLClassLoader(new URL[0], (ClassLoader)null)
|
||||
injector.transform(null, null, emptyLoader, null)
|
||||
// injecting into emptyLoader should not load on agent's classloader
|
||||
assert !TestUtils.isClassLoaded(helperClassName, DDAdvice.getAgentClassLoader())
|
||||
assert TestUtils.isClassLoaded(helperClassName, emptyLoader)
|
||||
|
||||
URL[] classpath = [createJarWithClasses(instrumentationClassName)]
|
||||
URLClassLoader classloader = new URLClassLoader(classpath, (ClassLoader)null)
|
||||
|
||||
when:
|
||||
classloader.loadClass(helperClassName)
|
||||
then:
|
||||
thrown ClassNotFoundException
|
||||
|
||||
when:
|
||||
Class<?> instrumentedClass = classloader.loadClass(instrumentationClassName)
|
||||
Method instrumentedMethod = instrumentedClass.getMethod("isInstrumented")
|
||||
then:
|
||||
instrumentedMethod.invoke(null)
|
||||
|
||||
when:
|
||||
classloader.loadClass(helperClassName)
|
||||
then:
|
||||
noExceptionThrown()
|
||||
|
||||
cleanup:
|
||||
classloader?.close()
|
||||
emptyLoader?.close()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package dd.test;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.*;
|
||||
|
||||
import dd.trace.DDAdvice;
|
||||
import dd.trace.HelperInjector;
|
||||
import dd.trace.Instrumenter;
|
||||
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
|
||||
public class TestInstrumentation implements Instrumenter {
|
||||
@Override
|
||||
public AgentBuilder instrument(AgentBuilder agentBuilder) {
|
||||
return agentBuilder
|
||||
.type(named(TestInstrumentation.class.getName() + "$ClassToInstrument"))
|
||||
.transform(new HelperInjector(TestInstrumentation.class.getName() + "$HelperClass"))
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
isMethod().and(isPublic()).and(named("isInstrumented")),
|
||||
AdviceClass.class.getName()))
|
||||
.asDecorator();
|
||||
}
|
||||
|
||||
public static class ClassToInstrument {
|
||||
public static boolean isInstrumented() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AdviceClass {
|
||||
@Advice.OnMethodExit(suppress = Throwable.class)
|
||||
public static void enterAdvice(@Advice.Return(readOnly = false) Boolean returnValue) {
|
||||
returnValue = HelperClass.returnTrue();
|
||||
}
|
||||
}
|
||||
|
||||
public static class HelperClass {
|
||||
public static boolean returnTrue() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,10 @@ import dd.trace.Instrumenter
|
|||
import io.opentracing.ActiveSpan
|
||||
import io.opentracing.Tracer
|
||||
import io.opentracing.util.GlobalTracer
|
||||
import java.lang.reflect.Method
|
||||
import java.util.jar.JarEntry
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.jar.Manifest
|
||||
import net.bytebuddy.agent.ByteBuddyAgent
|
||||
import net.bytebuddy.agent.builder.AgentBuilder
|
||||
|
||||
|
@ -32,12 +36,6 @@ class TestUtils {
|
|||
}
|
||||
|
||||
static registerOrReplaceGlobalTracer(Tracer tracer) {
|
||||
try {
|
||||
Class.forName("com.datadoghq.agent.InstrumentationRulesManager")
|
||||
.getMethod("registerClassLoad")
|
||||
.invoke(null)
|
||||
} catch (ClassNotFoundException e) {
|
||||
}
|
||||
try {
|
||||
GlobalTracer.register(tracer)
|
||||
} catch (final Exception e) {
|
||||
|
@ -57,4 +55,66 @@ class TestUtils {
|
|||
rootSpan.deactivate()
|
||||
}
|
||||
}
|
||||
|
||||
private static Method findLoadedClassMethod = ClassLoader.getDeclaredMethod("findLoadedClass", String)
|
||||
|
||||
static boolean isClassLoaded(String className, ClassLoader classLoader) {
|
||||
try {
|
||||
findLoadedClassMethod.setAccessible(true)
|
||||
return null != findLoadedClassMethod.invoke(classLoader, className)
|
||||
} finally {
|
||||
findLoadedClassMethod.setAccessible(false)
|
||||
}
|
||||
}
|
||||
|
||||
/** com.foo.Bar -> com/foo/Bar.class */
|
||||
static String getResourceName(Class<?> clazz) {
|
||||
return clazz.getName().replace('.', '/') + ".class"
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a temporary jar on the filesystem with the bytes of the given classes.
|
||||
*
|
||||
* <p>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
|
||||
*/
|
||||
static URL createJarWithClasses(Class<?>... classes) throws IOException {
|
||||
final File tmpJar = File.createTempFile(UUID.randomUUID().toString() + "", ".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()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ include ':dd-trace-examples'
|
|||
include ':dd-trace-annotations'
|
||||
|
||||
// integrations:
|
||||
include ':dd-java-agent:integrations:helpers'
|
||||
include ':dd-java-agent:integrations:apache-httpclient-4.3'
|
||||
include ':dd-java-agent:integrations:aws-sdk'
|
||||
include ':dd-java-agent:integrations:datastax-cassandra-3.2'
|
||||
|
|
Loading…
Reference in New Issue