JSP instrumentation

WIP: more unit tests to come
This commit is contained in:
Gary Huang 2018-06-13 16:44:48 -04:00
parent 7405b7711b
commit b1d06299cc
5 changed files with 264 additions and 0 deletions

View File

@ -0,0 +1,46 @@
apply plugin: 'version-scan'
// TODO figure out the earliest version that can be supported, and the latest
versionScan {
group = "org.apache.tomcat.embed"
module = 'tomcat-embed-jasper'
versions = "[8.0.41,)"
verifyPresent = [
"org.apache.jasper.servlet.JspServlet": null,
]
}
apply from: "${rootDir}/gradle/java.gradle"
apply plugin: 'org.unbroken-dome.test-sets'
testSets {
latestDepTest {
dirName = 'test'
}
}
dependencies {
compileOnly group: 'org.apache.tomcat.embed', name: 'tomcat-embed-jasper', version: '8.0.41'
compile project(':dd-trace-ot')
compile project(':dd-java-agent:agent-tooling')
compile deps.bytebuddy
compile deps.opentracing
annotationProcessor deps.autoservice
implementation deps.autoservice
testCompile project(':dd-java-agent:testing')
testCompile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '8.0.41'
testCompile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-jasper', version: '8.0.41'
testCompile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.10.0'
}
configurations.latestDepTestCompile {
resolutionStrategy {
force group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '+'
force group: 'org.apache.tomcat.embed', name: 'tomcat-embed-jasper', version: '+'
}
}

View File

@ -0,0 +1,104 @@
package datadog.trace.instrumentation.jsp;
import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClasses;
import static io.opentracing.log.Fields.ERROR_OBJECT;
import static net.bytebuddy.matcher.ElementMatchers.*;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.DDAdvice;
import datadog.trace.agent.tooling.DDTransformers;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.api.DDSpanTypes;
import datadog.trace.api.DDTags;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.tag.Tags;
import io.opentracing.util.GlobalTracer;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import org.apache.jasper.JspCompilationContext;
import org.apache.jasper.servlet.JspServletWrapper;
@AutoService(Instrumenter.class)
public final class JasperJSPInstrumentation extends Instrumenter.Configurable {
public JasperJSPInstrumentation() {
super("jsp", "tomcat-jsp");
}
@Override
public AgentBuilder apply(final AgentBuilder agentBuilder) {
return agentBuilder
.type(
named("org.apache.jasper.servlet.JspServletWrapper"),
classLoaderHasClasses("org.apache.jasper.servlet.JspServlet"))
.transform(DDTransformers.defaultTransformers())
.transform(
DDAdvice.create()
.advice(
named("service")
.and(takesArgument(0, named("javax.servlet.http.HttpServletRequest")))
.and(takesArgument(1, named("javax.servlet.http.HttpServletResponse")))
.and(isPublic()),
JasperJSPAdvice.class.getName()))
.asDecorator();
}
public static class JasperJSPAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static Scope startSpan(
@Advice.Argument(0) final HttpServletRequest req,
@Advice.Argument(2) final boolean preCompile) {
final Scope scope =
GlobalTracer.get()
.buildSpan("jsp.service")
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)
.withTag(DDTags.SPAN_TYPE, DDSpanTypes.WEB_SERVLET)
.withTag("servlet.context", req.getContextPath())
.startActive(true);
final Span span = scope.span();
span.setTag("jsp.precompile", preCompile);
Tags.COMPONENT.set(span, "tomcat-jsp-servlet");
Tags.HTTP_METHOD.set(span, req.getMethod());
Tags.HTTP_URL.set(span, req.getRequestURL().toString());
return scope;
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Argument(1) final HttpServletResponse resp,
@Advice.This JspServletWrapper jspServletWrapper,
@Advice.Enter final Scope scope,
@Advice.Thrown final Throwable throwable) {
final Span span = scope.span();
JspCompilationContext jspCompilationContext = jspServletWrapper.getJspEngineContext();
if (jspCompilationContext != null) {
span.setTag("jsp.compiler", jspCompilationContext.getCompiler().getClass().getName());
span.setTag("jsp.outputDir", jspCompilationContext.getOutputDir());
span.setTag("jsp.classFQCN", jspCompilationContext.getFQCN());
if (throwable != null) {
span.setTag("jsp.classpath", jspCompilationContext.getClassPath());
}
}
if (throwable != null) {
if (resp.getStatus() == HttpServletResponse.SC_OK) {
// exception is thrown in filter chain, but status code is incorrect
Tags.HTTP_STATUS.set(span, 500);
}
Tags.ERROR.set(span, Boolean.TRUE);
span.log(Collections.singletonMap(ERROR_OBJECT, throwable));
} else {
Tags.HTTP_STATUS.set(span, resp.getStatus());
}
scope.close();
}
}
}

View File

@ -0,0 +1,101 @@
import com.google.common.io.Files
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.TestUtils
import datadog.trace.api.DDSpanTypes
import io.netty.handler.codec.http.HttpResponseStatus
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.apache.catalina.Context
import org.apache.catalina.startup.Tomcat
import org.apache.tomcat.JarScanFilter
import org.apache.tomcat.JarScanType
import spock.lang.Shared
import static datadog.trace.agent.test.ListWriterAssert.assertTraces
class JSPInstrumentationTest extends AgentTestRunner {
static final int PORT = TestUtils.randomOpenPort()
OkHttpClient client = new OkHttpClient.Builder()
// Uncomment when debugging:
// .connectTimeout(1, TimeUnit.HOURS)
// .writeTimeout(1, TimeUnit.HOURS)
// .readTimeout(1, TimeUnit.HOURS)
.build()
static Tomcat tomcatServer
static Context appContext
static final String JSP_WEBAPP_CONTEXT = "/jsptest-context"
@Shared
static File baseDir
static String expectedJspClassFilesDir = "/work/Tomcat/localhost$JSP_WEBAPP_CONTEXT/org/apache/jsp/"
def setupSpec() {
tomcatServer = new Tomcat()
tomcatServer.setPort(PORT)
baseDir = Files.createTempDir()
baseDir.deleteOnExit()
expectedJspClassFilesDir = baseDir.getCanonicalFile().getAbsolutePath() + expectedJspClassFilesDir
tomcatServer.setBaseDir(baseDir.getAbsolutePath())
appContext = tomcatServer.addWebapp(JSP_WEBAPP_CONTEXT,
JSPInstrumentationTest.getResource("/webapps/jsptest").getPath())
// Speed up startup by disabling jar scanning:
appContext.getJarScanner().setJarScanFilter(new JarScanFilter() {
@Override
boolean check(JarScanType jarScanType, String jarName) {
return false
}
})
tomcatServer.start()
System.out.println(
"Tomcat server: http://" + tomcatServer.getHost().getName() + ":" + PORT + "/")
}
def cleanupSpec() {
tomcatServer.stop()
tomcatServer.destroy()
}
def "basic looping jsp"() {
setup:
String url = "http://localhost:$PORT$JSP_WEBAPP_CONTEXT/loop.jsp"
def req = new Request.Builder().url(new URL(url)).get().build()
when:
Response res = client.newCall(req).execute()
then:
assertTraces(TEST_WRITER, 1) {
trace(0, 1) {
span(0) {
serviceName "jsptest-context"
operationName "jsp.service"
resourceName "GET /jsptest-context/loop.jsp"
spanType DDSpanTypes.WEB_SERVLET
errored false
tags {
"http.url" url
"http.method" "GET"
"span.kind" "server"
"component" "tomcat-jsp-servlet"
"span.type" DDSpanTypes.WEB_SERVLET
"servlet.context" JSP_WEBAPP_CONTEXT
"http.status_code" 200
"jsp.classFQCN" "org.apache.jsp.loop_jsp"
"jsp.compiler" "org.apache.jasper.compiler.JDTCompiler"
"jsp.outputDir" expectedJspClassFilesDir
"jsp.precompile" false
defaultTags()
}
}
}
}
res.code() == HttpResponseStatus.OK.code()
}
}

View File

@ -0,0 +1,12 @@
<html>
<head><title>BASIC JSP</title></head>
<body>
<%
for (int i = 0; i < 3; ++i) {
%>
<h2>number:<%= i %></h2><p></p>
<%
}
%>
</body>
</html>

View File

@ -32,6 +32,7 @@ include ':dd-java-agent:instrumentation:jedis-1.4'
include ':dd-java-agent:instrumentation:jetty-8'
include ':dd-java-agent:instrumentation:jms-1'
include ':dd-java-agent:instrumentation:jms-2'
include ':dd-java-agent:instrumentation:jsp'
include ':dd-java-agent:instrumentation:kafka-clients-0.11'
include ':dd-java-agent:instrumentation:kafka-streams-0.11'
include ':dd-java-agent:instrumentation:lettuce-5'