diff --git a/dd-java-agent/instrumentation/jboss-modules/src/main/java/datadog/trace/instrumentation/jbossmodules/ModuleInstrumentation.java b/dd-java-agent/instrumentation/jboss-modules/src/main/java/datadog/trace/instrumentation/jbossmodules/ModuleInstrumentation.java index 825e212129e..235675a90f6 100644 --- a/dd-java-agent/instrumentation/jboss-modules/src/main/java/datadog/trace/instrumentation/jbossmodules/ModuleInstrumentation.java +++ b/dd-java-agent/instrumentation/jboss-modules/src/main/java/datadog/trace/instrumentation/jbossmodules/ModuleInstrumentation.java @@ -1,6 +1,7 @@ package datadog.trace.instrumentation.jbossmodules; import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; @@ -8,6 +9,7 @@ import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.api.naming.ClassloaderServiceNames; import datadog.trace.bootstrap.AgentClassLoading; import java.io.IOException; import java.io.InputStream; @@ -33,7 +35,8 @@ public String[] helperClassNames() { return new String[] { "org.jboss.modules.ModuleLinkageHelper", "org.jboss.modules.ModuleLinkageHelper$1", - "org.jboss.modules.ModuleLinkageHelper$2" + "org.jboss.modules.ModuleLinkageHelper$2", + packageName + ".ModuleNameHelper", }; } @@ -60,6 +63,7 @@ public void methodAdvice(MethodTransformer transformer) { .and(takesArgument(0, String.class)) .and(takesArgument(1, boolean.class)))), ModuleInstrumentation.class.getName() + "$WidenLoadClassAdvice"); + transformer.applyAdvice(isConstructor(), getClass().getName() + "$CaptureModuleNameAdvice"); } /** @@ -154,4 +158,14 @@ public static void onExit( } } } + + public static class CaptureModuleNameAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void afterConstruct(@Advice.This final Module module) { + final String name = ModuleNameHelper.extractDeploymentName(module.getClassLoader()); + if (name != null && !name.isEmpty()) { + ClassloaderServiceNames.addServiceName(module.getClassLoader(), name); + } + } + } } diff --git a/dd-java-agent/instrumentation/jboss-modules/src/main/java/datadog/trace/instrumentation/jbossmodules/ModuleNameHelper.java b/dd-java-agent/instrumentation/jboss-modules/src/main/java/datadog/trace/instrumentation/jbossmodules/ModuleNameHelper.java new file mode 100644 index 00000000000..d99f2ded925 --- /dev/null +++ b/dd-java-agent/instrumentation/jboss-modules/src/main/java/datadog/trace/instrumentation/jbossmodules/ModuleNameHelper.java @@ -0,0 +1,22 @@ +package datadog.trace.instrumentation.jbossmodules; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import org.jboss.modules.ModuleClassLoader; + +public class ModuleNameHelper { + private ModuleNameHelper() {} + + private static final Pattern SUBDEPLOYMENT_MATCH = + Pattern.compile("deployment(?>.+\\.ear)?\\.(.+)\\.[j|w]ar"); + + public static String extractDeploymentName(@Nonnull final ModuleClassLoader classLoader) { + final Matcher matcher = + SUBDEPLOYMENT_MATCH.matcher(classLoader.getModule().getIdentifier().getName()); + if (matcher.matches()) { + return matcher.group(1); + } + return null; + } +} diff --git a/dd-java-agent/instrumentation/liberty-20/src/main/java/datadog/trace/instrumentation/liberty20/BundleNameHelper.java b/dd-java-agent/instrumentation/liberty-20/src/main/java/datadog/trace/instrumentation/liberty20/BundleNameHelper.java new file mode 100644 index 00000000000..6acbed6647b --- /dev/null +++ b/dd-java-agent/instrumentation/liberty-20/src/main/java/datadog/trace/instrumentation/liberty20/BundleNameHelper.java @@ -0,0 +1,21 @@ +package datadog.trace.instrumentation.liberty20; + +import com.ibm.ws.classloading.internal.ThreadContextClassLoader; + +public class BundleNameHelper { + private BundleNameHelper() {} + + public static String extractDeploymentName(final ThreadContextClassLoader classLoader) { + final String id = classLoader.getKey(); + // id is something like :name#somethingelse + final int head = id.indexOf(':'); + if (head < 0) { + return null; + } + final int tail = id.lastIndexOf('#'); + if (tail <= head) { + return null; + } + return id.substring(head + 1, tail); + } +} diff --git a/dd-java-agent/instrumentation/liberty-20/src/main/java/datadog/trace/instrumentation/liberty20/LibertyServerInstrumentation.java b/dd-java-agent/instrumentation/liberty-20/src/main/java/datadog/trace/instrumentation/liberty20/LibertyServerInstrumentation.java index eac3a358f2a..02e4d1e797b 100644 --- a/dd-java-agent/instrumentation/liberty-20/src/main/java/datadog/trace/instrumentation/liberty20/LibertyServerInstrumentation.java +++ b/dd-java-agent/instrumentation/liberty-20/src/main/java/datadog/trace/instrumentation/liberty20/LibertyServerInstrumentation.java @@ -12,11 +12,15 @@ import com.google.auto.service.AutoService; import com.ibm.ws.webcontainer.srt.SRTServletRequest; import com.ibm.ws.webcontainer.srt.SRTServletResponse; +import com.ibm.ws.webcontainer.webapp.WebApp; +import com.ibm.wsspi.webcontainer.webapp.IWebAppDispatcherContext; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.api.Config; import datadog.trace.api.CorrelationIdentifier; import datadog.trace.api.GlobalTracer; import datadog.trace.api.gateway.Flow; +import datadog.trace.api.naming.ClassloaderServiceNames; import datadog.trace.bootstrap.ActiveSubsystems; import datadog.trace.bootstrap.ContextStore; import datadog.trace.bootstrap.InstrumentationContext; @@ -104,7 +108,18 @@ public static class HandleRequestAdvice { request.setAttribute(DD_EXTRACTED_CONTEXT_ATTRIBUTE, extractedContext); final AgentSpan span = DECORATE.startSpan(request, extractedContext); scope = activateSpan(span, true); - + if (Config.get().isJeeSplitByDeployment()) { + final IWebAppDispatcherContext dispatcherContext = request.getWebAppDispatcherContext(); + if (dispatcherContext != null) { + final WebApp webapp = dispatcherContext.getWebApp(); + if (webapp != null) { + final ClassLoader cl = webapp.getClassLoader(); + if (cl != null) { + ClassloaderServiceNames.maybeSetToSpan(span, cl); + } + } + } + } DECORATE.afterStart(span); DECORATE.onRequest(span, request, request, extractedContext); request.setAttribute(DD_SPAN_ATTRIBUTE, span); diff --git a/dd-java-agent/instrumentation/liberty-20/src/main/java/datadog/trace/instrumentation/liberty20/ThreadContextClassloaderInstrumentation.java b/dd-java-agent/instrumentation/liberty-20/src/main/java/datadog/trace/instrumentation/liberty20/ThreadContextClassloaderInstrumentation.java new file mode 100644 index 00000000000..3275e2abd45 --- /dev/null +++ b/dd-java-agent/instrumentation/liberty-20/src/main/java/datadog/trace/instrumentation/liberty20/ThreadContextClassloaderInstrumentation.java @@ -0,0 +1,47 @@ +package datadog.trace.instrumentation.liberty20; + +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; + +import com.google.auto.service.AutoService; +import com.ibm.ws.classloading.internal.ThreadContextClassLoader; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.api.naming.ClassloaderServiceNames; +import net.bytebuddy.asm.Advice; + +@AutoService(InstrumenterModule.class) +public class ThreadContextClassloaderInstrumentation extends InstrumenterModule.Tracing + implements Instrumenter.ForSingleType { + + public ThreadContextClassloaderInstrumentation() { + super("liberty", "liberty-classloading"); + } + + @Override + public String instrumentedType() { + return "com.ibm.ws.classloading.internal.ThreadContextClassLoader"; + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".BundleNameHelper", + }; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isConstructor(), getClass().getName() + "$ThreadContextClassloaderAdvice"); + } + + public static class ThreadContextClassloaderAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void afterConstruct(@Advice.This ThreadContextClassLoader self) { + final String name = BundleNameHelper.extractDeploymentName(self); + if (name != null && !name.isEmpty()) { + ClassloaderServiceNames.addServiceName(self, name); + } + } + } +} diff --git a/dd-java-agent/instrumentation/liberty-20/src/test/groovy/datadog/trace/instrumentation/liberty20/Liberty20Test.groovy b/dd-java-agent/instrumentation/liberty-20/src/test/groovy/datadog/trace/instrumentation/liberty20/Liberty20Test.groovy index 7e64aafba50..56c3ca15dc0 100644 --- a/dd-java-agent/instrumentation/liberty-20/src/test/groovy/datadog/trace/instrumentation/liberty20/Liberty20Test.groovy +++ b/dd-java-agent/instrumentation/liberty-20/src/test/groovy/datadog/trace/instrumentation/liberty20/Liberty20Test.groovy @@ -213,6 +213,20 @@ class Liberty20AsyncForkedTest extends Liberty20Test implements TestingGenericHt } } +@IgnoreIf({ + // failing because org.apache.xalan.transformer.TransformerImpl is + // instrumented while on the the global ignores list + System.getProperty('java.vm.name') == 'IBM J9 VM' && + System.getProperty('java.specification.version') == '1.8' }) +class LibertyServletClassloaderNamingForkedTest extends Liberty20V0ForkedTest { + @Override + protected void configurePreAgent() { + super.configurePreAgent() + // will not set the service name according to the servlet context value + injectSysConfig("trace.experimental.jee.split-by-deployment", "true") + } +} + @IgnoreIf({ // failing because org.apache.xalan.transformer.TransformerImpl is // instrumented while on the the global ignores list diff --git a/dd-java-agent/instrumentation/liberty-23/build.gradle b/dd-java-agent/instrumentation/liberty-23/build.gradle index 75e5b97f3d0..8d88ad12b93 100644 --- a/dd-java-agent/instrumentation/liberty-23/build.gradle +++ b/dd-java-agent/instrumentation/liberty-23/build.gradle @@ -34,6 +34,7 @@ dependencies { testImplementation testFixtures(project(':dd-java-agent:appsec')) testRuntimeOnly project(':dd-java-agent:instrumentation:osgi-4.3') testRuntimeOnly files({ tasks.filterLogbackClassic.filteredLogbackDir }) + testRuntimeOnly project(':dd-java-agent:instrumentation:liberty-20') testRuntimeOnly project(':dd-java-agent:instrumentation:servlet:request-5') testRuntimeOnly files({ tasks.shadowJar.archiveFile }) diff --git a/dd-java-agent/instrumentation/liberty-23/src/main/java/datadog/trace/instrumentation/liberty23/LibertyServerInstrumentation.java b/dd-java-agent/instrumentation/liberty-23/src/main/java/datadog/trace/instrumentation/liberty23/LibertyServerInstrumentation.java index 3209bbed508..8d0440eef3b 100644 --- a/dd-java-agent/instrumentation/liberty-23/src/main/java/datadog/trace/instrumentation/liberty23/LibertyServerInstrumentation.java +++ b/dd-java-agent/instrumentation/liberty-23/src/main/java/datadog/trace/instrumentation/liberty23/LibertyServerInstrumentation.java @@ -12,11 +12,15 @@ import com.google.auto.service.AutoService; import com.ibm.ws.webcontainer.srt.SRTServletRequest; import com.ibm.ws.webcontainer.srt.SRTServletResponse; +import com.ibm.ws.webcontainer.webapp.WebApp; +import com.ibm.wsspi.webcontainer.webapp.IWebAppDispatcherContext; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.api.Config; import datadog.trace.api.CorrelationIdentifier; import datadog.trace.api.GlobalTracer; import datadog.trace.api.gateway.Flow; +import datadog.trace.api.naming.ClassloaderServiceNames; import datadog.trace.bootstrap.ActiveSubsystems; import datadog.trace.bootstrap.ContextStore; import datadog.trace.bootstrap.InstrumentationContext; @@ -106,7 +110,18 @@ public static class HandleRequestAdvice { request.setAttribute(DD_EXTRACTED_CONTEXT_ATTRIBUTE, extractedContext); final AgentSpan span = DECORATE.startSpan(request, extractedContext); scope = activateSpan(span, true); - + if (Config.get().isJeeSplitByDeployment()) { + final IWebAppDispatcherContext dispatcherContext = request.getWebAppDispatcherContext(); + if (dispatcherContext != null) { + final WebApp webapp = dispatcherContext.getWebApp(); + if (webapp != null) { + final ClassLoader cl = webapp.getClassLoader(); + if (cl != null) { + ClassloaderServiceNames.maybeSetToSpan(span, cl); + } + } + } + } DECORATE.afterStart(span); DECORATE.onRequest(span, request, request, extractedContext); request.setAttribute(DD_SPAN_ATTRIBUTE, span); diff --git a/dd-java-agent/instrumentation/liberty-23/src/test/groovy/datadog/trace/instrumentation/liberty23/Liberty23Test.groovy b/dd-java-agent/instrumentation/liberty-23/src/test/groovy/datadog/trace/instrumentation/liberty23/Liberty23Test.groovy index 5ad1162f4ff..805e2dcfe97 100644 --- a/dd-java-agent/instrumentation/liberty-23/src/test/groovy/datadog/trace/instrumentation/liberty23/Liberty23Test.groovy +++ b/dd-java-agent/instrumentation/liberty-23/src/test/groovy/datadog/trace/instrumentation/liberty23/Liberty23Test.groovy @@ -170,3 +170,17 @@ class Liberty23V0ForkedTest extends Liberty23Test implements TestingGenericHttpN System.getProperty('java.specification.version') == '1.8' }) class Liberty23V1ForkedTest extends Liberty23Test implements TestingGenericHttpNamingConventions.ServerV1 { } + +@IgnoreIf({ + // failing because org.apache.xalan.transformer.TransformerImpl is + // instrumented while on the the global ignores list + System.getProperty('java.vm.name') == 'IBM J9 VM' && + System.getProperty('java.specification.version') == '1.8' }) +class LibertyServletClassloaderNamingForkedTest extends Liberty23V0ForkedTest { + @Override + protected void configurePreAgent() { + super.configurePreAgent() + // will not set the service name according to the servlet context value + injectSysConfig("trace.experimental.jee.split-by-deployment", "true") + } +} diff --git a/dd-java-agent/instrumentation/servlet/request-2/src/main/java/datadog/trace/instrumentation/servlet2/Servlet2Advice.java b/dd-java-agent/instrumentation/servlet/request-2/src/main/java/datadog/trace/instrumentation/servlet2/Servlet2Advice.java index 8f27ba741b0..5473d5d7a94 100644 --- a/dd-java-agent/instrumentation/servlet/request-2/src/main/java/datadog/trace/instrumentation/servlet2/Servlet2Advice.java +++ b/dd-java-agent/instrumentation/servlet/request-2/src/main/java/datadog/trace/instrumentation/servlet2/Servlet2Advice.java @@ -9,6 +9,7 @@ import datadog.trace.api.DDTags; import datadog.trace.api.GlobalTracer; import datadog.trace.api.gateway.Flow; +import datadog.trace.api.naming.ClassloaderServiceNames; import datadog.trace.bootstrap.InstrumentationContext; import datadog.trace.bootstrap.instrumentation.api.AgentScope; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; @@ -39,6 +40,8 @@ public static boolean onEnter( Object spanAttr = request.getAttribute(DD_SPAN_ATTRIBUTE); final boolean hasServletTrace = spanAttr instanceof AgentSpan; if (hasServletTrace) { + final AgentSpan span = (AgentSpan) spanAttr; + ClassloaderServiceNames.maybeSetToSpan(span); // Tracing might already be applied by the FilterChain or a parent request (forward/include). return false; } diff --git a/dd-java-agent/instrumentation/servlet/request-3/src/main/java/datadog/trace/instrumentation/servlet3/Servlet3Advice.java b/dd-java-agent/instrumentation/servlet/request-3/src/main/java/datadog/trace/instrumentation/servlet3/Servlet3Advice.java index 8171a5d5d4f..bc8ca7bb645 100644 --- a/dd-java-agent/instrumentation/servlet/request-3/src/main/java/datadog/trace/instrumentation/servlet3/Servlet3Advice.java +++ b/dd-java-agent/instrumentation/servlet/request-3/src/main/java/datadog/trace/instrumentation/servlet3/Servlet3Advice.java @@ -12,6 +12,7 @@ import datadog.trace.api.DDTags; import datadog.trace.api.GlobalTracer; import datadog.trace.api.gateway.Flow; +import datadog.trace.api.naming.ClassloaderServiceNames; import datadog.trace.bootstrap.instrumentation.api.AgentScope; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.instrumentation.servlet.ServletBlockingHelper; @@ -60,6 +61,8 @@ public static boolean onEnter( Object spanAttrValue = request.getAttribute(DD_SPAN_ATTRIBUTE); final boolean hasServletTrace = spanAttrValue instanceof AgentSpan; if (hasServletTrace) { + final AgentSpan span = (AgentSpan) spanAttrValue; + ClassloaderServiceNames.maybeSetToSpan(span); // Tracing might already be applied by other instrumentation, // the FilterChain or a parent request (forward/include). return false; diff --git a/dd-java-agent/instrumentation/servlet/request-3/src/main/java/datadog/trace/instrumentation/servlet3/Servlet3Decorator.java b/dd-java-agent/instrumentation/servlet/request-3/src/main/java/datadog/trace/instrumentation/servlet3/Servlet3Decorator.java index 8d5744e3590..9b1f179200e 100644 --- a/dd-java-agent/instrumentation/servlet/request-3/src/main/java/datadog/trace/instrumentation/servlet3/Servlet3Decorator.java +++ b/dd-java-agent/instrumentation/servlet/request-3/src/main/java/datadog/trace/instrumentation/servlet3/Servlet3Decorator.java @@ -1,5 +1,6 @@ package datadog.trace.instrumentation.servlet3; +import datadog.trace.api.naming.ClassloaderServiceNames; import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter; @@ -82,6 +83,7 @@ public AgentSpan onRequest( final HttpServletRequest request, AgentSpan.Context.Extracted context) { assert span != null; + ClassloaderServiceNames.maybeSetToSpan(span); if (request != null) { String contextPath = request.getContextPath(); String servletPath = request.getServletPath(); diff --git a/dd-java-agent/instrumentation/servlet/request-5/src/main/java/datadog/trace/instrumentation/servlet5/JakartaServletInstrumentation.java b/dd-java-agent/instrumentation/servlet/request-5/src/main/java/datadog/trace/instrumentation/servlet5/JakartaServletInstrumentation.java index b4d4084344f..01dc7225427 100644 --- a/dd-java-agent/instrumentation/servlet/request-5/src/main/java/datadog/trace/instrumentation/servlet5/JakartaServletInstrumentation.java +++ b/dd-java-agent/instrumentation/servlet/request-5/src/main/java/datadog/trace/instrumentation/servlet5/JakartaServletInstrumentation.java @@ -2,6 +2,7 @@ import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.hasSuperType; import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator.DD_SPAN_ATTRIBUTE; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; @@ -12,6 +13,7 @@ import datadog.trace.agent.tooling.InstrumenterModule; import datadog.trace.api.Config; import datadog.trace.api.DDTags; +import datadog.trace.api.naming.ClassloaderServiceNames; import datadog.trace.bootstrap.CallDepthThreadLocalMap; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import jakarta.servlet.ServletRequest; @@ -51,30 +53,33 @@ public void methodAdvice(MethodTransformer transformer) { public static class ExtractPrincipalAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) - public static boolean before(@Advice.Argument(0) final ServletRequest request) { + public static AgentSpan before(@Advice.Argument(0) final ServletRequest request) { if (!(request instanceof HttpServletRequest)) { - return false; + return null; } - return CallDepthThreadLocalMap.incrementCallDepth(HttpServletRequest.class) == 0; + Object span = request.getAttribute(DD_SPAN_ATTRIBUTE); + if (span instanceof AgentSpan + && CallDepthThreadLocalMap.incrementCallDepth(HttpServletRequest.class) == 0) { + final AgentSpan agentSpan = (AgentSpan) span; + ClassloaderServiceNames.maybeSetToSpan(agentSpan); + return agentSpan; + } + return null; } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void after( - @Advice.Enter boolean advice, @Advice.Argument(0) final ServletRequest request) { - if (advice) { - CallDepthThreadLocalMap.reset(HttpServletRequest.class); - final HttpServletRequest httpServletRequest = - (HttpServletRequest) request; // at this point the cast should be safe - if (Config.get().isServletPrincipalEnabled() - && httpServletRequest.getUserPrincipal() != null) { - Object span = - request.getAttribute( - "datadog.span"); // hardcode to avoid injecting HttpServiceDecorator just for this - if (span instanceof AgentSpan) { - ((AgentSpan) span) - .setTag(DDTags.USER_NAME, httpServletRequest.getUserPrincipal().getName()); - } - } + @Advice.Enter final AgentSpan span, @Advice.Argument(0) final ServletRequest request) { + if (span == null) { + return; + } + + CallDepthThreadLocalMap.reset(HttpServletRequest.class); + final HttpServletRequest httpServletRequest = + (HttpServletRequest) request; // at this point the cast should be safe + if (Config.get().isServletPrincipalEnabled() + && httpServletRequest.getUserPrincipal() != null) { + span.setTag(DDTags.USER_NAME, httpServletRequest.getUserPrincipal().getName()); } } } diff --git a/dd-java-agent/instrumentation/tomcat-5.5/build.gradle b/dd-java-agent/instrumentation/tomcat-5.5/build.gradle index fbee4c3d985..8ee94a412e3 100644 --- a/dd-java-agent/instrumentation/tomcat-5.5/build.gradle +++ b/dd-java-agent/instrumentation/tomcat-5.5/build.gradle @@ -98,6 +98,7 @@ dependencies { latest10TestImplementation(project(':dd-java-agent:instrumentation:tomcat-appsec-6')) latest10TestImplementation(project(':dd-java-agent:instrumentation:tomcat-appsec-7')) + latest10TestImplementation(project(':dd-java-agent:instrumentation:tomcat-classloading-9')) latestDepTestImplementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '11.+' latestDepTestImplementation group: 'org.apache.tomcat', name: 'jakartaee-migration', version: '1.+' @@ -105,6 +106,7 @@ dependencies { latestDepTestRuntimeOnly(project(':dd-java-agent:instrumentation:tomcat-appsec-6')) latestDepTestRuntimeOnly(project(':dd-java-agent:instrumentation:tomcat-appsec-7')) + latestDepTestRuntimeOnly(project(':dd-java-agent:instrumentation:tomcat-classloading-9')) } // Exclude all the dependencies from test for latestDepTest since the names are completely different. diff --git a/dd-java-agent/instrumentation/tomcat-5.5/src/latestDepTest/groovy/TomcatServletTest.groovy b/dd-java-agent/instrumentation/tomcat-5.5/src/latestDepTest/groovy/TomcatServletTest.groovy index 147ebacbd61..3569246fd0a 100644 --- a/dd-java-agent/instrumentation/tomcat-5.5/src/latestDepTest/groovy/TomcatServletTest.groovy +++ b/dd-java-agent/instrumentation/tomcat-5.5/src/latestDepTest/groovy/TomcatServletTest.groovy @@ -204,4 +204,13 @@ class TomcatServletTest extends AbstractServletTest { } } +class TomcatServletClassloaderNamingForkedTest extends TomcatServletTest { + @Override + protected void configurePreAgent() { + super.configurePreAgent() + // will not set the service name according to the servlet context value + injectSysConfig("trace.experimental.jee.split-by-deployment", "true") + } +} + diff --git a/dd-java-agent/instrumentation/tomcat-classloading-9/build.gradle b/dd-java-agent/instrumentation/tomcat-classloading-9/build.gradle new file mode 100644 index 00000000000..24c01c62e16 --- /dev/null +++ b/dd-java-agent/instrumentation/tomcat-classloading-9/build.gradle @@ -0,0 +1,17 @@ +evaluationDependsOn ':dd-java-agent:instrumentation:tomcat-5.5' + +muzzle { + pass { + group = 'org.apache.tomcat' + module = 'tomcat-catalina' + versions = '[9.0.0.M1,)' + } +} + +apply from: "$rootDir/gradle/java.gradle" + +dependencies { + compileOnly group: 'org.apache.tomcat', name: 'tomcat-catalina', version: '9.0.0.M1' +} + +// testing happens in tomcat-5.5 module diff --git a/dd-java-agent/instrumentation/tomcat-classloading-9/src/main/java/datadog/trace/instrumentation/tomcat9/WebappClassLoaderInstrumentation.java b/dd-java-agent/instrumentation/tomcat-classloading-9/src/main/java/datadog/trace/instrumentation/tomcat9/WebappClassLoaderInstrumentation.java new file mode 100644 index 00000000000..17199ce08fc --- /dev/null +++ b/dd-java-agent/instrumentation/tomcat-classloading-9/src/main/java/datadog/trace/instrumentation/tomcat9/WebappClassLoaderInstrumentation.java @@ -0,0 +1,48 @@ +package datadog.trace.instrumentation.tomcat9; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.api.naming.ClassloaderServiceNames; +import net.bytebuddy.asm.Advice; +import org.apache.catalina.Context; +import org.apache.catalina.WebResourceRoot; +import org.apache.catalina.loader.WebappClassLoaderBase; + +@AutoService(InstrumenterModule.class) +public class WebappClassLoaderInstrumentation extends InstrumenterModule.Tracing + implements Instrumenter.ForSingleType { + public WebappClassLoaderInstrumentation() { + super("tomcat", "tomcat-classloading"); + } + + @Override + public String instrumentedType() { + return "org.apache.catalina.loader.WebappClassLoaderBase"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod().and(named("setResources")), getClass().getName() + "$CaptureWebappNameAdvice"); + } + + public static class CaptureWebappNameAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onContextAvailable( + @Advice.This final WebappClassLoaderBase classLoader, + @Advice.Argument(0) final WebResourceRoot webResourceRoot) { + // at this moment we have the context set in this classloader, hence its name + final Context context = webResourceRoot.getContext(); + if (context != null) { + final String contextName = context.getBaseName(); + if (contextName != null && !contextName.isEmpty()) { + ClassloaderServiceNames.addServiceName(classLoader, contextName); + } + } + } + } +} diff --git a/dd-smoke-tests/wildfly/build.gradle b/dd-smoke-tests/wildfly/build.gradle index 970fac8aae2..d10e2ed4e73 100644 --- a/dd-smoke-tests/wildfly/build.gradle +++ b/dd-smoke-tests/wildfly/build.gradle @@ -1,15 +1,19 @@ ext { serverName = 'wildfly' - serverModule = 'servlet' + //serverModule = 'servlet' + serverModule = 'wildfly' serverVersion = '15.0.0.Final' serverExtension = 'zip' + maxJavaVersionForTests = JavaVersion.VERSION_11 } repositories { ivy { url 'https://download.jboss.org/' patternLayout { - artifact '/[organisation]/[revision]/[module]/[organisation]-[module]-[revision].[ext]' + // artifact '/[organisation]/[revision]/[module]/[organisation]-[module]-[revision].[ext]' + // we download the full EE profile and not the servlet minimal one + artifact '/[organisation]/[revision]/[organisation]-[revision].[ext]' metadataSources { artifact() } @@ -80,12 +84,12 @@ spotless { } } -def wildflyDir="${buildDir}/${serverName}-${serverModule}-${serverVersion}" +def wildflyDir="${buildDir}/${serverName}-${serverVersion}" tasks.register("unzip", Copy) { dependsOn tasks.earBuild mustRunAfter tasks.compileTestGroovy - def zipFileNamePrefix = "servlet" + def zipFileNamePrefix = "wildfly" def zipPath = project.configurations.serverFile.find { it.name.startsWith(zipFileNamePrefix) } diff --git a/dd-smoke-tests/wildfly/spring-ear/.gitignore b/dd-smoke-tests/wildfly/spring-ear/.gitignore index 1ec2c548b11..72d30a335be 100644 --- a/dd-smoke-tests/wildfly/spring-ear/.gitignore +++ b/dd-smoke-tests/wildfly/spring-ear/.gitignore @@ -1,5 +1,6 @@ # Ignore all project specific gradle directories/files .gradle +.idea gradle build gradlew diff --git a/dd-smoke-tests/wildfly/spring-ear/war/build.gradle b/dd-smoke-tests/wildfly/spring-ear/war/build.gradle index eb58e0085e6..2ebbfebc77b 100644 --- a/dd-smoke-tests/wildfly/spring-ear/war/build.gradle +++ b/dd-smoke-tests/wildfly/spring-ear/war/build.gradle @@ -7,4 +7,6 @@ repositories { dependencies { compileOnly 'org.springframework:spring-webmvc:5.3.0' + compileOnly group: 'javax', name: 'javaee-api', version: '8.0.1' + implementation group: 'com.datadoghq', name: 'dd-trace-api', version: '1.43.0' } diff --git a/dd-smoke-tests/wildfly/spring-ear/war/src/main/java/com/example/Common.java b/dd-smoke-tests/wildfly/spring-ear/war/src/main/java/com/example/Common.java new file mode 100644 index 00000000000..42cd6cc1ff7 --- /dev/null +++ b/dd-smoke-tests/wildfly/spring-ear/war/src/main/java/com/example/Common.java @@ -0,0 +1,8 @@ +package com.example; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class Common { + // for the sake of this example it avoids boilerplate ton inject an ejb into a spring context + public static final AtomicBoolean ENABLED = new AtomicBoolean(false); +} diff --git a/dd-smoke-tests/wildfly/spring-ear/war/src/main/java/com/example/ejb/ScheduledEjb.java b/dd-smoke-tests/wildfly/spring-ear/war/src/main/java/com/example/ejb/ScheduledEjb.java new file mode 100644 index 00000000000..0550a4e3b1e --- /dev/null +++ b/dd-smoke-tests/wildfly/spring-ear/war/src/main/java/com/example/ejb/ScheduledEjb.java @@ -0,0 +1,23 @@ +package com.example.ejb; + +import static com.example.Common.ENABLED; + +import datadog.trace.api.Trace; +import javax.ejb.Schedule; +import javax.ejb.Stateless; + +@Stateless +public class ScheduledEjb { + + @Schedule(second = "*/1", minute = "*", hour = "*") + public void runIt() { + if (ENABLED.getAndSet(false)) { + generateSomeTrace(); + } + } + + @Trace + private void generateSomeTrace() { + // empty + } +} diff --git a/dd-smoke-tests/wildfly/spring-ear/war/src/main/java/com/example/hello/HelloController.java b/dd-smoke-tests/wildfly/spring-ear/war/src/main/java/com/example/hello/HelloController.java index 891150cc10d..2592edc3ec8 100644 --- a/dd-smoke-tests/wildfly/spring-ear/war/src/main/java/com/example/hello/HelloController.java +++ b/dd-smoke-tests/wildfly/spring-ear/war/src/main/java/com/example/hello/HelloController.java @@ -1,5 +1,9 @@ package com.example.hello; +import static com.example.Common.ENABLED; + +import java.util.concurrent.CompletableFuture; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -10,4 +14,21 @@ public class HelloController { public String hello() { return "hello world"; } + + @RequestMapping("/enableScheduling") + public CompletableFuture> enableScheduling() { + ENABLED.set(true); + return CompletableFuture.supplyAsync( + () -> { + while (!ENABLED.get()) { + try { + Thread.sleep(200); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + return ResponseEntity.ok().build(); + }); + } } diff --git a/dd-smoke-tests/wildfly/spring-ear/war/src/main/webapp/WEB-INF/beans.xml b/dd-smoke-tests/wildfly/spring-ear/war/src/main/webapp/WEB-INF/beans.xml new file mode 100644 index 00000000000..f0ba505ab81 --- /dev/null +++ b/dd-smoke-tests/wildfly/spring-ear/war/src/main/webapp/WEB-INF/beans.xml @@ -0,0 +1,8 @@ + + + + diff --git a/dd-smoke-tests/wildfly/spring-ear/war/src/main/webapp/WEB-INF/web.xml b/dd-smoke-tests/wildfly/spring-ear/war/src/main/webapp/WEB-INF/web.xml index 36f2c845f1a..9d1fd42e198 100644 --- a/dd-smoke-tests/wildfly/spring-ear/war/src/main/webapp/WEB-INF/web.xml +++ b/dd-smoke-tests/wildfly/spring-ear/war/src/main/webapp/WEB-INF/web.xml @@ -8,6 +8,7 @@ dispatcher org.springframework.web.servlet.DispatcherServlet + true 1 diff --git a/dd-smoke-tests/wildfly/src/test/groovy/datadog/smoketest/WildflySmokeTest.groovy b/dd-smoke-tests/wildfly/src/test/groovy/datadog/smoketest/WildflySmokeTest.groovy index 5da6a415260..f6a3158c2f8 100644 --- a/dd-smoke-tests/wildfly/src/test/groovy/datadog/smoketest/WildflySmokeTest.groovy +++ b/dd-smoke-tests/wildfly/src/test/groovy/datadog/smoketest/WildflySmokeTest.groovy @@ -1,11 +1,13 @@ package datadog.smoketest +import datadog.trace.agent.test.utils.OkHttpUtils import datadog.trace.agent.test.utils.PortUtils -import datadog.trace.test.util.Flaky import okhttp3.Request import spock.lang.Shared +import spock.util.concurrent.PollingConditions + +import java.util.concurrent.atomic.AtomicInteger -@Flaky class WildflySmokeTest extends AbstractServerSmokeTest { @Shared @@ -24,12 +26,42 @@ class WildflySmokeTest extends AbstractServerSmokeTest { *defaultJavaProperties, "-Djboss.http.port=${httpPort}", "-Djboss.https.port=${httpsPort}", - "-Djboss.management.http.port=${managementPort}" + "-Djboss.management.http.port=${managementPort}", + "-Ddd.trace.experimental.jee.split-by-deployment=true", + "-Ddd.writer.type=MultiWriter:TraceStructureWriter:${output.getAbsolutePath()}:includeService,DDAgentWriter", ] - processBuilder.environment().put("JAVA_OPTS", javaOpts.collect({ it.replace(' ', '\\ ')}).join(' ')) + processBuilder.environment().put("JAVA_OPTS", javaOpts.collect({ it.replace(' ', '\\ ') }).join(' ')) return processBuilder } + @Override + File createTemporaryFile() { + def ret = File.createTempFile("trace-structure-docs", "out") + ret + } + + @Override + def inferServiceName() { + // do not set DD_SERVICE + false + } + + @Override + protected boolean isAcceptable(int processIndex, Map traceCounts) { + def hasServletRequestTraces = traceCounts.find { it.getKey() == "[war:servlet.request[war:spring.handler]]" }?.getValue()?.get() == 201 + def hasScheduledEjbTrace = traceCounts.find { it.getKey() == "[war:trace.annotation]" }?.getValue()?.get() == 1 + assert hasScheduledEjbTrace && hasServletRequestTraces: "Encountered traces: " + traceCounts + return true + } + + + def setupSpec() { + //wait for the deployment + new PollingConditions(timeout: 300, delay: 2).eventually { + assert OkHttpUtils.client().newCall(new Request.Builder().url("http://localhost:$httpPort/war/hello").build()).execute().code() == 200 + } + } + def cleanupSpec() { ProcessBuilder processBuilder = new ProcessBuilder( "${wildflyDirectory}/bin/jboss-cli.sh", @@ -41,9 +73,9 @@ class WildflySmokeTest extends AbstractServerSmokeTest { process.waitFor() } - def "default home page #n th time"() { + def "spring controller #n th time"() { setup: - String url = "http://localhost:$httpPort/" + String url = "http://localhost:$httpPort/war/hello" def request = new Request.Builder().url(url).get().build() when: @@ -52,26 +84,21 @@ class WildflySmokeTest extends AbstractServerSmokeTest { then: def responseBodyStr = response.body().string() responseBodyStr != null - responseBodyStr.contains("Your WildFly instance is running.") - response.body().contentType().toString().contains("text/html") + responseBodyStr.contentEquals("hello world") response.code() == 200 - where: n << (1..200) } - def "spring context loaded successfully"() { + def "scheduled ejb has right service name"() { setup: - String url = "http://localhost:$httpPort/war/hello" + String url = "http://localhost:$httpPort/war/enableScheduling" def request = new Request.Builder().url(url).get().build() when: def response = client.newCall(request).execute() then: - def responseBodyStr = response.body().string() - responseBodyStr != null - responseBodyStr.contentEquals("hello world") response.code() == 200 } } diff --git a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java index 9c12e7dda8c..c9ae677dfea 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java @@ -58,6 +58,7 @@ public final class ConfigDefaults { static final boolean DEFAULT_RUNTIME_CONTEXT_FIELD_INJECTION = true; static final boolean DEFAULT_SERIALVERSIONUID_FIELD_INJECTION = true; + static final boolean DEFAULT_EXPERIMENTATAL_JEE_SPLIT_BY_DEPLOYMENT = false; static final boolean DEFAULT_PRIORITY_SAMPLING_ENABLED = true; static final String DEFAULT_PRIORITY_SAMPLING_FORCE = null; static final boolean DEFAULT_TRACE_RESOLVER_ENABLED = true; diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java index 7fca0156733..a875c5da7ea 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java @@ -70,6 +70,9 @@ public final class TraceInstrumentationConfig { public static final String JDBC_CONNECTION_CLASS_NAME = "trace.jdbc.connection.class.name"; + public static final String EXPERIMENTATAL_JEE_SPLIT_BY_DEPLOYMENT = + "trace.experimental.jee.split-by-deployment"; + public static final String HTTP_URL_CONNECTION_CLASS_NAME = "trace.http.url.connection.class.name"; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java index 4be835039a2..7d93d13c2a6 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java @@ -40,6 +40,7 @@ import datadog.trace.api.interceptor.TraceInterceptor; import datadog.trace.api.internal.TraceSegment; import datadog.trace.api.metrics.SpanMetricRegistry; +import datadog.trace.api.naming.ClassloaderServiceNames; import datadog.trace.api.naming.SpanNaming; import datadog.trace.api.remoteconfig.ServiceNameCollector; import datadog.trace.api.sampling.PrioritySampling; @@ -1599,11 +1600,16 @@ private DDSpanContext buildSpanContext() { } if (serviceName == null) { serviceName = traceConfig.getPreferredServiceName(); - if (serviceName == null) { - // it could be on the initial snapshot but may be overridden to null and service name - // cannot be null - serviceName = CoreTracer.this.serviceName; - } + } + if (serviceName == null && parentServiceName == null) { + // in this case we have a local root without service name. We can try to see if we can find + // one from the thread context classloader + serviceName = ClassloaderServiceNames.maybeGetForCurrentThread(); + } + if (serviceName == null) { + // it could be on the initial snapshot but may be overridden to null and service name + // cannot be null + serviceName = CoreTracer.this.serviceName; } final CharSequence operationName = diff --git a/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java b/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java index 8229c5b42a9..56ae6da4c75 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java @@ -51,20 +51,23 @@ public class TagInterceptor { private final boolean shouldSet404ResourceName; private final boolean shouldSetUrlResourceAsName; + private final boolean jeeSplitByDeployment; public TagInterceptor(RuleFlags ruleFlags) { this( Config.get().isServiceNameSetByUser(), CapturedEnvironment.get().getProperties().get(GeneralConfig.SERVICE_NAME), Config.get().getSplitByTags(), - ruleFlags); + ruleFlags, + Config.get().isJeeSplitByDeployment()); } public TagInterceptor( boolean isServiceNameSetByUser, String inferredServiceName, Set splitServiceTags, - RuleFlags ruleFlags) { + RuleFlags ruleFlags, + boolean jeeSplitByDeployment) { this.isServiceNameSetByUser = isServiceNameSetByUser; this.inferredServiceName = inferredServiceName; this.splitServiceTags = splitServiceTags; @@ -76,6 +79,7 @@ public TagInterceptor( && ruleFlags.isEnabled(STATUS_404) && ruleFlags.isEnabled(STATUS_404_DECORATOR); shouldSetUrlResourceAsName = ruleFlags.isEnabled(URL_AS_RESOURCE_NAME); + this.jeeSplitByDeployment = jeeSplitByDeployment; } public boolean interceptTag(DDSpanContext span, String tag, Object value) { @@ -276,6 +280,7 @@ private boolean interceptServletContext(DDSpanContext span, Object value) { // so will always return false here. if (!splitByServletContext && (isServiceNameSetByUser + || jeeSplitByDeployment || !ruleFlags.isEnabled(RuleFlags.Feature.SERVLET_CONTEXT) || !span.getServiceName().isEmpty() && !span.getServiceName().equals(inferredServiceName) diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/taginterceptor/TagInterceptorTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/taginterceptor/TagInterceptorTest.groovy index b91cb2210b6..9156ce067bb 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/taginterceptor/TagInterceptorTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/taginterceptor/TagInterceptorTest.groovy @@ -1,8 +1,5 @@ package datadog.trace.core.taginterceptor -import datadog.trace.core.DDSpanContext -import datadog.trace.api.remoteconfig.ServiceNameCollector - import static datadog.trace.api.ConfigDefaults.DEFAULT_SERVICE_NAME import static datadog.trace.api.ConfigDefaults.DEFAULT_SERVLET_ROOT_CONTEXT_SERVICE_NAME import static datadog.trace.api.DDTags.ANALYTICS_SAMPLE_RATE @@ -12,6 +9,7 @@ import datadog.trace.api.DDSpanTypes import datadog.trace.api.DDTags import datadog.trace.api.config.GeneralConfig import datadog.trace.api.env.CapturedEnvironment +import datadog.trace.api.remoteconfig.ServiceNameCollector import datadog.trace.api.sampling.PrioritySampling import datadog.trace.bootstrap.instrumentation.api.AgentSpan import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags @@ -20,6 +18,7 @@ import datadog.trace.common.sampling.AllSampler import datadog.trace.common.writer.ListWriter import datadog.trace.common.writer.LoggingWriter import datadog.trace.core.CoreSpan +import datadog.trace.core.DDSpanContext import datadog.trace.core.test.DDCoreSpecification class TagInterceptorTest extends DDCoreSpecification { @@ -178,33 +177,34 @@ class TagInterceptorTest extends DDCoreSpecification { .sampler(new AllSampler()) // equivalent to split-by-tags: tag .tagInterceptor(new TagInterceptor(true, "my-service", - Collections.singleton(tag), new RuleFlags())) + Collections.singleton(tag), new RuleFlags(), false)) .build() } - def "split-by-tags for servlet.context"() { + def "split-by-tags for servlet.context and experimental jee split by deployment is #jeeActive"() { setup: - def tracer = createSplittingTracer(InstrumentationTags.SERVLET_CONTEXT) - - when: - def span = tracer.buildSpan("some span") - .withTag(InstrumentationTags.SERVLET_CONTEXT, "some-context") - .start() - span.finish() - - then: - span.serviceName == "some-context" - + def tracer = tracerBuilder() + .serviceName("my-service") + .writer(new LoggingWriter()) + .sampler(new AllSampler()) + .tagInterceptor(new TagInterceptor(false, "my-service", + Collections.emptySet(), new RuleFlags(), jeeActive)) + .build() when: - span = tracer.buildSpan("some span").start() + def span = tracer.buildSpan("some span").start() span.setTag(InstrumentationTags.SERVLET_CONTEXT, "some-context") span.finish() then: - span.serviceName == "some-context" + span.serviceName == expected cleanup: tracer.close() + + where: + expected | jeeActive + "some-context" | false + "my-service" | true } def "peer.service then split-by-tags via builder"() { @@ -694,13 +694,13 @@ class TagInterceptorTest extends DDCoreSpecification { tracer.close() } - void "when interceptServiceName extraServiceProvider is called"(){ + void "when interceptServiceName extraServiceProvider is called"() { setup: final extraServiceProvider = Mock(ServiceNameCollector) ServiceNameCollector.INSTANCE = extraServiceProvider final ruleFlags = Mock(RuleFlags) ruleFlags.isEnabled(_) >> true - final interceptor = new TagInterceptor(true, "my-service", Collections.singleton(DDTags.SERVICE_NAME), ruleFlags) + final interceptor = new TagInterceptor(true, "my-service", Collections.singleton(DDTags.SERVICE_NAME), ruleFlags, false) when: interceptor.interceptServiceName(null, Mock(DDSpanContext), "some-service") @@ -709,13 +709,13 @@ class TagInterceptorTest extends DDCoreSpecification { 1 * extraServiceProvider.addService("some-service") } - void "when interceptServletContext extraServiceProvider is called"(){ + void "when interceptServletContext extraServiceProvider is called"() { setup: final extraServiceProvider = Mock(ServiceNameCollector) ServiceNameCollector.INSTANCE = extraServiceProvider final ruleFlags = Mock(RuleFlags) ruleFlags.isEnabled(_) >> true - final interceptor = new TagInterceptor(true, "my-service", Collections.singleton("servlet.context"), ruleFlags) + final interceptor = new TagInterceptor(true, "my-service", Collections.singleton("servlet.context"), ruleFlags, false) when: interceptor.interceptServletContext(Mock(DDSpanContext), value) @@ -724,13 +724,13 @@ class TagInterceptorTest extends DDCoreSpecification { 1 * extraServiceProvider.addService(expected) where: - value | expected - "/" | "root-servlet" - "/test" | "test" - "test" | "test" + value | expected + "/" | "root-servlet" + "/test" | "test" + "test" | "test" } - void "When intercepts appsec propagation tag addAppsecPropagationTag is called"(){ + void "When intercepts appsec propagation tag addAppsecPropagationTag is called"() { setup: final ruleFlags = Mock(RuleFlags) ruleFlags.isEnabled(_) >> true diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 8b5a2cc40d8..7cf8b5ffe83 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -172,6 +172,7 @@ public static String getHostName() { private final boolean dbClientSplitByInstanceTypeSuffix; private final boolean dbClientSplitByHost; private final Set splitByTags; + private final boolean jeeSplitByDeployment; private final int scopeDepthLimit; private final boolean scopeStrictMode; private final int scopeIterationKeepAlive; @@ -848,6 +849,10 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins splitByTags = tryMakeImmutableSet(configProvider.getList(SPLIT_BY_TAGS)); + jeeSplitByDeployment = + configProvider.getBoolean( + EXPERIMENTATAL_JEE_SPLIT_BY_DEPLOYMENT, DEFAULT_EXPERIMENTATAL_JEE_SPLIT_BY_DEPLOYMENT); + springDataRepositoryInterfaceResourceName = configProvider.getBoolean(SPRING_DATA_REPOSITORY_INTERFACE_RESOURCE_NAME, true); @@ -2071,6 +2076,10 @@ public Set getSplitByTags() { return splitByTags; } + public boolean isJeeSplitByDeployment() { + return jeeSplitByDeployment; + } + public int getScopeDepthLimit() { return scopeDepthLimit; } @@ -4178,6 +4187,8 @@ public String toString() { + DBMPropagationMode + ", splitByTags=" + splitByTags + + ", jeeSplitByDeployment=" + + jeeSplitByDeployment + ", scopeDepthLimit=" + scopeDepthLimit + ", scopeStrictMode=" diff --git a/internal-api/src/main/java/datadog/trace/api/naming/ClassloaderServiceNames.java b/internal-api/src/main/java/datadog/trace/api/naming/ClassloaderServiceNames.java new file mode 100644 index 00000000000..c9d73f7df1d --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/api/naming/ClassloaderServiceNames.java @@ -0,0 +1,76 @@ +package datadog.trace.api.naming; + +import datadog.trace.api.Config; +import datadog.trace.api.config.GeneralConfig; +import datadog.trace.api.env.CapturedEnvironment; +import datadog.trace.api.remoteconfig.ServiceNameCollector; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import java.util.WeakHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ClassloaderServiceNames { + private static final boolean ENABLED = + Config.get().isJeeSplitByDeployment() && !Config.get().isServiceNameSetByUser(); + + private static class Lazy { + private static final ClassloaderServiceNames INSTANCE = new ClassloaderServiceNames(); + } + + private final WeakHashMap weakCache = new WeakHashMap<>(); + private final String inferredServiceName = + CapturedEnvironment.get().getProperties().get(GeneralConfig.SERVICE_NAME); + + private ClassloaderServiceNames() {} + + public static void addServiceName(@Nonnull ClassLoader classLoader, @Nonnull String serviceName) { + if (ENABLED) { + Lazy.INSTANCE.weakCache.put(classLoader, serviceName); + } + } + + @Nullable + public static String maybeGet(@Nonnull ClassLoader classLoader) { + if (ENABLED) { + return Lazy.INSTANCE.weakCache.get(classLoader); + } + return null; + } + + /** + * Fetches the service name linked to the current thread's context classloader. + * + * @return a nullable service name. + */ + @Nullable + public static String maybeGetForCurrentThread() { + return maybeGet(Thread.currentThread().getContextClassLoader()); + } + + /** + * Sets the service name to the provided spans according to the service name linked to the current + * thread's classloader. + * + * @param span a nonnull span + */ + public static void maybeSetToSpan(@Nonnull final AgentSpan span) { + maybeSetToSpan(span, Thread.currentThread().getContextClassLoader()); + } + + public static void maybeSetToSpan( + @Nonnull final AgentSpan span, @Nonnull final ClassLoader classLoader) { + if (!ENABLED) { + return; + } + final String currentServiceName = span.getServiceName(); + if (currentServiceName != null + && !currentServiceName.equals(Lazy.INSTANCE.inferredServiceName)) { + return; + } + final String service = maybeGet(classLoader); + if (service != null) { + span.setServiceName(service); + ServiceNameCollector.get().addService(service); + } + } +} diff --git a/internal-api/src/main/java/datadog/trace/api/naming/v0/MessagingNamingV0.java b/internal-api/src/main/java/datadog/trace/api/naming/v0/MessagingNamingV0.java index fe4b3bef3c3..89be91e3d7e 100644 --- a/internal-api/src/main/java/datadog/trace/api/naming/v0/MessagingNamingV0.java +++ b/internal-api/src/main/java/datadog/trace/api/naming/v0/MessagingNamingV0.java @@ -1,6 +1,7 @@ package datadog.trace.api.naming.v0; import datadog.trace.api.Config; +import datadog.trace.api.naming.ClassloaderServiceNames; import datadog.trace.api.naming.NamingSchema; import datadog.trace.api.remoteconfig.ServiceNameCollector; import javax.annotation.Nonnull; @@ -46,6 +47,11 @@ public String inboundService(@Nonnull final String messagingSystem, boolean useL ServiceNameCollector.get().addService(messagingSystem); return messagingSystem; } else { + final String contextual = ClassloaderServiceNames.maybeGetForCurrentThread(); + if (contextual != null) { + ServiceNameCollector.get().addService(contextual); + return contextual; + } return Config.get().getServiceName(); } } else { diff --git a/settings.gradle b/settings.gradle index 41c55a18d48..2d78784b023 100644 --- a/settings.gradle +++ b/settings.gradle @@ -481,6 +481,7 @@ include ':dd-java-agent:instrumentation:tomcat-5.5-common' include ':dd-java-agent:instrumentation:tomcat-appsec-5.5' include ':dd-java-agent:instrumentation:tomcat-appsec-6' include ':dd-java-agent:instrumentation:tomcat-appsec-7' +include ':dd-java-agent:instrumentation:tomcat-classloading-9' include ':dd-java-agent:instrumentation:trace-annotation' include ':dd-java-agent:instrumentation:twilio' include ':dd-java-agent:instrumentation:unbescape'