Merge pull request #174 from DataDog/ark/instrumentation_modules

Move Helper Classes into their specific instrumentation modules
This commit is contained in:
Andrew Kent 2017-12-19 17:24:19 -08:00 committed by GitHub
commit 5acd650efe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 413 additions and 736 deletions

View File

@ -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() {}

View File

@ -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();

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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(

View File

@ -1,4 +1,4 @@
package com.datadoghq.agent.integration;
package dd.inst.apachehttpclient;
import io.opentracing.ActiveSpan;
import io.opentracing.Tracer;

View File

@ -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'
}

View File

@ -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(

View File

@ -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

View File

@ -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(

View File

@ -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'))
}
}

View File

@ -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'

View File

@ -1,4 +1,4 @@
package com.datadoghq.agent.integration;
package dd.inst.jms.util;
import javax.jms.Destination;
import javax.jms.Message;

View File

@ -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();

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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'

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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'
}

View File

@ -1,4 +1,4 @@
package com.datadoghq.agent.integration;
package dd.inst.mongo;
import com.datadoghq.trace.DDTags;
import com.mongodb.event.CommandFailedEvent;

View File

@ -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(

View File

@ -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;

View File

@ -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
}
}

View File

@ -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(

View File

@ -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
}

View File

@ -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();
}

View File

@ -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')

View File

@ -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(

View File

@ -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')

View File

@ -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(

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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");
}
}
}
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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 {}
}

View File

@ -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());
}

View File

@ -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;
}
}

View File

@ -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()
}
}

View File

@ -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;
}
}
}

View File

@ -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()
}
}
}
}

View File

@ -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'