From e5924b3fe9e5c691bcf21e5a574e0b8129351ddc Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Mon, 6 Nov 2017 11:28:06 -0800 Subject: [PATCH] Migrate servlet instrumentation to byte buddy. --- dd-java-agent/dd-java-agent.gradle | 3 +- .../agent/integration/Servlet2Helper.java | 94 ----------- .../agent/integration/Servlet3Helper.java | 99 ------------ .../integrations/servlet-2/servlet-2.gradle | 15 ++ .../servlet2/HttpServlet2Instrumentation.java | 86 ++++++++++ .../integrations/servlet-3/servlet-3.gradle | 15 ++ .../servlet3/HttpServlet3Instrumentation.java | 148 ++++++++++++++++++ .../springweb/SpringWebInstrumentation.java | 6 +- .../src/main/resources/initializer-rules.btm | 12 -- .../src/main/resources/integration-rules.btm | 70 --------- .../dd/trace/ClassLoaderHasClassMatcher.java | 32 ++++ .../ClassLoaderHasClassWithFieldMatcher.java | 36 +++++ 12 files changed, 339 insertions(+), 277 deletions(-) delete mode 100644 dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet2Helper.java delete mode 100644 dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet3Helper.java create mode 100644 dd-java-agent/integrations/servlet-2/src/main/java/dd/inst/servlet2/HttpServlet2Instrumentation.java create mode 100644 dd-java-agent/integrations/servlet-3/src/main/java/dd/inst/servlet3/HttpServlet3Instrumentation.java create mode 100644 dd-java-agent/tooling/src/main/java/dd/trace/ClassLoaderHasClassMatcher.java create mode 100644 dd-java-agent/tooling/src/main/java/dd/trace/ClassLoaderHasClassWithFieldMatcher.java diff --git a/dd-java-agent/dd-java-agent.gradle b/dd-java-agent/dd-java-agent.gradle index f94a5671d5..d39f00b966 100644 --- a/dd-java-agent/dd-java-agent.gradle +++ b/dd-java-agent/dd-java-agent.gradle @@ -21,7 +21,8 @@ dependencies { compile project(':dd-java-agent:tooling') compile project(':dd-trace-annotations') - + compile project(':dd-java-agent:integrations:servlet-2') + compile project(':dd-java-agent:integrations:servlet-3') compile project(':dd-java-agent:integrations:spring-web') compile deps.bytebuddy 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 deleted file mode 100644 index 825476b8b7..0000000000 --- a/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet2Helper.java +++ /dev/null @@ -1,94 +0,0 @@ -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 java.util.Collections; -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(); - if (span != null) { - ServletFilterSpanDecorator.STANDARD_TAGS.onError(req, resp, ex, span); - span.log(Collections.singletonMap("error.object", ex)); - 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(); - if (span != null) { - 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 deleted file mode 100644 index a0c31b0905..0000000000 --- a/dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet3Helper.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.datadoghq.agent.integration; - -import io.opentracing.ActiveSpan; -import io.opentracing.contrib.web.servlet.filter.ServletFilterSpanDecorator; -import java.io.IOException; -import java.util.Collections; -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 (span != null) { - 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); - span.log(Collections.singletonMap("error.object", event.getThrowable())); - } - } - } - - @Override - public void onStartAsync(final AsyncEvent event) throws IOException {} - }); - } -} diff --git a/dd-java-agent/integrations/servlet-2/servlet-2.gradle b/dd-java-agent/integrations/servlet-2/servlet-2.gradle index afcb9bab92..ace3555004 100644 --- a/dd-java-agent/integrations/servlet-2/servlet-2.gradle +++ b/dd-java-agent/integrations/servlet-2/servlet-2.gradle @@ -9,3 +9,18 @@ versionScan { "javax.servlet.FilterChain" : null, ] } + +apply from: "${rootDir}/gradle/java.gradle" + +dependencies { + compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.3' + + compile project(':dd-trace') + compile project(':dd-java-agent:tooling') + + compile deps.bytebuddy + compile deps.opentracing + + compile group: 'io.opentracing.contrib', name: 'opentracing-web-servlet-filter', version: '0.0.9' + compile group: 'com.google.auto.service', name: 'auto-service', version: '1.0-rc3' +} diff --git a/dd-java-agent/integrations/servlet-2/src/main/java/dd/inst/servlet2/HttpServlet2Instrumentation.java b/dd-java-agent/integrations/servlet-2/src/main/java/dd/inst/servlet2/HttpServlet2Instrumentation.java new file mode 100644 index 0000000000..6dd5625136 --- /dev/null +++ b/dd-java-agent/integrations/servlet-2/src/main/java/dd/inst/servlet2/HttpServlet2Instrumentation.java @@ -0,0 +1,86 @@ +package dd.inst.servlet2; + +import static dd.trace.ClassLoaderHasClassMatcher.classLoaderHasClasses; +import static net.bytebuddy.matcher.ElementMatchers.isProtected; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import dd.trace.Instrumenter; +import io.opentracing.ActiveSpan; +import io.opentracing.SpanContext; +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 io.opentracing.util.GlobalTracer; +import java.util.Collections; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; + +@AutoService(Instrumenter.class) +public final class HttpServlet2Instrumentation implements Instrumenter { + public static final String SERVLET_OPERATION_NAME = "servlet.request"; + + @Override + public AgentBuilder instrument(final AgentBuilder agentBuilder) { + return agentBuilder + .type( + named("javax.servlet.http.HttpServlet"), + not(classLoaderHasClasses("javax.servlet.AsyncEvent", "javax.servlet.AsyncListener")) + .and( + classLoaderHasClasses( + "javax.servlet.ServletContextEvent", "javax.servlet.FilterChain"))) + .transform( + new AgentBuilder.Transformer.ForAdvice() + .advice( + named("service") + .and(takesArgument(0, named("javax.servlet.http.HttpServletRequest"))) + .and(takesArgument(1, named("javax.servlet.http.HttpServletResponse"))) + .and(isProtected()), + HttpServlet2Advice.class.getName())) + .asDecorator(); + } + + public static class HttpServlet2Advice { + + @Advice.OnMethodEnter + public static ActiveSpan startSpan(@Advice.Argument(0) final HttpServletRequest req) { + + final SpanContext extractedContext = + GlobalTracer.get() + .extract(Format.Builtin.HTTP_HEADERS, new HttpServletRequestExtractAdapter(req)); + + final ActiveSpan span = + GlobalTracer.get() + .buildSpan(SERVLET_OPERATION_NAME) + .asChildOf(extractedContext) + .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) + .startActive(); + + ServletFilterSpanDecorator.STANDARD_TAGS.onRequest(req, span); + return span; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class) + public static void stopSpan( + @Advice.Argument(0) final HttpServletRequest req, + @Advice.Argument(1) final HttpServletResponse resp, + @Advice.Enter final ActiveSpan span, + @Advice.Thrown final Throwable throwable) { + + if (span != null) { + if (throwable != null) { + ServletFilterSpanDecorator.STANDARD_TAGS.onError(req, resp, throwable, span); + span.log(Collections.singletonMap("error.object", throwable)); + } else { + ServletFilterSpanDecorator.STANDARD_TAGS.onResponse(req, resp, span); + } + span.deactivate(); + } + } + } +} diff --git a/dd-java-agent/integrations/servlet-3/servlet-3.gradle b/dd-java-agent/integrations/servlet-3/servlet-3.gradle index 0a9bf2d694..c5a91eb320 100644 --- a/dd-java-agent/integrations/servlet-3/servlet-3.gradle +++ b/dd-java-agent/integrations/servlet-3/servlet-3.gradle @@ -10,3 +10,18 @@ versionScan { "javax.servlet.AsyncListener": null, ] } + +apply from: "${rootDir}/gradle/java.gradle" + +dependencies { + compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.0.1' + + compile project(':dd-trace') + compile project(':dd-java-agent:tooling') + + compile deps.bytebuddy + compile deps.opentracing + + compile group: 'io.opentracing.contrib', name: 'opentracing-web-servlet-filter', version: '0.0.9' + compile group: 'com.google.auto.service', name: 'auto-service', version: '1.0-rc3' +} diff --git a/dd-java-agent/integrations/servlet-3/src/main/java/dd/inst/servlet3/HttpServlet3Instrumentation.java b/dd-java-agent/integrations/servlet-3/src/main/java/dd/inst/servlet3/HttpServlet3Instrumentation.java new file mode 100644 index 0000000000..182ae388ec --- /dev/null +++ b/dd-java-agent/integrations/servlet-3/src/main/java/dd/inst/servlet3/HttpServlet3Instrumentation.java @@ -0,0 +1,148 @@ +package dd.inst.servlet3; + +import static dd.trace.ClassLoaderHasClassMatcher.classLoaderHasClasses; +import static net.bytebuddy.matcher.ElementMatchers.isProtected; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import dd.trace.Instrumenter; +import io.opentracing.ActiveSpan; +import io.opentracing.SpanContext; +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 io.opentracing.util.GlobalTracer; +import java.io.IOException; +import java.util.Collections; +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 net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; + +@AutoService(Instrumenter.class) +public final class HttpServlet3Instrumentation implements Instrumenter { + public static final String SERVLET_OPERATION_NAME = "servlet.request"; + + @Override + public AgentBuilder instrument(final AgentBuilder agentBuilder) { + return agentBuilder + .type( + named("javax.servlet.http.HttpServlet"), + classLoaderHasClasses("javax.servlet.AsyncEvent", "javax.servlet.AsyncListener")) + .transform( + new AgentBuilder.Transformer.ForAdvice() + .advice( + named("service") + .and(takesArgument(0, named("javax.servlet.http.HttpServletRequest"))) + .and(takesArgument(1, named("javax.servlet.http.HttpServletResponse"))) + .and(isProtected()), + HttpServlet3Advice.class.getName())) + .asDecorator(); + } + + public static class HttpServlet3Advice { + + @Advice.OnMethodEnter + public static ActiveSpan startSpan(@Advice.Argument(0) final HttpServletRequest req) { + + final SpanContext extractedContext = + GlobalTracer.get() + .extract(Format.Builtin.HTTP_HEADERS, new HttpServletRequestExtractAdapter(req)); + + final ActiveSpan span = + GlobalTracer.get() + .buildSpan(SERVLET_OPERATION_NAME) + .asChildOf(extractedContext) + .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) + .startActive(); + + ServletFilterSpanDecorator.STANDARD_TAGS.onRequest(req, span); + return span; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class) + public static void stopSpan( + @Advice.Argument(0) final HttpServletRequest req, + @Advice.Argument(1) final HttpServletResponse resp, + @Advice.Enter final ActiveSpan span, + @Advice.Thrown final Throwable throwable) { + + if (span != null) { + if (throwable != null) { + ServletFilterSpanDecorator.STANDARD_TAGS.onError(req, resp, throwable, span); + span.log(Collections.singletonMap("error.object", throwable)); + } else if (req.isAsyncStarted()) { + 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 TagSettingAsyncListener(activated, cont, span)); + } else { + ServletFilterSpanDecorator.STANDARD_TAGS.onResponse(req, resp, span); + } + span.deactivate(); + } + } + + public static class TagSettingAsyncListener implements AsyncListener { + private final AtomicBoolean activated; + private final ActiveSpan.Continuation cont; + private final ActiveSpan span; + + public TagSettingAsyncListener( + final AtomicBoolean activated, + final ActiveSpan.Continuation cont, + final ActiveSpan span) { + this.activated = activated; + this.cont = cont; + this.span = span; + } + + @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 (event.getThrowable() != null && activated.compareAndSet(false, true)) { + try (ActiveSpan activeSpan = cont.activate()) { + ServletFilterSpanDecorator.STANDARD_TAGS.onError( + (HttpServletRequest) event.getSuppliedRequest(), + (HttpServletResponse) event.getSuppliedResponse(), + event.getThrowable(), + span); + span.log(Collections.singletonMap("error.object", event.getThrowable())); + } + } + } + + @Override + public void onStartAsync(final AsyncEvent event) throws IOException {} + } + } +} diff --git a/dd-java-agent/integrations/spring-web/src/main/java/dd/inst/springweb/SpringWebInstrumentation.java b/dd-java-agent/integrations/spring-web/src/main/java/dd/inst/springweb/SpringWebInstrumentation.java index 2e19e4bac4..77ebe81f15 100644 --- a/dd-java-agent/integrations/spring-web/src/main/java/dd/inst/springweb/SpringWebInstrumentation.java +++ b/dd-java-agent/integrations/spring-web/src/main/java/dd/inst/springweb/SpringWebInstrumentation.java @@ -1,5 +1,6 @@ package dd.inst.springweb; +import static dd.trace.ClassLoaderHasClassWithFieldMatcher.classLoaderHasClassWithField; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.isMethod; @@ -31,7 +32,10 @@ public final class SpringWebInstrumentation implements Instrumenter { return agentBuilder .type( not(isInterface()) - .and(hasSuperType(named("org.springframework.web.servlet.HandlerAdapter")))) + .and(hasSuperType(named("org.springframework.web.servlet.HandlerAdapter"))), + classLoaderHasClassWithField( + "org.springframework.web.servlet.HandlerMapping", + "BEST_MATCHING_PATTERN_ATTRIBUTE")) .transform( new AgentBuilder.Transformer.ForAdvice() .advice( diff --git a/dd-java-agent/src/main/resources/initializer-rules.btm b/dd-java-agent/src/main/resources/initializer-rules.btm index 8ef6b4ad53..6bcf01437f 100644 --- a/dd-java-agent/src/main/resources/initializer-rules.btm +++ b/dd-java-agent/src/main/resources/initializer-rules.btm @@ -80,15 +80,3 @@ IF TRUE DO com.datadoghq.agent.InstrumentationRulesManager.registerClassLoad($0); ENDRULE - - -# Instrument Servlet -# =========================== -RULE HttpServlet-init -CLASS ^javax.servlet.http.HttpServlet -METHOD -AT EXIT -IF TRUE -DO - 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 4ecd579026..bde4032cd5 100644 --- a/dd-java-agent/src/main/resources/integration-rules.btm +++ b/dd-java-agent/src/main/resources/integration-rules.btm @@ -106,73 +106,3 @@ IF TRUE DO patch($0) ENDRULE - -# Instrument Servlet 2 -# =========================== -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 - onRequest($1, $2) -ENDRULE - -RULE HttpServlet-2.service-exit -CLASS ^javax.servlet.http.HttpServlet -METHOD service(HttpServletRequest, HttpServletResponse) -HELPER com.datadoghq.agent.integration.Servlet2Helper -COMPILE -AT EXIT -IF TRUE -DO - 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/dd-java-agent/tooling/src/main/java/dd/trace/ClassLoaderHasClassMatcher.java b/dd-java-agent/tooling/src/main/java/dd/trace/ClassLoaderHasClassMatcher.java new file mode 100644 index 0000000000..88d6aa0d6d --- /dev/null +++ b/dd-java-agent/tooling/src/main/java/dd/trace/ClassLoaderHasClassMatcher.java @@ -0,0 +1,32 @@ +package dd.trace; + +import net.bytebuddy.matcher.ElementMatcher; + +public class ClassLoaderHasClassMatcher extends ElementMatcher.Junction.AbstractBase { + + private final String[] names; + + private ClassLoaderHasClassMatcher(final String... names) { + this.names = names; + } + + public static ElementMatcher.Junction.AbstractBase classLoaderHasClasses( + final String... names) { + return new ClassLoaderHasClassMatcher(names); + } + + @Override + public boolean matches(final ClassLoader target) { + try { + if (target != null) { + for (final String name : names) { + Class.forName(name, false, target); + } + return true; + } + return false; + } catch (final ClassNotFoundException e) { + return false; + } + } +} diff --git a/dd-java-agent/tooling/src/main/java/dd/trace/ClassLoaderHasClassWithFieldMatcher.java b/dd-java-agent/tooling/src/main/java/dd/trace/ClassLoaderHasClassWithFieldMatcher.java new file mode 100644 index 0000000000..b82e2768cd --- /dev/null +++ b/dd-java-agent/tooling/src/main/java/dd/trace/ClassLoaderHasClassWithFieldMatcher.java @@ -0,0 +1,36 @@ +package dd.trace; + +import net.bytebuddy.matcher.ElementMatcher; + +public class ClassLoaderHasClassWithFieldMatcher + extends ElementMatcher.Junction.AbstractBase { + + private final String className; + private final String fieldName; + + private ClassLoaderHasClassWithFieldMatcher(final String className, final String fieldName) { + this.className = className; + this.fieldName = fieldName; + } + + public static AbstractBase classLoaderHasClassWithField( + final String className, final String fieldName) { + return new ClassLoaderHasClassWithFieldMatcher(className, fieldName); + } + + @Override + public boolean matches(final ClassLoader target) { + try { + if (target != null) { + final Class aClass = Class.forName(className, false, target); + aClass.getDeclaredField(fieldName); + return true; + } + return false; + } catch (final ClassNotFoundException e) { + return false; + } catch (final NoSuchFieldException e) { + return false; + } + } +}