Skip to content

Commit

Permalink
Merge pull request #741 from DataDog/mar-kolya/tomcat-classloadin-ins…
Browse files Browse the repository at this point in the history
…trumentation

Add instrumentation for Tomcat webapp classloading
  • Loading branch information
mar-kolya authored Feb 28, 2019
2 parents 6e5c725 + aa031cc commit 3029d3d
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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: '+'
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down

0 comments on commit 3029d3d

Please sign in to comment.