Merge pull request #135 from DataDog/tyler/servlet
Replace tomcat/jetty specific integ with generic servlet integ.
This commit is contained in:
commit
897b3426b1
26
README.md
26
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
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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 {}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
]
|
||||
}
|
|
@ -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,
|
||||
]
|
||||
}
|
|
@ -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,
|
||||
]
|
||||
}
|
|
@ -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,
|
||||
]
|
||||
}
|
|
@ -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,
|
||||
]
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -82,25 +82,13 @@ DO
|
|||
ENDRULE
|
||||
|
||||
|
||||
# Instrument Servlet - Jetty
|
||||
# Instrument Servlet
|
||||
# ===========================
|
||||
RULE ServletContextHandler-clinit
|
||||
CLASS org.eclipse.jetty.servlet.ServletContextHandler
|
||||
METHOD <clinit>
|
||||
RULE HttpServlet-init
|
||||
CLASS ^javax.servlet.http.HttpServlet
|
||||
METHOD <init>
|
||||
AT EXIT
|
||||
IF TRUE
|
||||
DO
|
||||
com.datadoghq.agent.InstrumentationRulesManager.registerClassLoad();
|
||||
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();
|
||||
com.datadoghq.agent.InstrumentationRulesManager.registerClassLoad($0);
|
||||
ENDRULE
|
||||
|
|
|
@ -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 <init>
|
||||
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 <init>
|
||||
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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue