Merge pull request #135 from DataDog/tyler/servlet

Replace tomcat/jetty specific integ with generic servlet integ.
This commit is contained in:
Tyler Benson 2017-09-22 10:45:53 -07:00 committed by GitHub
commit 897b3426b1
23 changed files with 608 additions and 283 deletions

View File

@ -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). 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 #### Application Servers
| Server | Versions | Comments | | 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 | | 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 |
| 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 |
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 #### Databases

View File

@ -0,0 +1,125 @@
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)
// .readTimeout(1, TimeUnit.HOURS)
.build()
private Server jettyServer
private ServletContextHandler servletContext
ListWriter writer = new ListWriter() {
@Override
void write(final List<DDBaseSpan<?>> trace) {
add(trace)
JettyServletTest.latch.countDown()
}
}
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:
latch = new CountDownLatch(1)
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"
!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()
}
}
}

View File

@ -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()
}
}
}
}

View File

@ -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"
!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()
}
}
}

View File

@ -22,7 +22,7 @@ public class TraceAnnotationsManagerTest {
try { try {
GlobalTracer.register(tracer); GlobalTracer.register(tracer);
} catch (final Exception e) { } catch (final Exception e) {
// Force it anyway using reflexion // Force it anyway using reflection
final Field field = GlobalTracer.class.getDeclaredField("tracer"); final Field field = GlobalTracer.class.getDeclaredField("tracer");
field.setAccessible(true); field.setAccessible(true);
field.set(null, tracer); field.set(null, tracer);

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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-cassandra-driver', version: '0.0.2'
compile group: 'io.opentracing.contrib', name: 'opentracing-apache-httpclient', 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: 'javax.servlet', name: 'javax.servlet-api', version: '3.0.1'
compileOnly group: 'org.eclipse.jetty', name: 'jetty-server', version: '9.3.6.v20151106' // compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.3'
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: 'org.mongodb', name: 'mongo-java-driver', version: '3.4.2' 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: 'org.mongodb', name: 'mongodb-driver-async', version: '3.4.2'
compileOnly group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.6.0' compileOnly group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.6.0'

View File

@ -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<ServletContextHandler> {
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;
}
}

View File

@ -0,0 +1,88 @@
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";
public static final String SERVLET_OPERATION_NAME = "servlet.request";
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_OPERATION_NAME)
.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();
}
}

View File

@ -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 {}
});
}
}

View File

@ -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<ApplicationContext> {
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;
}
}

View File

@ -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",
]
}

View File

@ -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,
]
}

View File

@ -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,
]
}

View File

@ -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,
]
}

View File

@ -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,
]
}

View File

@ -80,10 +80,10 @@ public class InstrumentationRulesManager {
final ClassLoader cl; final ClassLoader cl;
if (obj instanceof ClassLoader) { if (obj instanceof ClassLoader) {
cl = (ClassLoader) obj; cl = (ClassLoader) obj;
log.info("Calling initialize with {}", cl); log.debug("Calling initialize with {}", cl);
} else { } else {
cl = obj.getClass().getClassLoader(); 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); AgentRulesManager.INSTANCE.instrumentationRulesManager.initialize(cl);

View File

@ -81,23 +81,43 @@ opentracing-okhttp3:
okhttp3.ConnectionPool: okhttp3.ConnectionPool:
okhttp3.Headers: okhttp3.Headers:
opentracing-web-servlet-filter_jetty: httpservlet-2:
- artifact: jetty-server - artifact: HttpServlet-2.service-entry
supported_version: (8\.|9\.).*
identifying_present_classes: 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: HttpServlet-2.service-exit
- artifact: opentracing-web-servlet-filter_tomcat
supported_version: (8\.|9\.).*
identifying_present_classes: identifying_present_classes:
org.apache.catalina.WebResource: javax.servlet.ServletContextEvent:
org.apache.catalina.webresources.TrackedInputStream: javax.servlet.FilterChain:
org.apache.catalina.webresources.AbstractArchiveResource: identifying_missing_classes:
- javax.servlet.AsyncEvent
- javax.servlet.AsyncListener
- artifact: opentracing-web-servlet-filter_tomcat - artifact: HttpServlet-2.service-error
supported_version: (8\.|9\.).*
identifying_present_classes: identifying_present_classes:
org.apache.catalina.WebResource: javax.servlet.ServletContextEvent:
org.apache.catalina.webresources.TrackedInputStream: javax.servlet.FilterChain:
org.apache.catalina.webresources.AbstractArchiveResource: 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:

View File

@ -82,25 +82,13 @@ DO
ENDRULE ENDRULE
# Instrument Servlet - Jetty # Instrument Servlet
# =========================== # ===========================
RULE ServletContextHandler-clinit RULE HttpServlet-init
CLASS org.eclipse.jetty.servlet.ServletContextHandler CLASS ^javax.servlet.http.HttpServlet
METHOD <clinit> METHOD <init>
AT EXIT AT EXIT
IF TRUE IF TRUE
DO DO
com.datadoghq.agent.InstrumentationRulesManager.registerClassLoad(); com.datadoghq.agent.InstrumentationRulesManager.registerClassLoad($0);
ENDRULE
# Instrument Servlet - Tomcat
# ===========================
RULE ApplicationContext-clinit
CLASS org.apache.catalina.core.ApplicationContext
METHOD <clinit>
AT EXIT
IF TRUE
DO
com.datadoghq.agent.InstrumentationRulesManager.registerClassLoad();
ENDRULE ENDRULE

View File

@ -107,31 +107,72 @@ DO
patch($0) patch($0)
ENDRULE ENDRULE
# Instrument Servlet 2
# Instrument Servlet - Jetty
# =========================== # ===========================
# State 0 - no filter installed RULE HttpServlet-2.service-entry
# State 1 - filter installed CLASS ^javax.servlet.http.HttpServlet
RULE opentracing-web-servlet-filter_jetty METHOD service(HttpServletRequest, HttpServletResponse)
CLASS org.eclipse.jetty.servlet.ServletContextHandler HELPER com.datadoghq.agent.integration.Servlet2Helper
METHOD <init> COMPILE
HELPER com.datadoghq.agent.integration.JettyServletHelper AT ENTRY
AT EXIT IF TRUE
IF getState($0.getServletContext()) == 0
DO DO
patch($0) onRequest($1, $2)
ENDRULE ENDRULE
RULE HttpServlet-2.service-exit
# Instrument Servlet - Tomcat CLASS ^javax.servlet.http.HttpServlet
# =========================== METHOD service(HttpServletRequest, HttpServletResponse)
RULE opentracing-web-servlet-filter_tomcat HELPER com.datadoghq.agent.integration.Servlet2Helper
CLASS org.apache.catalina.core.ApplicationContext
METHOD <init>
COMPILE COMPILE
HELPER com.datadoghq.agent.integration.TomcatServletHelper
AT EXIT AT EXIT
IF TRUE IF TRUE
DO 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 ENDRULE

View File

@ -17,8 +17,6 @@ public class OperationDecorator extends AbstractDecorator {
// Component name <> Operation name // Component name <> Operation name
put("apache-httpclient", "apache.http"); put("apache-httpclient", "apache.http");
put("java-aws-sdk", "aws.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 // FIXME: JMS ops card is low (jms-send or jms-receive), may be this mapping is useless
put("java-jms", "jms"); put("java-jms", "jms");
put("okhttp", "okhttp.http"); put("okhttp", "okhttp.http");

View File

@ -15,13 +15,12 @@ include ':dd-java-agent:integrations:helpers'
include ':dd-java-agent:integrations:apache-httpclient' include ':dd-java-agent:integrations:apache-httpclient'
include ':dd-java-agent:integrations:aws-sdk' include ':dd-java-agent:integrations:aws-sdk'
include ':dd-java-agent:integrations:cassandra' include ':dd-java-agent:integrations:cassandra'
include ':dd-java-agent:integrations:jetty'
include ':dd-java-agent:integrations:jms' include ':dd-java-agent:integrations:jms'
include ':dd-java-agent:integrations:mongo' include ':dd-java-agent:integrations:mongo'
include ':dd-java-agent:integrations:mongo-async' include ':dd-java-agent:integrations:mongo-async'
include ':dd-java-agent:integrations:okhttp' include ':dd-java-agent:integrations:okhttp'
include ':dd-java-agent:integrations:tomcat' include ':dd-java-agent:integrations:servlet-2'
include ':dd-java-agent:integrations:tomcat-embedded' include ':dd-java-agent:integrations:servlet-3'
def setBuildFile(project) { def setBuildFile(project) {
project.buildFileName = "${project.name}.gradle" project.buildFileName = "${project.name}.gradle"