Fix spark instrumentation
and rebase off master.
MODIFY
buildSrc/src/main/groovy/VersionScanPlugin.groovy
buildSrc/src/main/groovy/VersionScanPlugin.groovy
@@ -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)

MODIFY
dd-java-agent-ittests/src/test/groovy/datadog/trace/agent/integration/classloading/ShadowPackageRenamingTest.groovy
dd-java-agent-ittests/src/test/groovy/datadog/trace/agent/integration/classloading/ShadowPackageRenamingTest.groovy
@@ -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:

MODIFY
dd-java-agent/instrumentation/jetty-8/jetty-8.gradle
dd-java-agent/instrumentation/jetty-8/jetty-8.gradle
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'
}

MODIFY
dd-java-agent/instrumentation/jetty-8/src/main/java/datadog/trace/instrumentation/jetty8/HandlerInstrumentation.java
dd-java-agent/instrumentation/jetty-8/src/main/java/datadog/trace/instrumentation/jetty8/HandlerInstrumentation.java
@@ -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) {

ADD
dd-java-agent/instrumentation/jetty-8/src/test/groovy/JettyHandlerTest.groovy
+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
+ }
+}

MODIFY
dd-java-agent/instrumentation/servlet-3/servlet-3.gradle
dd-java-agent/instrumentation/servlet-3/servlet-3.gradle
@@ -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'

MODIFY
dd-java-agent/instrumentation/servlet-3/src/test/groovy/TomcatServletTest.groovy
dd-java-agent/instrumentation/servlet-3/src/test/groovy/TomcatServletTest.groovy
@@ -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()

DELETE
dd-java-agent/instrumentation/sparkjava-2.3/src/main/java/datadog/trace/instrumentation/sparkjava/RoutesInstrumentation.java
-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);
- }
- }
- }
-}

RENAME
dd-java-agent/instrumentation/sparkjava-2.3/sparkjava-2.3.gradle
dd-java-agent/instrumentation/sparkjava-2.4/sparkjava-2.4.gradle
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'
-dependencies {
+compileTestJava {
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
- compile project(':dd-java-agent:agent-tooling')
+}
+
+dependencies {
+ 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'
-
}

ADD
dd-java-agent/instrumentation/sparkjava-2.4/src/main/java/datadog/trace/instrumentation/sparkjava/RoutesInstrumentation.java
+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);
+ }
+ }
+ }
+}

RENAME
dd-java-agent/instrumentation/sparkjava-2.3/src/test/groovy/SparkJavaBasedTest.groovy
dd-java-agent/instrumentation/sparkjava-2.4/src/test/groovy/SparkJavaBasedTest.groovy
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
}
}

RENAME
dd-java-agent/instrumentation/sparkjava-2.3/src/test/java/TestSparkJavaApplication.java
dd-java-agent/instrumentation/sparkjava-2.4/src/test/java/TestSparkJavaApplication.java
@@ -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();
}
}

MODIFY
dd-trace-ot/src/test/groovy/datadog/opentracing/scopemanager/ScopeManagerTest.groovy
dd-trace-ot/src/test/groovy/datadog/opentracing/scopemanager/ScopeManagerTest.groovy
@@ -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)

MODIFY
settings.gradle
settings.gradle
@@ -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'

Fix spark instrumentation

and rebase off master.