From 30721d00dc7453380838b1c4ac2fa7ffe6d9f8ee Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Wed, 20 Sep 2017 16:01:20 -0700 Subject: [PATCH 1/4] Replace tomcat/jetty specific integ with generic servlet integ. This should also work with things like Weblogic and Websphere with the main requirement being Servlet 2.3 or above compatibility. --- .../servlet/JettyServletTest.groovy | 103 +++++++++++++++ .../integration/servlet/TestServlet.groovy | 32 +++++ .../servlet/TomcatServletTest.groovy | 125 ++++++++++++++++++ .../agent/TraceAnnotationsManagerTest.java | 2 +- .../JettyServletInstrumentationTest.java | 40 ------ .../TomcatServletInstrumentationTest.java | 49 ------- .../integrations/helpers/helpers.gradle | 6 +- .../agent/integration/JettyServletHelper.java | 41 ------ .../agent/integration/Servlet2Helper.java | 86 ++++++++++++ .../agent/integration/Servlet3Helper.java | 95 +++++++++++++ .../integration/TomcatServletHelper.java | 40 ------ dd-java-agent/integrations/jetty/jetty.gradle | 13 -- .../integrations/servlet-2/servlet-2.gradle | 11 ++ .../integrations/servlet-3/servlet-3.gradle | 12 ++ .../tomcat-embedded/tomcat-embedded.gradle | 12 -- .../integrations/tomcat/tomcat.gradle | 12 -- .../dd-trace-supported-framework.yaml | 50 ++++--- .../src/main/resources/initializer-rules.btm | 22 +-- .../src/main/resources/integration-rules.btm | 79 ++++++++--- settings.gradle | 5 +- 20 files changed, 569 insertions(+), 266 deletions(-) create mode 100644 dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/JettyServletTest.groovy create mode 100644 dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/TestServlet.groovy create mode 100644 dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/TomcatServletTest.groovy delete mode 100644 dd-java-agent-ittests/src/test/java/com/datadoghq/agent/integration/JettyServletInstrumentationTest.java delete mode 100644 dd-java-agent-ittests/src/test/java/com/datadoghq/agent/integration/TomcatServletInstrumentationTest.java delete mode 100644 dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/JettyServletHelper.java create mode 100644 dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet2Helper.java create mode 100644 dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet3Helper.java delete mode 100644 dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/TomcatServletHelper.java delete mode 100644 dd-java-agent/integrations/jetty/jetty.gradle create mode 100644 dd-java-agent/integrations/servlet-2/servlet-2.gradle create mode 100644 dd-java-agent/integrations/servlet-3/servlet-3.gradle delete mode 100644 dd-java-agent/integrations/tomcat-embedded/tomcat-embedded.gradle delete mode 100644 dd-java-agent/integrations/tomcat/tomcat.gradle diff --git a/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/JettyServletTest.groovy b/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/JettyServletTest.groovy new file mode 100644 index 0000000000..63bfb42bbf --- /dev/null +++ b/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/JettyServletTest.groovy @@ -0,0 +1,103 @@ +package com.datadoghq.agent.integration.servlet + +import com.datadoghq.trace.DDTags +import com.datadoghq.trace.DDTracer +import com.datadoghq.trace.writer.ListWriter +import io.opentracing.tag.Tags +import io.opentracing.util.GlobalTracer +import okhttp3.OkHttpClient +import okhttp3.Request +import org.eclipse.jetty.server.Server +import org.eclipse.jetty.servlet.ServletContextHandler +import spock.lang.Specification +import spock.lang.Unroll + +import java.lang.reflect.Field + +class JettyServletTest extends Specification { + + static final int PORT = randomOpenPort() + OkHttpClient client = new OkHttpClient.Builder() + // Uncomment when debugging: +// .connectTimeout(1, TimeUnit.HOURS) +// .writeTimeout(1, TimeUnit.HOURS) +// .readTimeout(1, TimeUnit.HOURS) + .build() + + private Server jettyServer + private ServletContextHandler servletContext + + ListWriter writer = new ListWriter() + DDTracer tracer = new DDTracer(writer) + + def setup() { + jettyServer = new Server(PORT) + servletContext = new ServletContextHandler() + + servletContext.addServlet(TestServlet.Sync, "/sync") + servletContext.addServlet(TestServlet.Async, "/async") + + jettyServer.setHandler(servletContext) + jettyServer.start() + + System.out.println( + "Jetty server: http://localhost:" + PORT + "/") + + try { + GlobalTracer.register(tracer) + } catch (final Exception e) { + // Force it anyway using reflection + final Field field = GlobalTracer.getDeclaredField("tracer") + field.setAccessible(true) + field.set(null, tracer) + } + writer.start() + assert GlobalTracer.isRegistered() + } + + def cleanup() { + jettyServer.stop() + jettyServer.destroy() + } + + @Unroll + def "test #path servlet call"() { + setup: + def request = new Request.Builder() + .url("http://localhost:$PORT/$path") + .get() + .build() + def response = client.newCall(request).execute() + + expect: + response.body().string().trim() == expectedResponse + writer.size() == 2 // second (parent) trace is the okhttp call above... + def trace = writer.firstTrace() + trace.size() == 1 + def span = trace[0] + + span.context().operationName == "Servlet Request - GET" + !span.context().getErrorFlag() + span.context().parentId != 0 // parent should be the okhttp call. + span.context().tags[Tags.HTTP_URL.key] == "http://localhost:$PORT/$path" + span.context().tags[Tags.HTTP_METHOD.key] == "GET" + span.context().tags[Tags.SPAN_KIND.key] == Tags.SPAN_KIND_SERVER + span.context().tags[Tags.COMPONENT.key] == "java-web-servlet" + span.context().tags[Tags.HTTP_STATUS.key] == 200 + span.context().tags[DDTags.THREAD_NAME] != null + span.context().tags[DDTags.THREAD_ID] != null + span.context().tags.size() == 7 + + where: + path | expectedResponse + "async" | "Hello Async" + "sync" | "Hello Sync" + } + + private static int randomOpenPort() { + new ServerSocket(0).withCloseable { + it.setReuseAddress(true) + return it.getLocalPort() + } + } +} diff --git a/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/TestServlet.groovy b/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/TestServlet.groovy new file mode 100644 index 0000000000..373049f204 --- /dev/null +++ b/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/TestServlet.groovy @@ -0,0 +1,32 @@ +package com.datadoghq.agent.integration.servlet + +import groovy.servlet.AbstractHttpServlet + +import javax.servlet.annotation.WebServlet +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse + +class TestServlet { + + @WebServlet + static class Sync extends AbstractHttpServlet { + @Override + void doGet(HttpServletRequest req, HttpServletResponse resp) { + resp.writer.print("Hello Sync") + } + } + + @WebServlet(asyncSupported = true) + static class Async extends AbstractHttpServlet { + @Override + void doGet(HttpServletRequest req, HttpServletResponse resp) { + Thread initialThread = Thread.currentThread() + def context = req.startAsync() + context.start { + assert Thread.currentThread() != initialThread + resp.writer.print("Hello Async") + context.complete() + } + } + } +} diff --git a/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/TomcatServletTest.groovy b/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/TomcatServletTest.groovy new file mode 100644 index 0000000000..d30b808056 --- /dev/null +++ b/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/TomcatServletTest.groovy @@ -0,0 +1,125 @@ +package com.datadoghq.agent.integration.servlet + +import com.datadoghq.trace.DDTags +import com.datadoghq.trace.DDTracer +import com.datadoghq.trace.writer.ListWriter +import com.google.common.io.Files +import io.opentracing.tag.Tags +import io.opentracing.util.GlobalTracer +import okhttp3.OkHttpClient +import okhttp3.Request +import org.apache.catalina.Context +import org.apache.catalina.startup.Tomcat +import org.apache.tomcat.JarScanFilter +import org.apache.tomcat.JarScanType +import spock.lang.Specification +import spock.lang.Unroll + +import java.lang.reflect.Field + +class TomcatServletTest extends Specification { + + static final int PORT = randomOpenPort() + OkHttpClient client = new OkHttpClient.Builder() + // Uncomment when debugging: +// .connectTimeout(1, TimeUnit.HOURS) +// .writeTimeout(1, TimeUnit.HOURS) +// .readTimeout(1, TimeUnit.HOURS) + .build() + + Tomcat tomcatServer + Context appContext + + ListWriter writer = new ListWriter() + DDTracer tracer = new DDTracer(writer) + + def setup() { + tomcatServer = new Tomcat() + tomcatServer.setPort(PORT) + + def baseDir = Files.createTempDir() + baseDir.deleteOnExit() + tomcatServer.setBaseDir(baseDir.getAbsolutePath()) + + final File applicationDir = new File(baseDir, "/webapps/ROOT") + if (!applicationDir.exists()) { + applicationDir.mkdirs() + } + appContext = tomcatServer.addWebapp("", applicationDir.getAbsolutePath()) + // Speed up startup by disabling jar scanning: + appContext.getJarScanner().setJarScanFilter(new JarScanFilter(){ + @Override + boolean check(JarScanType jarScanType, String jarName) { + return false + } + }) + + Tomcat.addServlet(appContext, "syncServlet", new TestServlet.Sync()) + appContext.addServletMappingDecoded("/sync", "syncServlet") + + Tomcat.addServlet(appContext, "asyncServlet", new TestServlet.Async()) + appContext.addServletMappingDecoded("/async", "asyncServlet") + + tomcatServer.start() + System.out.println( + "Tomcat server: http://" + tomcatServer.getHost().getName() + ":" + PORT + "/") + + + try { + GlobalTracer.register(tracer) + } catch (final Exception e) { + // Force it anyway using reflection + final Field field = GlobalTracer.getDeclaredField("tracer") + field.setAccessible(true) + field.set(null, tracer) + } + writer.start() + assert GlobalTracer.isRegistered() + } + + def cleanup() { + tomcatServer.stop() + tomcatServer.destroy() + } + + @Unroll + def "test #path servlet call"() { + setup: + def request = new Request.Builder() + .url("http://localhost:$PORT/$path") + .get() + .build() + def response = client.newCall(request).execute() + + expect: + response.body().string().trim() == expectedResponse + writer.size() == 2 // second (parent) trace is the okhttp call above... + def trace = writer.firstTrace() + trace.size() == 1 + def span = trace[0] + + span.context().operationName == "Servlet Request - GET" + !span.context().getErrorFlag() + span.context().parentId != 0 // parent should be the okhttp call. + span.context().tags[Tags.HTTP_URL.key] == "http://localhost:$PORT/$path" + span.context().tags[Tags.HTTP_METHOD.key] == "GET" + span.context().tags[Tags.SPAN_KIND.key] == Tags.SPAN_KIND_SERVER + span.context().tags[Tags.COMPONENT.key] == "java-web-servlet" + span.context().tags[Tags.HTTP_STATUS.key] == 200 + span.context().tags[DDTags.THREAD_NAME] != null + span.context().tags[DDTags.THREAD_ID] != null + span.context().tags.size() == 7 + + where: + path | expectedResponse + "async" | "Hello Async" + "sync" | "Hello Sync" + } + + private static int randomOpenPort() { + new ServerSocket(0).withCloseable { + it.setReuseAddress(true) + return it.getLocalPort() + } + } +} diff --git a/dd-java-agent-ittests/src/test/java/com/datadoghq/agent/TraceAnnotationsManagerTest.java b/dd-java-agent-ittests/src/test/java/com/datadoghq/agent/TraceAnnotationsManagerTest.java index b302e68b2f..f290c63645 100644 --- a/dd-java-agent-ittests/src/test/java/com/datadoghq/agent/TraceAnnotationsManagerTest.java +++ b/dd-java-agent-ittests/src/test/java/com/datadoghq/agent/TraceAnnotationsManagerTest.java @@ -22,7 +22,7 @@ public class TraceAnnotationsManagerTest { try { GlobalTracer.register(tracer); } catch (final Exception e) { - // Force it anyway using reflexion + // Force it anyway using reflection final Field field = GlobalTracer.class.getDeclaredField("tracer"); field.setAccessible(true); field.set(null, tracer); diff --git a/dd-java-agent-ittests/src/test/java/com/datadoghq/agent/integration/JettyServletInstrumentationTest.java b/dd-java-agent-ittests/src/test/java/com/datadoghq/agent/integration/JettyServletInstrumentationTest.java deleted file mode 100644 index 94c7100026..0000000000 --- a/dd-java-agent-ittests/src/test/java/com/datadoghq/agent/integration/JettyServletInstrumentationTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.datadoghq.agent.integration; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.IOException; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -/** @author renaudboutet */ -public class JettyServletInstrumentationTest { - - private Server jettyServer; - private ServletContextHandler servletContext; - - @Before - public void beforeTest() throws Exception { - servletContext = new ServletContextHandler(); - servletContext.setContextPath("/"); - // servletContext.addServlet(TestServlet.class, "/hello"); - - jettyServer = new Server(0); - jettyServer.setHandler(servletContext); - jettyServer.start(); - } - - @Test - public void testIsTracingFilterPresent() throws IOException { - assertThat(servletContext.getServletContext().getFilterRegistration("tracingFilter")) - .isNotNull(); - } - - @After - public void afterTest() throws Exception { - jettyServer.stop(); - jettyServer.join(); - } -} diff --git a/dd-java-agent-ittests/src/test/java/com/datadoghq/agent/integration/TomcatServletInstrumentationTest.java b/dd-java-agent-ittests/src/test/java/com/datadoghq/agent/integration/TomcatServletInstrumentationTest.java deleted file mode 100644 index 47e4ad7364..0000000000 --- a/dd-java-agent-ittests/src/test/java/com/datadoghq/agent/integration/TomcatServletInstrumentationTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.datadoghq.agent.integration; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.File; -import org.apache.catalina.Context; -import org.apache.catalina.startup.Tomcat; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class TomcatServletInstrumentationTest { - - private final int serverPort = 9786; - - protected Tomcat tomcatServer; - Context appContext; - - @Before - public void beforeTest() throws Exception { - tomcatServer = new Tomcat(); - tomcatServer.setPort(serverPort); - - final File baseDir = new File("tomcat"); - tomcatServer.setBaseDir(baseDir.getAbsolutePath()); - - final File applicationDir = new File(baseDir + "/webapps", "/ROOT"); - if (!applicationDir.exists()) { - applicationDir.mkdirs(); - } - appContext = tomcatServer.addWebapp("", applicationDir.getAbsolutePath()); - // Tomcat.addServlet(appContext, "helloWorldServlet", new TestServlet()); - // appContext.addServletMappingDecoded("/hello", "helloWorldServlet"); - - tomcatServer.start(); - System.out.println( - "Tomcat server: http://" + tomcatServer.getHost().getName() + ":" + serverPort + "/"); - } - - @Test - public void test() { - assertThat(appContext.getServletContext().getFilterRegistration("tracingFilter")).isNotNull(); - } - - @After - public void afterTest() throws Exception { - tomcatServer.stop(); - } -} diff --git a/dd-java-agent/integrations/helpers/helpers.gradle b/dd-java-agent/integrations/helpers/helpers.gradle index 1adb393301..56c86aa6a0 100644 --- a/dd-java-agent/integrations/helpers/helpers.gradle +++ b/dd-java-agent/integrations/helpers/helpers.gradle @@ -22,10 +22,8 @@ dependencies { compile group: 'io.opentracing.contrib', name: 'opentracing-cassandra-driver', version: '0.0.2' compile group: 'io.opentracing.contrib', name: 'opentracing-apache-httpclient', version: '0.0.2' - compileOnly group: 'org.eclipse.jetty', name: 'jetty-util', version: '9.3.6.v20151106' - compileOnly group: 'org.eclipse.jetty', name: 'jetty-server', version: '9.3.6.v20151106' - compileOnly group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '9.3.6.v20151106' - compileOnly group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '9.0.0.M1' + compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.0.1' +// compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.3' compileOnly group: 'org.mongodb', name: 'mongo-java-driver', version: '3.4.2' compileOnly group: 'org.mongodb', name: 'mongodb-driver-async', version: '3.4.2' compileOnly group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.6.0' diff --git a/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/JettyServletHelper.java b/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/JettyServletHelper.java deleted file mode 100644 index 3ae2378098..0000000000 --- a/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/JettyServletHelper.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.datadoghq.agent.integration; - -import io.opentracing.contrib.web.servlet.filter.TracingFilter; -import java.util.EnumSet; -import javax.servlet.Filter; -import javax.servlet.FilterRegistration; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.jboss.byteman.rule.Rule; - -/** Patch the Jetty Servlet during the init steps */ -public class JettyServletHelper extends DDAgentTracingHelper { - - private static final String pattern = "/*"; - - public JettyServletHelper(final Rule rule) { - super(rule); - } - - /** - * Strategy: Use the contextHandler provided to add a new Tracing filter - * - * @param contextHandler The current contextHandler - * @return The same current contextHandler but patched - * @throws Exception - */ - @Override - protected ServletContextHandler doPatch(final ServletContextHandler contextHandler) - throws Exception { - if (contextHandler.getServletContext().getFilterRegistration("tracingFilter") == null) { - final Filter filter = new TracingFilter(tracer); - final FilterRegistration.Dynamic registration = - contextHandler.getServletContext().addFilter("tracingFilter", filter); - if (registration != null) { // filter of that name must already be registered. - registration.setAsyncSupported(true); - registration.addMappingForUrlPatterns( - EnumSet.allOf(javax.servlet.DispatcherType.class), true, pattern); - } - } - return contextHandler; - } -} diff --git a/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet2Helper.java b/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet2Helper.java new file mode 100644 index 0000000000..f7d9fa5a9c --- /dev/null +++ b/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet2Helper.java @@ -0,0 +1,86 @@ +package com.datadoghq.agent.integration; + +import io.opentracing.ActiveSpan; +import io.opentracing.NoopTracerFactory; +import io.opentracing.SpanContext; +import io.opentracing.Tracer; +import io.opentracing.contrib.web.servlet.filter.HttpServletRequestExtractAdapter; +import io.opentracing.contrib.web.servlet.filter.ServletFilterSpanDecorator; +import io.opentracing.propagation.Format; +import io.opentracing.tag.Tags; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.jboss.byteman.rule.Rule; + +/** Please be very careful not to introduce any Servlet 3 dependencies into this class. */ +@Slf4j +public class Servlet2Helper extends OpenTracingHelper { + + /** + * Used as a key of {@link HttpServletRequest#setAttribute(String, Object)} to inject server span + * context + */ + public static final String SERVER_SPAN_CONTEXT = + Servlet2Helper.class.getName() + ".activeSpanContext"; + + protected final Tracer tracer; + + public Servlet2Helper(final Rule rule) { + super(rule); + Tracer tracerResolved; + try { + tracerResolved = getTracer(); + tracerResolved = tracerResolved == null ? NoopTracerFactory.create() : tracerResolved; + } catch (final Exception e) { + tracerResolved = NoopTracerFactory.create(); + log.warn("Failed to retrieve the tracer, using a NoopTracer instead: {}", e.getMessage()); + log.warn(e.getMessage(), e); + } + tracer = tracerResolved; + } + + public void onRequest(final HttpServletRequest req, final HttpServletResponse resp) { + if (req.getAttribute(SERVER_SPAN_CONTEXT) != null) { + // Perhaps we're already tracing? + return; + } + + final SpanContext extractedContext = + tracer.extract(Format.Builtin.HTTP_HEADERS, new HttpServletRequestExtractAdapter(req)); + + final ActiveSpan span = + tracer + .buildSpan("Servlet Request - " + req.getMethod()) + .asChildOf(extractedContext) + .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) + .startActive(); + + req.setAttribute(SERVER_SPAN_CONTEXT, span.context()); + + ServletFilterSpanDecorator.STANDARD_TAGS.onRequest(req, span); + } + + public void onError( + final HttpServletRequest req, final HttpServletResponse resp, final Throwable ex) { + if (req.getAttribute(SERVER_SPAN_CONTEXT) == null) { + // Doesn't look like an active span was started at the beginning + return; + } + + final ActiveSpan span = tracer.activeSpan(); + ServletFilterSpanDecorator.STANDARD_TAGS.onError(req, resp, ex, span); + span.deactivate(); + } + + public void onResponse(final HttpServletRequest req, final HttpServletResponse resp) { + if (req.getAttribute(SERVER_SPAN_CONTEXT) == null) { + // Doesn't look like an active span was started at the beginning + return; + } + + final ActiveSpan span = tracer.activeSpan(); + ServletFilterSpanDecorator.STANDARD_TAGS.onResponse(req, resp, span); + span.deactivate(); + } +} diff --git a/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet3Helper.java b/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet3Helper.java new file mode 100644 index 0000000000..de00d58715 --- /dev/null +++ b/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet3Helper.java @@ -0,0 +1,95 @@ +package com.datadoghq.agent.integration; + +import io.opentracing.ActiveSpan; +import io.opentracing.contrib.web.servlet.filter.ServletFilterSpanDecorator; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.jboss.byteman.rule.Rule; + +@Slf4j +public class Servlet3Helper extends Servlet2Helper { + + public Servlet3Helper(final Rule rule) { + super(rule); + } + + /** + * The distinction with this method compared with Servlet2Helper.onResponse is the addition of the + * async support. + * + * @param req + * @param resp + */ + @Override + public void onResponse(final HttpServletRequest req, final HttpServletResponse resp) { + if (req.getAttribute(SERVER_SPAN_CONTEXT) == null) { + // Doesn't look like an active span was started at the beginning + return; + } + + final ActiveSpan span = tracer.activeSpan(); + if (req.isAsyncStarted()) { + addAsyncListeners(req, resp, span); + } else { + ServletFilterSpanDecorator.STANDARD_TAGS.onResponse(req, resp, span); + } + span.deactivate(); + } + + private void addAsyncListeners( + final HttpServletRequest req, final HttpServletResponse resp, final ActiveSpan span) { + + final ActiveSpan.Continuation cont = span.capture(); + final AtomicBoolean activated = new AtomicBoolean(false); + // what if async is already finished? This would not be called + req.getAsyncContext() + .addListener( + new AsyncListener() { + @Override + public void onComplete(final AsyncEvent event) throws IOException { + if (activated.compareAndSet(false, true)) { + try (ActiveSpan activeSpan = cont.activate()) { + ServletFilterSpanDecorator.STANDARD_TAGS.onResponse( + (HttpServletRequest) event.getSuppliedRequest(), + (HttpServletResponse) event.getSuppliedResponse(), + span); + } + } + } + + @Override + public void onTimeout(final AsyncEvent event) throws IOException { + if (activated.compareAndSet(false, true)) { + try (ActiveSpan activeSpan = cont.activate()) { + ServletFilterSpanDecorator.STANDARD_TAGS.onTimeout( + (HttpServletRequest) event.getSuppliedRequest(), + (HttpServletResponse) event.getSuppliedResponse(), + event.getAsyncContext().getTimeout(), + span); + } + } + } + + @Override + public void onError(final AsyncEvent event) throws IOException { + if (activated.compareAndSet(false, true)) { + try (ActiveSpan activeSpan = cont.activate()) { + ServletFilterSpanDecorator.STANDARD_TAGS.onError( + (HttpServletRequest) event.getSuppliedRequest(), + (HttpServletResponse) event.getSuppliedResponse(), + event.getThrowable(), + span); + } + } + } + + @Override + public void onStartAsync(final AsyncEvent event) throws IOException {} + }); + } +} diff --git a/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/TomcatServletHelper.java b/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/TomcatServletHelper.java deleted file mode 100644 index 0cb37cc540..0000000000 --- a/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/TomcatServletHelper.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.datadoghq.agent.integration; - -import io.opentracing.contrib.web.servlet.filter.TracingFilter; -import java.util.EnumSet; -import javax.servlet.Filter; -import javax.servlet.FilterRegistration; -import org.apache.catalina.core.ApplicationContext; -import org.jboss.byteman.rule.Rule; - -/** Patch the Tomcat Servlet during the init steps */ -public class TomcatServletHelper extends DDAgentTracingHelper { - - private static final String pattern = "/*"; - - public TomcatServletHelper(final Rule rule) { - super(rule); - } - - /** - * Strategy: Use the contextHandler provided to add a new Tracing filter - * - * @param contextHandler The current contextHandler - * @return The same current contextHandler but patched - * @throws Exception - */ - @Override - protected ApplicationContext doPatch(final ApplicationContext contextHandler) throws Exception { - if (contextHandler.getFilterRegistration("tracingFilter") == null) { - final Filter filter = new TracingFilter(tracer); - final FilterRegistration.Dynamic registration = - contextHandler.addFilter("tracingFilter", filter); - if (registration != null) { // filter of that name must already be registered. - registration.setAsyncSupported(true); - registration.addMappingForUrlPatterns( - EnumSet.allOf(javax.servlet.DispatcherType.class), true, pattern); - } - } - return contextHandler; - } -} diff --git a/dd-java-agent/integrations/jetty/jetty.gradle b/dd-java-agent/integrations/jetty/jetty.gradle deleted file mode 100644 index cf84c809fd..0000000000 --- a/dd-java-agent/integrations/jetty/jetty.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'version-scan' - -versionScan { - group = "org.eclipse.jetty" - module = "jetty-server" - versions = "[8.0,)" - legacyGroup = "org.mortbay.jetty" - legacyModule = "jetty" - scanMethods = true - verifyPresent = [ - "org.eclipse.jetty.server.ServletRequestHttpWrapper": "getPart", - ] -} diff --git a/dd-java-agent/integrations/servlet-2/servlet-2.gradle b/dd-java-agent/integrations/servlet-2/servlet-2.gradle new file mode 100644 index 0000000000..afcb9bab92 --- /dev/null +++ b/dd-java-agent/integrations/servlet-2/servlet-2.gradle @@ -0,0 +1,11 @@ +apply plugin: 'version-scan' + +versionScan { + group = "javax.servlet" + module = "servlet-api" + versions = "[2.3,)" + verifyPresent = [ + "javax.servlet.ServletContextEvent": null, + "javax.servlet.FilterChain" : null, + ] +} diff --git a/dd-java-agent/integrations/servlet-3/servlet-3.gradle b/dd-java-agent/integrations/servlet-3/servlet-3.gradle new file mode 100644 index 0000000000..0a9bf2d694 --- /dev/null +++ b/dd-java-agent/integrations/servlet-3/servlet-3.gradle @@ -0,0 +1,12 @@ +apply plugin: 'version-scan' + +versionScan { + group = "javax.servlet" + module = 'javax.servlet-api' + legacyModule = "servlet-api" + versions = "[3.0,)" + verifyPresent = [ + "javax.servlet.AsyncEvent" : null, + "javax.servlet.AsyncListener": null, + ] +} diff --git a/dd-java-agent/integrations/tomcat-embedded/tomcat-embedded.gradle b/dd-java-agent/integrations/tomcat-embedded/tomcat-embedded.gradle deleted file mode 100644 index db93b220bb..0000000000 --- a/dd-java-agent/integrations/tomcat-embedded/tomcat-embedded.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'version-scan' - -versionScan { - group = "org.apache.tomcat.embed" - module = "tomcat-embed-core" - versions = "[8.0,)" - verifyPresent = [ - "org.apache.catalina.WebResource" : null, - "org.apache.catalina.webresources.TrackedInputStream" : null, - "org.apache.catalina.webresources.AbstractArchiveResource": null, - ] -} diff --git a/dd-java-agent/integrations/tomcat/tomcat.gradle b/dd-java-agent/integrations/tomcat/tomcat.gradle deleted file mode 100644 index 4f6ea77377..0000000000 --- a/dd-java-agent/integrations/tomcat/tomcat.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'version-scan' - -versionScan { - group = "org.apache.tomcat" - module = "tomcat-catalina" - versions = "[8.0,)" - verifyPresent = [ - "org.apache.catalina.WebResource" : null, - "org.apache.catalina.webresources.TrackedInputStream" : null, - "org.apache.catalina.webresources.AbstractArchiveResource": null, - ] -} diff --git a/dd-java-agent/src/main/resources/dd-trace-supported-framework.yaml b/dd-java-agent/src/main/resources/dd-trace-supported-framework.yaml index 7feceb653e..248b0d741d 100644 --- a/dd-java-agent/src/main/resources/dd-trace-supported-framework.yaml +++ b/dd-java-agent/src/main/resources/dd-trace-supported-framework.yaml @@ -81,23 +81,43 @@ opentracing-okhttp3: okhttp3.ConnectionPool: okhttp3.Headers: -opentracing-web-servlet-filter_jetty: - - artifact: jetty-server - supported_version: (8\.|9\.).* +httpservlet-2: + - artifact: HttpServlet-2.service-entry identifying_present_classes: - org.eclipse.jetty.server.ServletRequestHttpWrapper: getPart + javax.servlet.ServletContextEvent: + javax.servlet.FilterChain: + identifying_missing_classes: + - javax.servlet.AsyncEvent + - javax.servlet.AsyncListener -opentracing-web-servlet-filter_tomcat: - - artifact: opentracing-web-servlet-filter_tomcat - supported_version: (8\.|9\.).* + - artifact: HttpServlet-2.service-exit identifying_present_classes: - org.apache.catalina.WebResource: - org.apache.catalina.webresources.TrackedInputStream: - org.apache.catalina.webresources.AbstractArchiveResource: + javax.servlet.ServletContextEvent: + javax.servlet.FilterChain: + identifying_missing_classes: + - javax.servlet.AsyncEvent + - javax.servlet.AsyncListener - - artifact: opentracing-web-servlet-filter_tomcat - supported_version: (8\.|9\.).* + - artifact: HttpServlet-2.service-error identifying_present_classes: - org.apache.catalina.WebResource: - org.apache.catalina.webresources.TrackedInputStream: - org.apache.catalina.webresources.AbstractArchiveResource: + javax.servlet.ServletContextEvent: + javax.servlet.FilterChain: + identifying_missing_classes: + - javax.servlet.AsyncEvent + - javax.servlet.AsyncListener + +httpservlet-3: + - artifact: HttpServlet-3.service-entry + identifying_present_classes: + javax.servlet.AsyncEvent: + javax.servlet.AsyncListener: + + - artifact: HttpServlet-3.service-exit + identifying_present_classes: + javax.servlet.AsyncEvent: + javax.servlet.AsyncListener: + + - artifact: HttpServlet-3.service-error + identifying_present_classes: + javax.servlet.AsyncEvent: + javax.servlet.AsyncListener: diff --git a/dd-java-agent/src/main/resources/initializer-rules.btm b/dd-java-agent/src/main/resources/initializer-rules.btm index 433adb44f1..8ef6b4ad53 100644 --- a/dd-java-agent/src/main/resources/initializer-rules.btm +++ b/dd-java-agent/src/main/resources/initializer-rules.btm @@ -82,25 +82,13 @@ DO ENDRULE -# Instrument Servlet - Jetty +# Instrument Servlet # =========================== -RULE ServletContextHandler-clinit -CLASS org.eclipse.jetty.servlet.ServletContextHandler -METHOD +RULE HttpServlet-init +CLASS ^javax.servlet.http.HttpServlet +METHOD AT EXIT IF TRUE DO - com.datadoghq.agent.InstrumentationRulesManager.registerClassLoad(); -ENDRULE - - -# Instrument Servlet - Tomcat -# =========================== -RULE ApplicationContext-clinit -CLASS org.apache.catalina.core.ApplicationContext -METHOD -AT EXIT -IF TRUE -DO - com.datadoghq.agent.InstrumentationRulesManager.registerClassLoad(); + com.datadoghq.agent.InstrumentationRulesManager.registerClassLoad($0); ENDRULE diff --git a/dd-java-agent/src/main/resources/integration-rules.btm b/dd-java-agent/src/main/resources/integration-rules.btm index f445d05883..4ecd579026 100644 --- a/dd-java-agent/src/main/resources/integration-rules.btm +++ b/dd-java-agent/src/main/resources/integration-rules.btm @@ -107,31 +107,72 @@ DO patch($0) ENDRULE - -# Instrument Servlet - Jetty +# Instrument Servlet 2 # =========================== -# State 0 - no filter installed -# State 1 - filter installed -RULE opentracing-web-servlet-filter_jetty -CLASS org.eclipse.jetty.servlet.ServletContextHandler -METHOD -HELPER com.datadoghq.agent.integration.JettyServletHelper -AT EXIT -IF getState($0.getServletContext()) == 0 +RULE HttpServlet-2.service-entry +CLASS ^javax.servlet.http.HttpServlet +METHOD service(HttpServletRequest, HttpServletResponse) +HELPER com.datadoghq.agent.integration.Servlet2Helper +COMPILE +AT ENTRY +IF TRUE DO - patch($0) + onRequest($1, $2) ENDRULE - -# Instrument Servlet - Tomcat -# =========================== -RULE opentracing-web-servlet-filter_tomcat -CLASS org.apache.catalina.core.ApplicationContext -METHOD +RULE HttpServlet-2.service-exit +CLASS ^javax.servlet.http.HttpServlet +METHOD service(HttpServletRequest, HttpServletResponse) +HELPER com.datadoghq.agent.integration.Servlet2Helper COMPILE -HELPER com.datadoghq.agent.integration.TomcatServletHelper AT EXIT IF TRUE DO - patch($0) + onResponse($1, $2) +ENDRULE + +RULE HttpServlet-2.service-error +CLASS ^javax.servlet.http.HttpServlet +METHOD service(HttpServletRequest, HttpServletResponse) +HELPER com.datadoghq.agent.integration.Servlet2Helper +COMPILE +AT EXCEPTION EXIT +IF TRUE +DO + onError($1, $2, $^) +ENDRULE + +# Instrument Servlet 3 +# =========================== +RULE HttpServlet-3.service-entry +CLASS ^javax.servlet.http.HttpServlet +METHOD service(HttpServletRequest, HttpServletResponse) +HELPER com.datadoghq.agent.integration.Servlet3Helper +COMPILE +AT ENTRY +IF TRUE +DO + onRequest($1, $2) +ENDRULE + +RULE HttpServlet-3.service-exit +CLASS ^javax.servlet.http.HttpServlet +METHOD service(HttpServletRequest, HttpServletResponse) +HELPER com.datadoghq.agent.integration.Servlet3Helper +COMPILE +AT EXIT +IF TRUE +DO + onResponse($1, $2) +ENDRULE + +RULE HttpServlet-3.service-error +CLASS ^javax.servlet.http.HttpServlet +METHOD service(HttpServletRequest, HttpServletResponse) +HELPER com.datadoghq.agent.integration.Servlet3Helper +COMPILE +AT EXCEPTION EXIT +IF TRUE +DO + onError($1, $2, $^) ENDRULE diff --git a/settings.gradle b/settings.gradle index e4e3e3af0b..c5863ce952 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,13 +15,12 @@ include ':dd-java-agent:integrations:helpers' include ':dd-java-agent:integrations:apache-httpclient' include ':dd-java-agent:integrations:aws-sdk' include ':dd-java-agent:integrations:cassandra' -include ':dd-java-agent:integrations:jetty' include ':dd-java-agent:integrations:jms' include ':dd-java-agent:integrations:mongo' include ':dd-java-agent:integrations:mongo-async' include ':dd-java-agent:integrations:okhttp' -include ':dd-java-agent:integrations:tomcat' -include ':dd-java-agent:integrations:tomcat-embedded' +include ':dd-java-agent:integrations:servlet-2' +include ':dd-java-agent:integrations:servlet-3' def setBuildFile(project) { project.buildFileName = "${project.name}.gradle" From fb133c890586ae0df838a82baaa454caf3010ffd Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Thu, 21 Sep 2017 08:12:44 -0700 Subject: [PATCH 2/4] Update servlet operation name. --- .../agent/integration/servlet/JettyServletTest.groovy | 2 +- .../agent/integration/servlet/TomcatServletTest.groovy | 2 +- .../java/com/datadoghq/agent/integration/Servlet2Helper.java | 4 +++- .../com/datadoghq/trace/integration/OperationDecorator.java | 2 -- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/JettyServletTest.groovy b/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/JettyServletTest.groovy index 63bfb42bbf..5c1fa28cd4 100644 --- a/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/JettyServletTest.groovy +++ b/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/JettyServletTest.groovy @@ -76,7 +76,7 @@ class JettyServletTest extends Specification { trace.size() == 1 def span = trace[0] - span.context().operationName == "Servlet Request - GET" + span.context().operationName == "servlet.request" !span.context().getErrorFlag() span.context().parentId != 0 // parent should be the okhttp call. span.context().tags[Tags.HTTP_URL.key] == "http://localhost:$PORT/$path" diff --git a/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/TomcatServletTest.groovy b/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/TomcatServletTest.groovy index d30b808056..2f494f52eb 100644 --- a/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/TomcatServletTest.groovy +++ b/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/TomcatServletTest.groovy @@ -98,7 +98,7 @@ class TomcatServletTest extends Specification { trace.size() == 1 def span = trace[0] - span.context().operationName == "Servlet Request - GET" + span.context().operationName == "servlet.request" !span.context().getErrorFlag() span.context().parentId != 0 // parent should be the okhttp call. span.context().tags[Tags.HTTP_URL.key] == "http://localhost:$PORT/$path" diff --git a/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet2Helper.java b/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet2Helper.java index f7d9fa5a9c..182a933823 100644 --- a/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet2Helper.java +++ b/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet2Helper.java @@ -24,6 +24,8 @@ public class Servlet2Helper extends OpenTracingHelper { public static final String SERVER_SPAN_CONTEXT = Servlet2Helper.class.getName() + ".activeSpanContext"; + public static final String SERVLET_OPERATION_NAME = "servlet.request"; + protected final Tracer tracer; public Servlet2Helper(final Rule rule) { @@ -51,7 +53,7 @@ public class Servlet2Helper extends OpenTracingHelper { final ActiveSpan span = tracer - .buildSpan("Servlet Request - " + req.getMethod()) + .buildSpan(SERVLET_OPERATION_NAME) .asChildOf(extractedContext) .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) .startActive(); diff --git a/dd-trace/src/main/java/com/datadoghq/trace/integration/OperationDecorator.java b/dd-trace/src/main/java/com/datadoghq/trace/integration/OperationDecorator.java index 72ff05e703..f66c103550 100644 --- a/dd-trace/src/main/java/com/datadoghq/trace/integration/OperationDecorator.java +++ b/dd-trace/src/main/java/com/datadoghq/trace/integration/OperationDecorator.java @@ -17,8 +17,6 @@ public class OperationDecorator extends AbstractDecorator { // Component name <> Operation name put("apache-httpclient", "apache.http"); put("java-aws-sdk", "aws.http"); - // Jetty + Tomcat (same integration used) - put("java-web-servlet", "servlet.request"); // FIXME: JMS ops card is low (jms-send or jms-receive), may be this mapping is useless put("java-jms", "jms"); put("okhttp", "okhttp.http"); From cffde51b9e304a251a9a0a12635a54074f86401d Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Thu, 21 Sep 2017 13:42:43 -0700 Subject: [PATCH 3/4] Add funky locking to ensure consistent execution for jetty test. --- .../servlet/JettyServletTest.groovy | 24 ++++++++++++++++++- .../agent/InstrumentationRulesManager.java | 4 ++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/JettyServletTest.groovy b/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/JettyServletTest.groovy index 5c1fa28cd4..bbff95be0a 100644 --- a/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/JettyServletTest.groovy +++ b/dd-java-agent-ittests/src/test/groovy/com/datadoghq/agent/integration/servlet/JettyServletTest.groovy @@ -1,23 +1,38 @@ package com.datadoghq.agent.integration.servlet +import com.datadoghq.trace.DDBaseSpan import com.datadoghq.trace.DDTags import com.datadoghq.trace.DDTracer import com.datadoghq.trace.writer.ListWriter import io.opentracing.tag.Tags import io.opentracing.util.GlobalTracer +import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Request +import okhttp3.Response import org.eclipse.jetty.server.Server import org.eclipse.jetty.servlet.ServletContextHandler import spock.lang.Specification import spock.lang.Unroll import java.lang.reflect.Field +import java.util.concurrent.CountDownLatch class JettyServletTest extends Specification { static final int PORT = randomOpenPort() + + // Jetty needs this to ensure consistent ordering for async. + static CountDownLatch latch OkHttpClient client = new OkHttpClient.Builder() + .addNetworkInterceptor(new Interceptor() { + @Override + Response intercept(Interceptor.Chain chain) throws IOException { + def response = chain.proceed(chain.request()) + JettyServletTest.latch.await() + return response + } + }) // Uncomment when debugging: // .connectTimeout(1, TimeUnit.HOURS) // .writeTimeout(1, TimeUnit.HOURS) @@ -27,7 +42,13 @@ class JettyServletTest extends Specification { private Server jettyServer private ServletContextHandler servletContext - ListWriter writer = new ListWriter() + ListWriter writer = new ListWriter() { + @Override + void write(final List> trace) { + add(trace) + JettyServletTest.latch.countDown() + } + } DDTracer tracer = new DDTracer(writer) def setup() { @@ -63,6 +84,7 @@ class JettyServletTest extends Specification { @Unroll def "test #path servlet call"() { setup: + latch = new CountDownLatch(1) def request = new Request.Builder() .url("http://localhost:$PORT/$path") .get() diff --git a/dd-java-agent/src/main/java/com/datadoghq/agent/InstrumentationRulesManager.java b/dd-java-agent/src/main/java/com/datadoghq/agent/InstrumentationRulesManager.java index ed51e65fb3..0d5393909b 100644 --- a/dd-java-agent/src/main/java/com/datadoghq/agent/InstrumentationRulesManager.java +++ b/dd-java-agent/src/main/java/com/datadoghq/agent/InstrumentationRulesManager.java @@ -80,10 +80,10 @@ public class InstrumentationRulesManager { final ClassLoader cl; if (obj instanceof ClassLoader) { cl = (ClassLoader) obj; - log.info("Calling initialize with {}", cl); + log.debug("Calling initialize with {}", cl); } else { cl = obj.getClass().getClassLoader(); - log.info("Calling initialize with {} and classloader ", obj, cl); + log.debug("Calling initialize with {} and classloader {}", obj, cl); } AgentRulesManager.INSTANCE.instrumentationRulesManager.initialize(cl); From 12323cef9f31649ac4bc213491980597c3f63d53 Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Thu, 21 Sep 2017 14:37:41 -0700 Subject: [PATCH 4/4] Update readme. --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 5992b976d1..7f7241a698 100644 --- a/README.md +++ b/README.md @@ -64,24 +64,24 @@ Finally, add the following JVM argument when starting your application—in your The Java Agent—once passed to your application—automatically traces requests to the frameworks, application servers, and databases shown below. It does this by using various libraries from [opentracing-contrib](https://github.com/opentracing-contrib). In most cases you don't need to install or configure anything; traces will automatically show up in your Datadog dashboards. The exception is [any database library that uses JDBC](#jdbc). -#### Frameworks - -| Framework | Versions | Comments | -| ------------- |:-------------:| ----- | -| [OkHTTP](https://github.com/opentracing-contrib/java-okhttp) | 3.x | HTTP client calls with [cross-process](http://opentracing.io/documentation/pages/api/cross-process-tracing.html) headers | -| [Apache HTTP Client](https://github.com/opentracing-contrib/java-apache-httpclient) | 4.3 + | HTTP client calls with [cross-process](http://opentracing.io/documentation/pages/api/cross-process-tracing.html) headers| -| [AWS SDK](https://github.com/opentracing-contrib/java-aws-sdk) | 1.11.0+ | Trace all client calls to any AWS service | -| [Web Servlet Filters](https://github.com/opentracing-contrib/java-web-servlet-filter) | Depends on web server | See [Application Servers](#application-servers) | -| [JMS 2](https://github.com/opentracing-contrib/java-jms) | 2.x | Trace calls to message brokers; distributed trace propagation not yet supported | - #### Application Servers | Server | Versions | Comments | | ------------- |:-------------:| -----| -| Jetty | 8.x, 9.x | HTTP client calls with [cross-process](http://opentracing.io/documentation/pages/api/cross-process-tracing.html) headers | -| Tomcat | 8.0.x, 8.5.x & 9.x | HTTP client calls with [cross-process](http://opentracing.io/documentation/pages/api/cross-process-tracing.html) headers | +| Java Servlet Compatible | 2.3+, 3.0+ | HTTP client calls with [cross-process](http://opentracing.io/documentation/pages/api/cross-process-tracing.html) headers are linked | -Requests to any web frameworks that use these application servers—Dropwizard and Spring Boot, for example—are automatically traced as well. +*Note:* Many application servers are Servlet compatible such as Tomcat, Jetty, Websphere, Weblogic, etc. +Also, frameworks like Spring Boot and Dropwizard inherently work because they use a Servlet compatible embedded application server. + +#### Frameworks + +| Framework | Versions | Comments | +| ------------- |:-------------:| ----- | +| [OkHTTP](https://github.com/opentracing-contrib/java-okhttp) | 3.x | HTTP client calls with [cross-process](http://opentracing.io/documentation/pages/api/cross-process-tracing.html) headers are linked | +| [Apache HTTP Client](https://github.com/opentracing-contrib/java-apache-httpclient) | 4.3 + | HTTP client calls with [cross-process](http://opentracing.io/documentation/pages/api/cross-process-tracing.html) headers are linked| +| [AWS SDK](https://github.com/opentracing-contrib/java-aws-sdk) | 1.11.0+ | Trace all client calls to any AWS service | +| [Web Servlet Filters](https://github.com/opentracing-contrib/java-web-servlet-filter) | Depends on web server | See [Application Servers](#application-servers) | +| [JMS 2](https://github.com/opentracing-contrib/java-jms) | 2.x | Trace calls to message brokers; distributed trace propagation not yet supported | #### Databases