Merge pull request #741 from DataDog/mar-kolya/tomcat-classloadin-instrumentation

Add instrumentation for Tomcat webapp classloading
This commit is contained in:
Nikolay Martynov 2019-02-28 13:48:40 -05:00 committed by GitHub
commit 3029d3d85c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 133 additions and 0 deletions

View File

@ -0,0 +1,68 @@
package datadog.trace.instrumentation.tomcat;
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Constants;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.api.GlobalTracer;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
/**
* Instrument Tomcat's web app classloader so it loads Datadog's bootstrap classes from parent
* classloader. Without this change web apps get their oen versions of Datadog's classes leading to
* there being multiple {@link GlobalTracer}s in existance, some of them not configured properly.
* This is really the same idea we have for OSGi and JBoss.
*/
@AutoService(Instrumenter.class)
public final class TomcatClassloadingInstrumentation extends Instrumenter.Default {
public TomcatClassloadingInstrumentation() {
super("tomcat-classloading");
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return safeHasSuperType(named("org.apache.catalina.loader.WebappClassLoaderBase"));
}
@Override
public String[] helperClassNames() {
return new String[] {Constants.class.getName()};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
isMethod()
.and(named("filter"))
.and(takesArgument(0, String.class))
// Older versions have 1 argument method, newer versions have two arguments
.and(takesArguments(2).or(takesArguments(1))),
WebappClassLoaderAdvice.class.getName());
}
public static class WebappClassLoaderAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void methodExit(
@Advice.Argument(0) final String name, @Advice.Return(readOnly = false) boolean result) {
if (result) {
return;
}
for (final String prefix : Constants.BOOTSTRAP_PACKAGE_PREFIXES) {
if (name.startsWith(prefix)) {
result = true;
break;
}
}
}
}
}

View File

@ -0,0 +1,30 @@
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.api.GlobalTracer
import org.apache.catalina.WebResource
import org.apache.catalina.WebResourceRoot
import org.apache.catalina.loader.ParallelWebappClassLoader
class TomcatClassloadingTest extends AgentTestRunner {
WebResourceRoot resources = Mock(WebResourceRoot) {
getResource(_) >> Mock(WebResource)
listResources(_) >> []
// Looks like 9.x.x needs this one:
getResources(_) >> []
}
ParallelWebappClassLoader classloader = new ParallelWebappClassLoader(null)
def "tomcat class loading delegates to parent for Datadog classes"() {
setup:
classloader.setResources(resources)
classloader.init()
classloader.start()
when:
// If instrumentation didn't work this would blow up with NPE due to incomplete resources mocking
def clazz = classloader.loadClass("datadog.trace.api.GlobalTracer")
then:
clazz == GlobalTracer
}
}

View File

@ -0,0 +1,34 @@
muzzle {
pass {
group = "org.apache.tomcat"
module = 'tomcat-catalina'
versions = "[3.0.14,)"
assertInverse = true
}
}
apply from: "${rootDir}/gradle/java.gradle"
apply plugin: 'org.unbroken-dome.test-sets'
testSets {
latestDepTest {
dirName = 'test'
}
}
dependencies {
compile project(':dd-java-agent:agent-tooling')
compile deps.bytebuddy
annotationProcessor deps.autoservice
implementation deps.autoservice
testCompile project(':dd-java-agent:testing')
//This seems to be the earliest version that has org.apache.catalina.loader.WebappClassLoaderBase
//Older versions would require slightly different instrumentation.
testCompile group: 'org.apache.tomcat', name: 'tomcat-catalina', version: '8.0.14'
latestDepTestCompile group: 'org.apache.tomcat', name: 'tomcat-catalina', version: '+'
}

View File

@ -77,6 +77,7 @@ include ':dd-java-agent:instrumentation:sparkjava-2.3'
include ':dd-java-agent:instrumentation:spring-web'
include ':dd-java-agent:instrumentation:spring-webflux'
include ':dd-java-agent:instrumentation:spymemcached-2.12'
include ':dd-java-agent:instrumentation:tomcat-classloading'
include ':dd-java-agent:instrumentation:trace-annotation'
include ':dd-java-agent:instrumentation:vertx'