Fix spark instrumentation

and rebase off master.
This commit is contained in:
Tyler Benson 2018-04-26 14:53:09 +10:00
parent de350c7c4f
commit 3db2d654f1
14 changed files with 267 additions and 133 deletions

View File

@ -181,7 +181,9 @@ class VersionScanPlugin implements Plugin<Project> {
}
}
// println "Scanning ${includeVersionSet.size()} included and ${excludeVersionSet.size()} excluded versions. Included: ${includeVersionSet.collect { it.version }}}"
// println "Scanning ${includeVersionSet.size()} included and ${excludeVersionSet.size()} excluded versions."
// println "Included: ${includeVersionSet.collect { it.version }}}"
// println "Excluded: ${excludeVersionSet.collect { it.version }}}"
includeVersionSet.each { version ->
addScanTask("Include", new DefaultArtifact(version.groupId, version.artifactId, "jar", version.version), keyPresent, allInclude, project)

View File

@ -9,7 +9,7 @@ import spock.lang.Timeout
import java.lang.reflect.Field
@Timeout(1)
@Timeout(10)
class ShadowPackageRenamingTest extends Specification {
def "agent dependencies renamed"() {
setup:

View File

@ -1,12 +1,11 @@
apply plugin: 'version-scan'
versionScan {
group = "org.eclipse.jetty.server"
module = 'org.eclipse.jetty.server-Handler'
group = "org.eclipse.jetty"
module = 'jetty-server'
versions = "[8.0.0.v20110901,)"
verifyPresent = [
"javax.servlet.AsyncEvent" : null,
"javax.servlet.AsyncListener": null,
verifyMissing = [
"org.eclipse.jetty.server.AsyncContext",
]
}
@ -29,6 +28,5 @@ dependencies {
testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version: '8.0.0.v20110901'
testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '8.0.0.v20110901'
testCompile project(':dd-java-agent:instrumentation:okhttp-3') // used in the tests
testCompile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.6.0'
}

View File

@ -2,7 +2,12 @@ package datadog.trace.instrumentation.jetty8;
import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClasses;
import static io.opentracing.log.Fields.ERROR_OBJECT;
import static net.bytebuddy.matcher.ElementMatchers.*;
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.DDAdvice;
@ -45,8 +50,10 @@ public final class HandlerInstrumentation extends Instrumenter.Configurable {
public AgentBuilder apply(final AgentBuilder agentBuilder) {
return agentBuilder
.type(
not(isInterface()).and(hasSuperType(named("org.eclipse.jetty.server.Handler"))),
classLoaderHasClasses("javax.servlet.AsyncEvent", "javax.servlet.AsyncListener"))
not(isInterface())
.and(hasSuperType(named("org.eclipse.jetty.server.Handler")))
.and(not(named("org.eclipse.jetty.server.handler.HandlerWrapper"))),
not(classLoaderHasClasses("org.eclipse.jetty.server.AsyncContext")))
.transform(
new HelperInjector(
"io.opentracing.contrib.web.servlet.filter.HttpServletRequestExtractAdapter",
@ -60,10 +67,10 @@ public final class HandlerInstrumentation extends Instrumenter.Configurable {
DDAdvice.create()
.advice(
named("handle")
.and(takesArgument(0, named("String")))
.and(takesArgument(0, named("java.lang.String")))
.and(takesArgument(1, named("org.eclipse.jetty.server.Request")))
.and(takesArgument(2, named("javax.servlet.HttpServletRequest")))
.and(takesArgument(3, named("javax.servlet.HttpServletResponse")))
.and(takesArgument(2, named("javax.servlet.http.HttpServletRequest")))
.and(takesArgument(3, named("javax.servlet.http.HttpServletResponse")))
.and(isPublic()),
HandlerInstrumentationAdvice.class.getName()))
.asDecorator();
@ -73,7 +80,8 @@ public final class HandlerInstrumentation extends Instrumenter.Configurable {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static Scope startSpan(
@Advice.Argument(0) final String target, @Advice.Argument(2) final HttpServletRequest req) {
@Advice.This final Object source, @Advice.Argument(2) final HttpServletRequest req) {
if (GlobalTracer.get().activeSpan() != null) {
// Tracing might already be applied. If so ignore this.
return null;
@ -82,18 +90,19 @@ public final class HandlerInstrumentation extends Instrumenter.Configurable {
final SpanContext extractedContext =
GlobalTracer.get()
.extract(Format.Builtin.HTTP_HEADERS, new HttpServletRequestExtractAdapter(req));
final String resourceName = req.getMethod() + target;
final String resourceName = req.getMethod() + " " + source.getClass().getName();
final Scope scope =
GlobalTracer.get()
.buildSpan(SERVLET_OPERATION_NAME)
.asChildOf(extractedContext)
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)
.withTag(DDTags.SPAN_TYPE, DDSpanTypes.WEB_SERVLET)
.withTag("span.origin.type", HandlerInstrumentationAdvice.class.getName())
.withTag(DDTags.RESOURCE_NAME, resourceName)
.withTag("span.origin.type", source.getClass().getName())
.startActive(false);
ServletFilterSpanDecorator.STANDARD_TAGS.onRequest(req, scope.span());
Tags.COMPONENT.set(scope.span(), "jetty-handler");
scope.span().setTag(DDTags.RESOURCE_NAME, resourceName);
return scope;
}
@ -103,6 +112,7 @@ public final class HandlerInstrumentation extends Instrumenter.Configurable {
@Advice.Argument(3) final HttpServletResponse resp,
@Advice.Enter final Scope scope,
@Advice.Thrown final Throwable throwable) {
if (scope != null) {
final Span span = scope.span();
if (throwable != null) {

View File

@ -0,0 +1,127 @@
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.TestUtils
import datadog.trace.api.DDSpanTypes
import okhttp3.OkHttpClient
import org.eclipse.jetty.server.Handler
import org.eclipse.jetty.server.Request
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.AbstractHandler
import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
class JettyHandlerTest extends AgentTestRunner {
static {
System.setProperty("dd.integration.jetty.enabled", "true")
}
int port = TestUtils.randomOpenPort()
Server server = new Server(port)
OkHttpClient client = new OkHttpClient.Builder()
// Uncomment when debugging:
// .connectTimeout(1, TimeUnit.HOURS)
// .writeTimeout(1, TimeUnit.HOURS)
// .readTimeout(1, TimeUnit.HOURS)
.build()
def cleanup() {
server.stop()
}
@Override
void afterTest() {
}
def "call to jetty creates a trace"() {
setup:
Handler handler = new AbstractHandler() {
@Override
void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.setContentType("text/plain;charset=utf-8")
response.setStatus(HttpServletResponse.SC_OK)
baseRequest.setHandled(true)
response.getWriter().println("Hello World")
}
}
server.setHandler(handler)
server.start()
def request = new okhttp3.Request.Builder()
.url("http://localhost:$port/")
.get()
.build()
def response = client.newCall(request).execute()
expect:
response.body().string().trim() == "Hello World"
TEST_WRITER.waitForTraces(1)
TEST_WRITER.size() == 1
def trace = TEST_WRITER.firstTrace()
trace.size() == 1
def context = trace[0].context()
context.serviceName == "unnamed-java-app"
context.operationName == "jetty.request"
context.resourceName == "GET ${handler.class.name}"
context.spanType == DDSpanTypes.WEB_SERVLET
!context.getErrorFlag()
context.parentId == 0
def tags = context.tags
tags["http.url"] == "http://localhost:$port/"
tags["http.method"] == "GET"
tags["span.kind"] == "server"
tags["span.type"] == "web"
tags["component"] == "jetty-handler"
tags["http.status_code"] == 200
tags["thread.name"] != null
tags["thread.id"] != null
tags["span.origin.type"] == handler.class.name
tags.size() == 9
}
def "call to jetty with error creates a trace"() {
setup:
Handler handler = new AbstractHandler() {
@Override
void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
throw new RuntimeException()
}
}
server.setHandler(handler)
server.start()
def request = new okhttp3.Request.Builder()
.url("http://localhost:$port/")
.get()
.build()
def response = client.newCall(request).execute()
expect:
response.body().string().trim() == ""
TEST_WRITER.waitForTraces(1)
TEST_WRITER.size() == 1
def trace = TEST_WRITER.firstTrace()
trace.size() == 1
def context = trace[0].context()
context.serviceName == "unnamed-java-app"
context.operationName == "jetty.request"
context.resourceName == "GET ${handler.class.name}"
context.spanType == DDSpanTypes.WEB_SERVLET
context.getErrorFlag()
context.parentId == 0
def tags = context.tags
tags["http.url"] == "http://localhost:$port/"
tags["http.method"] == "GET"
tags["span.kind"] == "server"
tags["span.type"] == "web"
tags["component"] == "jetty-handler"
tags["http.status_code"] == 500
tags["thread.name"] != null
tags["thread.id"] != null
tags["span.origin.type"] == handler.class.name
tags["error"] == true
tags["error.type"] == RuntimeException.name
tags["error.stack"] != null
tags.size() == 12
}
}

View File

@ -27,6 +27,7 @@ dependencies {
compile deps.autoservice
testCompile project(':dd-java-agent:testing')
testCompile project(':dd-java-agent:instrumentation:jetty-8') // See if there's any conflicts.
testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version: '8.2.0.v20160908'
testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '8.2.0.v20160908'
testCompile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '8.0.41'

View File

@ -16,7 +16,7 @@ import spock.lang.Unroll
import java.lang.reflect.Field
@Timeout(5)
@Timeout(15)
class TomcatServletTest extends AgentTestRunner {
static final int PORT = TestUtils.randomOpenPort()

View File

@ -1,59 +0,0 @@
package datadog.trace.instrumentation.sparkjava;
import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClasses;
import static net.bytebuddy.matcher.ElementMatchers.*;
import datadog.trace.agent.tooling.DDAdvice;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.api.DDTags;
import io.opentracing.Scope;
import io.opentracing.Span;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import spark.route.HttpMethod;
import spark.routematch.RouteMatch;
public class RoutesInstrumentation extends Instrumenter.Configurable {
public RoutesInstrumentation() {
super("sparkjava", "sparkjava-2.3");
}
@Override
public boolean defaultEnabled() {
return false;
}
@Override
public AgentBuilder apply(final AgentBuilder agentBuilder) {
return agentBuilder
.type(
is(named("spark.route.Routes")),
classLoaderHasClasses("spark.embeddedserver.jetty.EmbeddedJettyServer"))
.transform(
DDAdvice.create()
.advice(
named("find")
.and(takesArgument(0, named("spark.route.HttpMethod")))
.and(takesArgument(1, named("String")))
.and(takesArgument(2, named("String")))
.and(isPublic()),
RoutesInstrumentationAdvice.class.getName()))
.asDecorator();
}
public static class RoutesInstrumentationAdvice {
@Advice.OnMethodExit()
public static void routeMatchEnricher(
@Advice.Argument(0) final HttpMethod method,
@Advice.Enter final Scope scope,
@Advice.Return final RouteMatch routeMatch) {
if (scope != null && routeMatch != null) {
final Span span = scope.span();
final String resourceName = method.name() + " " + routeMatch.getMatchUri();
span.setTag(DDTags.RESOURCE_NAME, resourceName);
}
}
}
}

View File

@ -1,43 +1,34 @@
apply plugin: 'version-scan'
versionScan {
group = "spark"
module = 'spark'
versions = "[2.3,)"
group = "com.sparkjava"
module = 'spark-core'
versions = "[2.4,)"
verifyPresent = [
"spark.embeddedserver.jetty.EmbeddedJettyServer" : null
"spark.route.Routes": null
]
}
apply from: "${rootDir}/gradle/java.gradle"
if (!JavaVersion.current().isJava8Compatible()) {
sourceSets {
test {
groovy {
// Sparkjava is not compatible with < Java 8
exclude '**/SparkJavaBasedTest.groovy'
}
java {
exclude '**/TestSparkJavaApplication.java'
}
}
}
testJava8Minimum += '**/SparkJavaBasedTest.class'
compileTestJava {
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
}
dependencies {
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
compile project(':dd-java-agent:agent-tooling')
compileOnly group: 'com.sparkjava', name: 'spark-core', version: '2.4'
compile project(':dd-java-agent:agent-tooling')
compile deps.bytebuddy
compile deps.opentracing
compile deps.autoservice
compile group: 'com.sparkjava', name: 'spark-core', version: '2.3'
testCompile project(':dd-java-agent:instrumentation:jetty-8')
testCompile project(':dd-java-agent:testing')
testCompile group: 'com.sparkjava', name: 'spark-core', version: '2.4'
testCompile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.6.0'
}

View File

@ -0,0 +1,59 @@
package datadog.trace.instrumentation.sparkjava;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.DDAdvice;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.api.DDTags;
import io.opentracing.Scope;
import io.opentracing.util.GlobalTracer;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import spark.route.HttpMethod;
import spark.routematch.RouteMatch;
@AutoService(Instrumenter.class)
public class RoutesInstrumentation extends Instrumenter.Configurable {
public RoutesInstrumentation() {
super("sparkjava", "sparkjava-2.4");
}
@Override
public boolean defaultEnabled() {
return false;
}
@Override
public AgentBuilder apply(final AgentBuilder agentBuilder) {
return agentBuilder
.type(named("spark.route.Routes"))
.transform(
DDAdvice.create()
.advice(
named("find")
.and(takesArgument(0, named("spark.route.HttpMethod")))
.and(returns(named("spark.routematch.RouteMatch")))
.and(isPublic()),
RoutesAdvice.class.getName()))
.asDecorator();
}
public static class RoutesAdvice {
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void routeMatchEnricher(
@Advice.Argument(0) final HttpMethod method, @Advice.Return final RouteMatch routeMatch) {
final Scope scope = GlobalTracer.get().scopeManager().active();
if (scope != null && routeMatch != null) {
final String resourceName = method.name().toUpperCase() + " " + routeMatch.getMatchUri();
scope.span().setTag(DDTags.RESOURCE_NAME, resourceName);
}
}
}
}

View File

@ -1,11 +1,12 @@
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.TestUtils
import datadog.trace.api.DDSpanTypes
import okhttp3.OkHttpClient
import okhttp3.Request
import spark.Spark
import spark.embeddedserver.jetty.JettyHandler
import spock.lang.Timeout
@Timeout(20)
class SparkJavaBasedTest extends AgentTestRunner {
@ -13,45 +14,46 @@ class SparkJavaBasedTest extends AgentTestRunner {
System.setProperty("dd.integration.jetty.enabled", "true")
System.setProperty("dd.integration.sparkjava.enabled", "true")
}
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()
def setupSpec() {
TestSparkJavaApplication.initSpark()
TestSparkJavaApplication.initSpark(PORT)
}
def cleanupSpec() {
Spark.stop()
}
def setup() {
TEST_WRITER.start()
}
private int port = 4567
OkHttpClient client = new OkHttpClient.Builder().build()
def "valid response"() {
setup:
def request = new Request.Builder()
.url("http://localhost:$port/")
.url("http://localhost:$PORT/")
.get()
.build()
def response = client.newCall(request).execute()
expect:
port != 0
PORT != 0
response.body().string() == "Hello World"
}
def "valid response with registered trace"() {
setup:
def request = new Request.Builder()
.url("http://localhost:$port/")
.url("http://localhost:$PORT/")
.get()
.build()
def response = client.newCall(request).execute()
expect:
port != 0
PORT != 0
response.body().string() == "Hello World"
and:
@ -63,7 +65,7 @@ class SparkJavaBasedTest extends AgentTestRunner {
def "generates spans"() {
setup:
def request = new Request.Builder()
.url("http://localhost:$port/param/asdf1234")
.url("http://localhost:$PORT/param/asdf1234")
.get()
.build()
def response = client.newCall(request).execute()
@ -75,22 +77,24 @@ class SparkJavaBasedTest extends AgentTestRunner {
def trace = TEST_WRITER.firstTrace()
trace.size() == 1
def spanContext = trace[0].context()
spanContext.operationName == "jetty.request"
spanContext.resourceName == "GET /param/:param/"
spanContext.spanType == DDSpanTypes.WEB_SERVLET
!spanContext.getErrorFlag()
spanContext.parentId == 0
spanContext.tags["http.url"] == "http://localhost:$port/param/asdf1234/"
spanContext.tags["http.method"] == "GET"
spanContext.tags["span.kind"] == "server"
spanContext.tags["span.type"] == "web"
spanContext.tags["component"] == "java-web-servlet"
spanContext.tags["http.status_code"] == 200
spanContext.tags["thread.name"] != null
spanContext.tags["thread.id"] != null
spanContext.tags.size() == 8
def context = trace[0].context()
context.serviceName == "unnamed-java-app"
context.operationName == "jetty.request"
context.resourceName == "GET /param/:param"
context.spanType == DDSpanTypes.WEB_SERVLET
!context.getErrorFlag()
context.parentId == 0
def tags = context.tags
tags["http.url"] == "http://localhost:$PORT/param/asdf1234"
tags["http.method"] == "GET"
tags["span.kind"] == "server"
tags["span.type"] == "web"
tags["component"] == "jetty-handler"
tags["http.status_code"] == 200
tags["thread.name"] != null
tags["thread.id"] != null
tags["span.origin.type"] == JettyHandler.name
tags.size() == 9
}
}

View File

@ -2,8 +2,8 @@ import spark.Spark;
public class TestSparkJavaApplication {
public static void initSpark() {
Spark.port(4567);
public static void initSpark(final int port) {
Spark.port(port);
Spark.get("/", (req, res) -> "Hello World");
Spark.get("/param/:param", (req, res) -> "Hello " + req.params("param"));
@ -13,6 +13,7 @@ public class TestSparkJavaApplication {
(req, res) -> {
throw new RuntimeException(req.params("param"));
});
Spark.awaitInitialization();
}
}

View File

@ -15,7 +15,7 @@ import spock.lang.Unroll
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
@Timeout(1)
@Timeout(10)
class ScopeManagerTest extends Specification {
def writer = new ListWriter()
def tracer = new DDTracer(writer)

View File

@ -35,7 +35,7 @@ include ':dd-java-agent:instrumentation:play-2.4:play-2.6-testing'
include ':dd-java-agent:instrumentation:ratpack-1.4'
include ':dd-java-agent:instrumentation:servlet-2'
include ':dd-java-agent:instrumentation:servlet-3'
include ':dd-java-agent:instrumentation:sparkjava-2.3'
include ':dd-java-agent:instrumentation:sparkjava-2.4'
include ':dd-java-agent:instrumentation:spring-web'
include ':dd-java-agent:instrumentation:trace-annotation'