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 39c07bebdb0..da0545b0fb1 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 @@ -9,7 +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.api.ClassloaderConfigurationOverrides; import datadog.trace.bootstrap.AgentClassLoading; import java.io.IOException; import java.io.InputStream; @@ -164,7 +164,7 @@ public static class CaptureModuleNameAdvice { 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); + ClassloaderConfigurationOverrides.withPinnedServiceName(module.getClassLoader(), name); } } } 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 87960ac1940..c6fdef889ab 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 @@ -16,11 +16,11 @@ import com.ibm.wsspi.webcontainer.webapp.IWebAppDispatcherContext; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.api.ClassloaderConfigurationOverrides; 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; @@ -116,7 +116,7 @@ public static class HandleRequestAdvice { if (webapp != null) { final ClassLoader cl = webapp.getClassLoader(); if (cl != null) { - ClassloaderServiceNames.maybeSetToSpan(span, cl); + ClassloaderConfigurationOverrides.maybeEnrichSpan(span, cl); } } } 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 index bac66e2ef5a..9b71fda635c 100644 --- 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 @@ -6,7 +6,7 @@ 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 datadog.trace.api.ClassloaderConfigurationOverrides; import net.bytebuddy.asm.Advice; @AutoService(InstrumenterModule.class) @@ -40,7 +40,7 @@ public static class ThreadContextClassloaderAdvice { public static void afterConstruct(@Advice.This ThreadContextClassLoader self) { final String name = BundleNameHelper.extractDeploymentName(self); if (name != null && !name.isEmpty()) { - ClassloaderServiceNames.addServiceName(self, name); + ClassloaderConfigurationOverrides.withPinnedServiceName(self, name); } } } 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 9591f2d50e1..bc101255856 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 @@ -16,11 +16,11 @@ import com.ibm.wsspi.webcontainer.webapp.IWebAppDispatcherContext; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.api.ClassloaderConfigurationOverrides; 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; @@ -118,7 +118,7 @@ public static class HandleRequestAdvice { if (webapp != null) { final ClassLoader cl = webapp.getClassLoader(); if (cl != null) { - ClassloaderServiceNames.maybeSetToSpan(span, cl); + ClassloaderConfigurationOverrides.maybeEnrichSpan(span, cl); } } } 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 30feb834777..85570d6d7f8 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 @@ -4,12 +4,12 @@ import static datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator.DD_SPAN_ATTRIBUTE; import static datadog.trace.instrumentation.servlet2.Servlet2Decorator.DECORATE; +import datadog.trace.api.ClassloaderConfigurationOverrides; import datadog.trace.api.Config; import datadog.trace.api.CorrelationIdentifier; 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; @@ -42,7 +42,7 @@ public static boolean onEnter( final boolean hasServletTrace = spanAttr instanceof AgentSpan; if (hasServletTrace) { final AgentSpan span = (AgentSpan) spanAttr; - ClassloaderServiceNames.maybeSetToSpan(span); + ClassloaderConfigurationOverrides.maybeEnrichSpan(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 fa6018ae060..0bf2807e9f8 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 @@ -7,12 +7,12 @@ import static datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator.DD_SPAN_ATTRIBUTE; import static datadog.trace.instrumentation.servlet3.Servlet3Decorator.DECORATE; +import datadog.trace.api.ClassloaderConfigurationOverrides; import datadog.trace.api.Config; import datadog.trace.api.CorrelationIdentifier; 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.bootstrap.instrumentation.api.AgentSpanContext; @@ -63,7 +63,7 @@ public static boolean onEnter( final boolean hasServletTrace = spanAttrValue instanceof AgentSpan; if (hasServletTrace) { final AgentSpan span = (AgentSpan) spanAttrValue; - ClassloaderServiceNames.maybeSetToSpan(span); + ClassloaderConfigurationOverrides.maybeEnrichSpan(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 41964668659..f36907c76d9 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,6 +1,6 @@ package datadog.trace.instrumentation.servlet3; -import datadog.trace.api.naming.ClassloaderServiceNames; +import datadog.trace.api.ClassloaderConfigurationOverrides; import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; @@ -84,7 +84,7 @@ public AgentSpan onRequest( final HttpServletRequest request, AgentSpanContext.Extracted context) { assert span != null; - ClassloaderServiceNames.maybeSetToSpan(span); + ClassloaderConfigurationOverrides.maybeEnrichSpan(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 b038d43c78f..d60711b404c 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 @@ -11,9 +11,9 @@ import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.api.ClassloaderConfigurationOverrides; 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; @@ -61,7 +61,7 @@ public static AgentSpan before(@Advice.Argument(0) final ServletRequest request) if (span instanceof AgentSpan && CallDepthThreadLocalMap.incrementCallDepth(HttpServletRequest.class) == 0) { final AgentSpan agentSpan = (AgentSpan) span; - ClassloaderServiceNames.maybeSetToSpan(agentSpan); + ClassloaderConfigurationOverrides.maybeEnrichSpan(agentSpan); return agentSpan; } return null; diff --git a/dd-java-agent/instrumentation/tomcat-5.5/src/latestDepTest/groovy/TomcatServer.groovy b/dd-java-agent/instrumentation/tomcat-5.5/src/latestDepTest/groovy/TomcatServer.groovy index 0e419a7b3cd..0edb4a2f4a4 100644 --- a/dd-java-agent/instrumentation/tomcat-5.5/src/latestDepTest/groovy/TomcatServer.groovy +++ b/dd-java-agent/instrumentation/tomcat-5.5/src/latestDepTest/groovy/TomcatServer.groovy @@ -38,7 +38,6 @@ class TomcatServer implements HttpServer { return false } } - setupServlets(servletContext) (server.host as StandardHost).errorReportValveClass = TomcatServletTest.ErrorHandlerValve.name 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 3569246fd0a..5b3f05d15db 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 @@ -9,6 +9,7 @@ import org.apache.catalina.connector.Request import org.apache.catalina.connector.Response import org.apache.catalina.startup.Tomcat import org.apache.catalina.valves.ErrorReportValve +import org.apache.tomcat.util.descriptor.web.ContextEnvironment import org.apache.tomcat.util.descriptor.web.FilterDef import org.apache.tomcat.util.descriptor.web.FilterMap @@ -213,4 +214,32 @@ class TomcatServletClassloaderNamingForkedTest extends TomcatServletTest { } } +class TomcatServletEnvEntriesTagTest extends TomcatServletTest { + def addEntry (context, name, value) { + def envEntry = new ContextEnvironment() + envEntry.setName(name) + envEntry.setValue(value) + envEntry.setType("java.lang.String") + context.getNamingResources().addEnvironment(envEntry) + } + @Override + protected void setupServlets(Context context) { + super.setupServlets(context) + addEntry(context, "datadog/tags/custom-tag", "custom-value") + addEntry(context, "java:comp/env/datadog/tags/service", "custom-service") + } + + @Override + String expectedServiceName() { + "custom-service" + } + + @Override + Map expectedExtraServerTags(ServerEndpoint endpoint) { + super.expectedExtraServerTags(endpoint) + ["custom-tag": "custom-value"] as Map + } +} + + + diff --git a/dd-java-agent/instrumentation/tomcat-5.5/src/test/groovy/TomcatServletTest.groovy b/dd-java-agent/instrumentation/tomcat-5.5/src/test/groovy/TomcatServletTest.groovy index cc7f79e3309..c7f80b84a67 100644 --- a/dd-java-agent/instrumentation/tomcat-5.5/src/test/groovy/TomcatServletTest.groovy +++ b/dd-java-agent/instrumentation/tomcat-5.5/src/test/groovy/TomcatServletTest.groovy @@ -1,3 +1,11 @@ +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.CUSTOM_EXCEPTION +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.TIMEOUT_ERROR +import static org.junit.Assume.assumeTrue + import com.google.common.io.Files import datadog.trace.agent.test.asserts.TraceAssert import datadog.trace.agent.test.base.HttpServer @@ -18,14 +26,6 @@ import org.apache.coyote.http11.Http11BaseProtocol import javax.servlet.Servlet import javax.servlet.ServletException -import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.CUSTOM_EXCEPTION -import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR -import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION -import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND -import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT -import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.TIMEOUT_ERROR -import static org.junit.Assume.assumeTrue - abstract class TomcatServletTest extends AbstractServletTest { class TomcatServer implements HttpServer { @@ -187,7 +187,7 @@ abstract class TomcatServletTest extends AbstractServletTest childOfPrevious() tags { "component" "java-web-servlet-response" - if ({isDataStreamsEnabled()}) { + if ({ isDataStreamsEnabled() }) { "$DDTags.PATHWAY_HASH" { String } } defaultTags() @@ -227,8 +227,8 @@ abstract class TomcatServletTest extends AbstractServletTest if (endpoint.throwsException) { // Exception classes get wrapped in ServletException ["error.message": { endpoint == EXCEPTION ? "Servlet execution threw an exception" : it == endpoint.body }, - "error.type": { it == ServletException.name || it == InputMismatchException.name }, - "error.stack": String] + "error.type" : { it == ServletException.name || it == InputMismatchException.name }, + "error.stack" : String] } else { Collections.emptyMap() } 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 index 2980565bec8..ccd92cba35c 100644 --- 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 @@ -1,16 +1,21 @@ package datadog.trace.instrumentation.tomcat9; import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.api.ClassloaderConfigurationOverrides.DATADOG_TAGS_JNDI_PREFIX; +import static datadog.trace.api.ClassloaderConfigurationOverrides.DATADOG_TAGS_PREFIX; 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 datadog.trace.api.ClassloaderConfigurationOverrides; +import java.util.HashMap; +import java.util.Map; import net.bytebuddy.asm.Advice; import org.apache.catalina.Context; import org.apache.catalina.WebResourceRoot; import org.apache.catalina.loader.WebappClassLoaderBase; +import org.apache.tomcat.util.descriptor.web.ContextEnvironment; @AutoService(InstrumenterModule.class) public class WebappClassLoaderInstrumentation extends InstrumenterModule.Tracing @@ -37,10 +42,43 @@ public static void onContextAvailable( @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); + if (context == null) { + return; + } + ClassloaderConfigurationOverrides.ContextualInfo info = null; + + final String contextName = context.getBaseName(); + if (contextName != null && !contextName.isEmpty()) { + info = ClassloaderConfigurationOverrides.withPinnedServiceName(classLoader, contextName); + } + if (context.getNamingResources() != null) { + final ContextEnvironment[] envs = context.getNamingResources().findEnvironments(); + if (envs != null) { + final Map tags = new HashMap<>(); + for (final ContextEnvironment env : envs) { + // as a limitation here we simplify a lot the logic and we do not try to resolve the + // typed value but we just take the string representation. It avoids implementing the + // logic to convert to other types + // (i.e. long, double) or instrument more deeply tomcat naming + if (env.getValue() == null || env.getValue().isEmpty()) { + continue; + } + String name = null; + if (env.getName().startsWith(DATADOG_TAGS_PREFIX)) { + name = env.getName().substring(DATADOG_TAGS_PREFIX.length()); + } else if (env.getName().startsWith(DATADOG_TAGS_JNDI_PREFIX)) { + name = env.getName().substring(DATADOG_TAGS_JNDI_PREFIX.length()); + } + if (name != null && !name.isEmpty()) { + tags.put(name, env.getValue()); + } + } + if (!tags.isEmpty()) { + if (info == null) { + info = ClassloaderConfigurationOverrides.maybeCreateContextualInfo(classLoader); + } + tags.forEach(info::addTag); + } } } } diff --git a/dd-java-agent/instrumentation/wildfly-9/build.gradle b/dd-java-agent/instrumentation/wildfly-9/build.gradle new file mode 100644 index 00000000000..10d93694ef7 --- /dev/null +++ b/dd-java-agent/instrumentation/wildfly-9/build.gradle @@ -0,0 +1,193 @@ +ext { + minJavaVersionForTests = JavaVersion.VERSION_11 + latestDepTestMinJavaVersionForTests = JavaVersion.VERSION_17 + latestDepForkedTestMinJavaVersionForTests = JavaVersion.VERSION_17 +} + +repositories { + maven { + url 'https://maven.repository.redhat.com/ga/' + } + ivy { + url 'https://download.jboss.org/' + patternLayout { + artifact '/[organisation]/[revision]/[module]/[organisation]-[module]-[revision].[ext]' + metadataSources { + artifact() + } + } + } + ivy { + url 'https://github.com/wildfly' + patternLayout { + artifact '/[organisation]/releases/download/[revision]/[organisation]-[revision].[ext]' + metadataSources { + artifact() + } + } + } +} + +muzzle { + extraRepository('redhat-ga', 'https://maven.repository.redhat.com/ga/') + pass { + group = 'org.wildfly' + module = 'wildfly-ee' + versions = '[9.0.0.Final,)' + excludeDependency 'org.jboss.xnio:*' // not related and causes issues with missing jar in maven repo + } +} + +apply from: "$rootDir/gradle/java.gradle" + +addTestSuiteForDir("latestDepTest", "test") +addTestSuiteExtendingForDir("latestDepForkedTest", "latestDepTest", "test") + +configurations { + wildflyTest + wildflyLatestDepTest + wildflyLatestPoll { + canBeResolved = true + } +} + +dependencies { + compileOnly group: 'org.wildfly', name: 'wildfly-ee', version: '9.0.0.Final' + + testImplementation group: 'javax.servlet', name: 'javax.servlet-api', version: '3.0.1' + testImplementation group: 'jakarta.servlet', name: 'jakarta.servlet-api', version: '6.0.0' + + testImplementation group: 'org.wildfly.core', name: 'wildfly-embedded', version: '21.1.0.Final' + testImplementation group: 'org.wildfly.core', name: 'wildfly-server', version: '21.1.0.Final' + testImplementation group: 'org.jboss.shrinkwrap', name: 'shrinkwrap-api', version: '1.2.6' + + testRuntimeOnly project(':dd-java-agent:instrumentation:servlet:request-3') + testRuntimeOnly project(':dd-java-agent:instrumentation:jboss-modules') + testRuntimeOnly project(':dd-java-agent:instrumentation:undertow:undertow-2.0') + testRuntimeOnly project(':dd-java-agent:instrumentation:undertow:undertow-2.2') + testRuntimeOnly group: 'org.jboss.shrinkwrap', name: 'shrinkwrap-spi', version: '1.2.6' + testRuntimeOnly group: 'org.jboss.shrinkwrap', name: 'shrinkwrap-impl-base', version: '1.2.6' + + wildflyTest "wildfly:servlet:21.0.0.Final@zip" + + latestDepTestImplementation group: 'org.wildfly.core', name: 'wildfly-embedded', version: '+' + latestDepTestImplementation group: 'org.wildfly.core', name: 'wildfly-server', version: '+' + wildflyLatestPoll group: 'org.wildfly', name: 'wildfly-dist', version: '+' + + configurations.wildflyLatestPoll.resolve() + def latestWildflyVersion = configurations.wildflyLatestPoll.resolvedConfiguration.getResolvedArtifacts().find { + it.name == "wildfly-dist" + }.moduleVersion.id.version + wildflyLatestDepTest "wildfly:wildfly:$latestWildflyVersion@zip" + latestDepForkedTest { + configure { + jvmArgs += ["-Dtest.jboss.home=$buildDir/wildfly-${latestWildflyVersion}"] + } + } + + latestDepTestRuntimeOnly project(':dd-java-agent:instrumentation:servlet:request-5') +} + + +def extractWildfly(config, zipFileNamePrefix, sync) { + delete(fileTree(buildDir).include("wildfly-*/standalone/deployments/**")) + + def zipPath = config.find { + it.name.startsWith(zipFileNamePrefix) + } + if (zipPath != null) { + def zipFile = file(zipPath) + def outputDir = file("${buildDir}") + + sync.from zipTree(zipFile) + sync.into outputDir + } else { + throw new GradleException("Can't find server zip file that starts with: " + zipFileNamePrefix) + } +} + + +tasks.register("extractWildfly", Copy) { + dependsOn configurations.wildflyTest + mustRunAfter tasks.compileTestGroovy + extractWildfly(configurations.wildflyTest, "servlet", it) + + // When tests are disabled this would still be run, so disable this manually + onlyIf { !project.rootProject.hasProperty("skipTests") } +} + +tasks.register("extractLatestWildfly", Copy) { + dependsOn configurations.wildflyLatestDepTest + mustRunAfter tasks.compileLatestDepTestGroovy + mustRunAfter tasks.compileLatestDepForkedTestGroovy + mustRunAfter tasks.compileLatestDepTestJava + mustRunAfter tasks.compileLatestDepForkedTestJava + mustRunAfter tasks.compileJava + extractWildfly(configurations.wildflyLatestDepTest, "wildfly", it) + + // When tests are disabled this would still be run, so disable this manually + onlyIf { !project.rootProject.hasProperty("skipTests") } +} + + +tasks.named("test").configure { + dependsOn 'extractWildfly' +} + +tasks.named("forkedTest").configure { + dependsOn 'extractWildfly' +} +tasks.named("latestDepForkedTest").configure { + dependsOn 'extractLatestWildfly' +} + +tasks.named("latestDepTest").configure { + dependsOn 'extractLatestWildfly' +} +compileTestGroovy.configure { + javaLauncher = getJavaLauncherFor(11) +} + +[compileLatestDepTestGroovy, compileLatestDepForkedTestGroovy].each { + it.configure { + javaLauncher = getJavaLauncherFor(17) + } +} +compileTestJava.configure { + it.configure { + setJavaVersion(it, 11) + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +[compileLatestDepTestJava, compileLatestDepForkedTestJava].each { + it.configure { + setJavaVersion(it, 17) + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +processTestResources { + filesMatching('**/WEB-INF/web.xml') { + expand( + 'servletClass': 'test.TestServlet', + ) + } +} +[processLatestDepTestResources, processLatestDepForkedTestResources].each { + it.filesMatching('**/WEB-INF/web.xml') { + expand( + 'servletClass': 'test.JakartaTestServlet', + ) + } +} + +forkedTest { + configure { + jvmArgs += ["-Dtest.jboss.home=$buildDir/wildfly-servlet-21.0.0.Final"] + } +} + + diff --git a/dd-java-agent/instrumentation/wildfly-9/src/main/java/datadog/trace/instrumentation/wildfly/EnvEntryInjectionSourceInstrumentation.java b/dd-java-agent/instrumentation/wildfly-9/src/main/java/datadog/trace/instrumentation/wildfly/EnvEntryInjectionSourceInstrumentation.java new file mode 100644 index 00000000000..58c6a5275ac --- /dev/null +++ b/dd-java-agent/instrumentation/wildfly-9/src/main/java/datadog/trace/instrumentation/wildfly/EnvEntryInjectionSourceInstrumentation.java @@ -0,0 +1,44 @@ +package datadog.trace.instrumentation.wildfly; + +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.bootstrap.InstrumentationContext; +import java.util.Collections; +import java.util.Map; +import net.bytebuddy.asm.Advice; +import org.jboss.as.ee.component.EnvEntryInjectionSource; + +@AutoService(InstrumenterModule.class) +public class EnvEntryInjectionSourceInstrumentation extends InstrumenterModule.Tracing + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + public EnvEntryInjectionSourceInstrumentation() { + super("wildfly", "jee-env-entry"); + } + + @Override + public String instrumentedType() { + return "org.jboss.as.ee.component.EnvEntryInjectionSource"; + } + + @Override + public Map contextStore() { + return Collections.singletonMap( + "org.jboss.as.ee.component.EnvEntryInjectionSource", Object.class.getName()); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice(isConstructor(), getClass().getName() + "$ConstructorAdvice"); + } + + public static class ConstructorAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This final EnvEntryInjectionSource self, @Advice.Argument(0) final Object value) { + InstrumentationContext.get(EnvEntryInjectionSource.class, Object.class).put(self, value); + } + } +} diff --git a/dd-java-agent/instrumentation/wildfly-9/src/main/java/datadog/trace/instrumentation/wildfly/ResourceReferenceProcessorInstrumentation.java b/dd-java-agent/instrumentation/wildfly-9/src/main/java/datadog/trace/instrumentation/wildfly/ResourceReferenceProcessorInstrumentation.java new file mode 100644 index 00000000000..f716563c242 --- /dev/null +++ b/dd-java-agent/instrumentation/wildfly-9/src/main/java/datadog/trace/instrumentation/wildfly/ResourceReferenceProcessorInstrumentation.java @@ -0,0 +1,73 @@ +package datadog.trace.instrumentation.wildfly; + +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.ClassloaderConfigurationOverrides; +import datadog.trace.bootstrap.ContextStore; +import datadog.trace.bootstrap.InstrumentationContext; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import net.bytebuddy.asm.Advice; +import org.jboss.as.ee.component.BindingConfiguration; +import org.jboss.as.ee.component.EnvEntryInjectionSource; + +@AutoService(InstrumenterModule.class) +public class ResourceReferenceProcessorInstrumentation extends InstrumenterModule.Tracing + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + public ResourceReferenceProcessorInstrumentation() { + super("wildfly", "jee-env-entry"); + } + + @Override + public String instrumentedType() { + return "org.jboss.as.ee.component.deployers.ResourceReferenceProcessor"; + } + + @Override + public Map contextStore() { + return Collections.singletonMap( + "org.jboss.as.ee.component.EnvEntryInjectionSource", Object.class.getName()); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod().and(named("getEnvironmentEntries")), + getClass().getName() + "$GetEnvironmentEntriesAdvice"); + } + + public static class GetEnvironmentEntriesAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.Argument(value = 1) final ClassLoader classLoader, + @Advice.Return final List configurations) { + ClassloaderConfigurationOverrides.ContextualInfo info = null; + ContextStore contextStore = + InstrumentationContext.get(EnvEntryInjectionSource.class, Object.class); + for (BindingConfiguration bindingConfiguration : configurations) { + if (bindingConfiguration.getSource() instanceof EnvEntryInjectionSource) { + final Object value = + contextStore.get((EnvEntryInjectionSource) bindingConfiguration.getSource()); + if (value != null + && bindingConfiguration + .getName() + .startsWith(ClassloaderConfigurationOverrides.DATADOG_TAGS_JNDI_PREFIX)) { + if (info == null) { + info = ClassloaderConfigurationOverrides.maybeCreateContextualInfo(classLoader); + } + info.addTag( + bindingConfiguration + .getName() + .substring(ClassloaderConfigurationOverrides.DATADOG_TAGS_JNDI_PREFIX.length()), + value); + } + } + } + } + } +} diff --git a/dd-java-agent/instrumentation/wildfly-9/src/test/groovy/EmbeddedWildfly.groovy b/dd-java-agent/instrumentation/wildfly-9/src/test/groovy/EmbeddedWildfly.groovy new file mode 100644 index 00000000000..c4e9f78d56a --- /dev/null +++ b/dd-java-agent/instrumentation/wildfly-9/src/test/groovy/EmbeddedWildfly.groovy @@ -0,0 +1,57 @@ +import datadog.trace.agent.test.utils.OkHttpUtils +import datadog.trace.agent.test.utils.PortUtils +import okhttp3.HttpUrl +import okhttp3.Request +import org.jboss.shrinkwrap.api.exporter.ExplodedExporter +import org.jboss.shrinkwrap.api.spec.WebArchive +import org.wildfly.core.embedded.Configuration +import org.wildfly.core.embedded.EmbeddedProcessFactory +import org.wildfly.core.embedded.StandaloneServer +import spock.util.concurrent.PollingConditions + +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.util.concurrent.TimeUnit + +class EmbeddedWildfly { + + private final StandaloneServer server + private final Path home + private final int port + + EmbeddedWildfly(String jbossHome, int httpPort) { + home = Paths.get(jbossHome) + port = httpPort + def conf = Configuration.Builder + .of(home) + .addCommandArgument("-Djboss.management.http.port=0") + .addCommandArgument("-Djboss.management.https.port=0") + .addCommandArgument("-Djboss.ajp.port=0") + .addCommandArgument("-Djboss.http.port=$httpPort") + .addCommandArgument("-Djboss.https.port=0") + .addSystemPackages("datadog", "groovy") + .build() + server = EmbeddedProcessFactory.createStandaloneServer(conf) + } + + void start() { + server.start() + PortUtils.waitForPortToOpen(port, 30, TimeUnit.SECONDS) + } + + void stop() { + server.stop() + } + + void deploy(WebArchive webArchive) { + def deploymentPath = home.resolve("standalone/deployments") + webArchive.as(ExplodedExporter).exportExploded(deploymentPath.toFile(), "test.war") + Files.createFile(deploymentPath.resolve("test.war.dodeploy")) + new PollingConditions(timeout: 30, delay: 2).eventually { + def request = new Request.Builder().url(HttpUrl.get("http://localhost:$port/test")).build() + def call = OkHttpUtils.client().newCall(request) + assert call.execute().code() == 200 + } + } +} diff --git a/dd-java-agent/instrumentation/wildfly-9/src/test/groovy/WildFlyForkedTest.groovy b/dd-java-agent/instrumentation/wildfly-9/src/test/groovy/WildFlyForkedTest.groovy new file mode 100644 index 00000000000..969d831b5be --- /dev/null +++ b/dd-java-agent/instrumentation/wildfly-9/src/test/groovy/WildFlyForkedTest.groovy @@ -0,0 +1,81 @@ +import datadog.trace.agent.test.base.WithHttpServer +import datadog.trace.agent.test.naming.TestingGenericHttpNamingConventions +import datadog.trace.agent.test.utils.OkHttpUtils +import datadog.trace.api.DDSpanTypes +import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags +import datadog.trace.bootstrap.instrumentation.api.Tags +import okhttp3.HttpUrl +import okhttp3.Request +import org.jboss.shrinkwrap.api.ShrinkWrap +import org.jboss.shrinkwrap.api.spec.WebArchive +import test.JakartaTestServlet +import test.TestServlet + +class WildFlyForkedTest extends WithHttpServer implements TestingGenericHttpNamingConventions.ServerV0 { + @Override + EmbeddedWildfly startServer(int port) { + // create the archive + def war = ShrinkWrap.create(WebArchive) + war.setWebXML(getClass().getResource("/WEB-INF/web.xml")) + .addClass(TestServlet) // for wildfly 21 (EE 8) + .addClass(JakartaTestServlet) // for latestDep + + def server = new EmbeddedWildfly(System.getProperty("test.jboss.home"), port) + server.start() + server.deploy(war) + server + } + + @Override + void stopServer(EmbeddedWildfly embeddedWildfly) { + embeddedWildfly?.stop() + } + + @Override + protected void configurePreAgent() { + super.configurePreAgent() + // otherwise there are differences in setting the resource name across wildfly versions + injectSysConfig("undertow.legacy.tracing.enabled", "false") + } + + def "should have service name and tags set"() { + setup: + TEST_WRITER.clear() + when: + def request = new Request.Builder().url(HttpUrl.get(server.address()).resolve("/test/hello")).build() + def call = OkHttpUtils.client().newCall(request) + def response = call.execute() + then: + assert response.code() == 200 + assertTraces(1, { + trace(1) { + span { + serviceName "custom-service" + operationName "servlet.request" + resourceName "GET /test/hello" + spanType DDSpanTypes.HTTP_SERVER + errored false + parent() + + tags { + "$Tags.COMPONENT" "undertow-http-server" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER + "$Tags.PEER_PORT" Integer + "$Tags.PEER_HOST_IPV4" { String } + "$Tags.HTTP_CLIENT_IP" { String } + "$Tags.HTTP_HOSTNAME" address.host + "$Tags.HTTP_URL" { String } + "$Tags.HTTP_METHOD" "GET" + "$Tags.HTTP_STATUS" 200 + "$Tags.HTTP_USER_AGENT" String + "$Tags.HTTP_ROUTE" { true } + "$InstrumentationTags.SERVLET_CONTEXT" "/test" + "$InstrumentationTags.SERVLET_PATH" "/hello" + "custom-metric" 1983 + defaultTags(true) + } + } + } + }) + } +} diff --git a/dd-java-agent/instrumentation/wildfly-9/src/test/java/test/JakartaTestServlet.java b/dd-java-agent/instrumentation/wildfly-9/src/test/java/test/JakartaTestServlet.java new file mode 100644 index 00000000000..2af90032412 --- /dev/null +++ b/dd-java-agent/instrumentation/wildfly-9/src/test/java/test/JakartaTestServlet.java @@ -0,0 +1,12 @@ +package test; + +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class JakartaTestServlet extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) { + resp.setStatus(HttpServletResponse.SC_OK); + } +} diff --git a/dd-java-agent/instrumentation/wildfly-9/src/test/java/test/ModulePatchInstrumentation.java b/dd-java-agent/instrumentation/wildfly-9/src/test/java/test/ModulePatchInstrumentation.java new file mode 100644 index 00000000000..84095fa4762 --- /dev/null +++ b/dd-java-agent/instrumentation/wildfly-9/src/test/java/test/ModulePatchInstrumentation.java @@ -0,0 +1,49 @@ +package test; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import net.bytebuddy.asm.Advice; +import org.jboss.modules.Module; + +/** + * This instrumentation is to hack the way the jboss module classloader is loading SPI services. In + * fact, in test we have a classloader different from the one usually used when launching wildfly. + * In particular, we do not want to have SPI load services defined outside the jboss classloader + * module, otherwise this class won't be found afterwards. + */ +@AutoService(InstrumenterModule.class) +public class ModulePatchInstrumentation extends InstrumenterModule.Tracing + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + public ModulePatchInstrumentation() { + super("jboss-module-patch"); + } + + @Override + public String instrumentedType() { + return "org.jboss.modules.Module"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice(named("getResources"), getClass().getName() + "$SystemResourcesAdvice"); + } + + public static class SystemResourcesAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This Module self, + @Advice.Argument(0) String name, + @Advice.Return(readOnly = false) Enumeration ret) { + if (self.getName().endsWith(".jdk")) { + ret = Collections.emptyEnumeration(); + } + } + } +} diff --git a/dd-java-agent/instrumentation/wildfly-9/src/test/java/test/TestServlet.java b/dd-java-agent/instrumentation/wildfly-9/src/test/java/test/TestServlet.java new file mode 100644 index 00000000000..551fa4baf4b --- /dev/null +++ b/dd-java-agent/instrumentation/wildfly-9/src/test/java/test/TestServlet.java @@ -0,0 +1,12 @@ +package test; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class TestServlet extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) { + resp.setStatus(HttpServletResponse.SC_OK); + } +} diff --git a/dd-java-agent/instrumentation/wildfly-9/src/test/resources/WEB-INF/web.xml b/dd-java-agent/instrumentation/wildfly-9/src/test/resources/WEB-INF/web.xml new file mode 100644 index 00000000000..bc4afc2b00a --- /dev/null +++ b/dd-java-agent/instrumentation/wildfly-9/src/test/resources/WEB-INF/web.xml @@ -0,0 +1,24 @@ + + + + + test + ${servletClass} + + + + test + /* + + + + java:comp/env/datadog/tags/service + java.lang.String + custom-service + + + java:comp/env/datadog/tags/custom-metric + java.lang.Integer + 1983 + + diff --git a/dd-trace-core/src/jmh/java/datadog/trace/core/CoreTracerClassloaderNamingBenchmark.java b/dd-trace-core/src/jmh/java/datadog/trace/core/CoreTracerClassloaderNamingBenchmark.java new file mode 100644 index 00000000000..cc68ddf847f --- /dev/null +++ b/dd-trace-core/src/jmh/java/datadog/trace/core/CoreTracerClassloaderNamingBenchmark.java @@ -0,0 +1,62 @@ +package datadog.trace.core; + +import static java.util.concurrent.TimeUnit.MICROSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import java.util.WeakHashMap; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +@State(Scope.Benchmark) +@Warmup(iterations = 1, time = 15, timeUnit = SECONDS) +@Measurement(iterations = 3, time = 30, timeUnit = SECONDS) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(MICROSECONDS) +@Fork(value = 1) +/** + * Benchmark (simulateOverhead) Mode Cnt Score Error Units + * CoreTracerClassloaderNamingBenchmark.benchSpanCreation false avgt 3 0.747 ± 0.040 us/op + * CoreTracerClassloaderNamingBenchmark.benchSpanCreation true avgt 3 0.695 ± 0.106 us/op + */ +public class CoreTracerClassloaderNamingBenchmark { + CoreTracer tracer; + + WeakHashMap weakCache; + + @Param({"false", "true"}) + boolean simulateOverhead; + + @Setup(Level.Iteration) + public void init(Blackhole blackhole) { + tracer = + CoreTracer.builder() + .writer(new BlackholeWriter(blackhole, new TraceCounters(), 0)) + .strictTraceWrites(false) + .build(); + weakCache = new WeakHashMap<>(); + weakCache.put(Thread.currentThread().getContextClassLoader(), "test"); + } + + @Benchmark + public void benchSpanCreation(Blackhole blackhole) { + final AgentSpan span = tracer.startSpan("", ""); + if (simulateOverhead) { + // simulates an extra getContextClassLoader + a WeakHashMap.get + weakCache.get(Thread.currentThread().getContextClassLoader()); + } + span.finish(); + blackhole.consume(span); + } +} 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 437c397883b..29208628624 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 @@ -18,6 +18,7 @@ import datadog.communication.ddagent.SharedCommunicationObjects; import datadog.communication.monitor.Monitoring; import datadog.communication.monitor.Recording; +import datadog.trace.api.ClassloaderConfigurationOverrides; import datadog.trace.api.Config; import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; @@ -40,7 +41,6 @@ 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; @@ -139,8 +139,10 @@ public static CoreTracerBuilder builder() { /** Tracer start time in nanoseconds measured up to a millisecond accuracy */ private final long startTimeNano; + /** Nanosecond ticks value at tracer start */ private final long startNanoTicks; + /** How often should traced threads check clock ticks against the wall clock */ private final long clockSyncPeriod; @@ -149,6 +151,7 @@ public static CoreTracerBuilder builder() { /** Last time (in nanosecond ticks) the clock was checked for drift */ private volatile long lastSyncTicks; + /** Nanosecond offset to counter clock drift */ private volatile long counterDrift; @@ -160,10 +163,13 @@ public static CoreTracerBuilder builder() { /** Default service name if none provided on the trace or span */ final String serviceName; + /** Writer is an charge of reporting traces and spans to the desired endpoint */ final Writer writer; + /** Sampler defines the sampling policy in order to reduce the number of traces for instance */ final Sampler initialSampler; + /** Scope manager is in charge of managing the scopes from which spans are created */ final ContinuableScopeManager scopeManager; @@ -171,10 +177,13 @@ public static CoreTracerBuilder builder() { /** Initial static configuration associated with the tracer. */ final Config initialConfig; + /** Maintains dynamic configuration associated with the tracer */ private final DynamicConfig dynamicConfig; + /** A set of tags that are added only to the application's root span */ private final Map localRootSpanTags; + /** A set of tags that are added to every span */ private final Map defaultSpanTags; @@ -1589,10 +1598,19 @@ private DDSpanContext buildSpanContext() { if (serviceName == null) { serviceName = traceConfig.getPreferredServiceName(); } - 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(); + Map contextualTags = null; + if (parentServiceName == null) { + // only fetch this on local root spans + final ClassloaderConfigurationOverrides.ContextualInfo contextualInfo = + ClassloaderConfigurationOverrides.maybeGetContextualInfo(); + if (contextualInfo != 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 + if (serviceName == null) { + serviceName = contextualInfo.getServiceName(); + } + contextualTags = contextualInfo.getTags(); + } } if (serviceName == null) { // it could be on the initial snapshot but may be overridden to null and service name @@ -1609,7 +1627,8 @@ private DDSpanContext buildSpanContext() { mergedTracerTags.size() + (null == tags ? 0 : tags.size()) + (null == coreTags ? 0 : coreTags.size()) - + (null == rootSpanTags ? 0 : rootSpanTags.size()); + + (null == rootSpanTags ? 0 : rootSpanTags.size()) + + (null == contextualTags ? 0 : contextualTags.size()); if (builderRequestContextDataAppSec != null) { requestContextDataAppSec = builderRequestContextDataAppSec; @@ -1655,6 +1674,9 @@ private DDSpanContext buildSpanContext() { context.setAllTags(tags); context.setAllTags(coreTags); context.setAllTags(rootSpanTags); + if (contextualTags != null) { + context.setAllTags(contextualTags); + } return context; } } diff --git a/internal-api/build.gradle b/internal-api/build.gradle index dea10d40519..7b05c6989d7 100644 --- a/internal-api/build.gradle +++ b/internal-api/build.gradle @@ -195,6 +195,7 @@ excludedClassesCoverage += [ ] excludedClassesBranchCoverage = [ 'datadog.trace.api.ProductActivationConfig', + 'datadog.trace.api.ClassloaderConfigurationOverrides.Lazy', 'datadog.trace.api.Config', 'datadog.trace.util.stacktrace.HotSpotStackWalker', 'datadog.trace.util.stacktrace.StackWalkerFactory' diff --git a/internal-api/src/main/java/datadog/trace/api/ClassloaderConfigurationOverrides.java b/internal-api/src/main/java/datadog/trace/api/ClassloaderConfigurationOverrides.java new file mode 100644 index 00000000000..cfcff9a10b0 --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/api/ClassloaderConfigurationOverrides.java @@ -0,0 +1,144 @@ +package datadog.trace.api; + +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.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ClassloaderConfigurationOverrides { + public static final String DATADOG_TAGS_PREFIX = "datadog/tags/"; + public static final String DATADOG_TAGS_JNDI_PREFIX = "java:comp/env/" + DATADOG_TAGS_PREFIX; + // not final for testing purposes + static boolean CAN_SPLIT_SERVICE_NAME_BY_DEPLOYMENT = + Config.get().isJeeSplitByDeployment() && !Config.get().isServiceNameSetByUser(); + + static class Lazy { + static final ClassloaderConfigurationOverrides INSTANCE = + new ClassloaderConfigurationOverrides(); + } + + public static class ContextualInfo { + private final String serviceName; + private final Map tags = new HashMap<>(); + + public ContextualInfo(String serviceName) { + this.serviceName = serviceName; + } + + public String getServiceName() { + return serviceName; + } + + public void addTag(String name, Object value) { + tags.put(name, value); + } + + public Map getTags() { + return Collections.unmodifiableMap(tags); + } + } + + private static final Function EMPTY_CONTEXTUAL_INFO_ADDER = + ignored -> new ContextualInfo(null); + + private final WeakHashMap weakCache = new WeakHashMap<>(); + private final String inferredServiceName = + CapturedEnvironment.get().getProperties().get(GeneralConfig.SERVICE_NAME); + + private static volatile boolean atLeastOneEntry; + private static final Lock lock = new ReentrantLock(); + + private ClassloaderConfigurationOverrides() {} + + public static void addContextualInfo(ClassLoader classLoader, ContextualInfo contextualInfo) { + try { + lock.lock(); + Lazy.INSTANCE.weakCache.put(classLoader, contextualInfo); + atLeastOneEntry = true; + } finally { + lock.unlock(); + } + } + + public static ContextualInfo maybeCreateContextualInfo(ClassLoader classLoader) { + try { + lock.lock(); + final ContextualInfo ret = + Lazy.INSTANCE.weakCache.computeIfAbsent(classLoader, EMPTY_CONTEXTUAL_INFO_ADDER); + atLeastOneEntry = true; + return ret; + } finally { + lock.unlock(); + } + } + + @Nullable + public static ContextualInfo withPinnedServiceName(ClassLoader classLoader, String serviceName) { + if (!CAN_SPLIT_SERVICE_NAME_BY_DEPLOYMENT) { + return null; + } + final ContextualInfo contextualInfo = new ContextualInfo(serviceName); + addContextualInfo(classLoader, contextualInfo); + return contextualInfo; + } + + @Nullable + public static ContextualInfo maybeGetContextualInfo(ClassLoader classLoader) { + if (atLeastOneEntry) { + return Lazy.INSTANCE.weakCache.get(classLoader); + } + return null; + } + + /** + * Fetches the contextual information linked to the current thread's context classloader. + * + * @return a nullable service name. + */ + @Nullable + public static ContextualInfo maybeGetContextualInfo() { + if (atLeastOneEntry) { + return maybeGetContextualInfo(Thread.currentThread().getContextClassLoader()); + } + return null; + } + + /** + * Enriches the provided spans according to the service name and tags linked to the current + * thread's classloader contextual information. + * + * @param span a nonnull span + */ + public static void maybeEnrichSpan(@Nonnull final AgentSpan span) { + if (atLeastOneEntry) { + maybeEnrichSpan(span, Thread.currentThread().getContextClassLoader()); + } + } + + public static void maybeEnrichSpan( + @Nonnull final AgentSpan span, @Nonnull final ClassLoader classLoader) { + final ContextualInfo contextualInfo = maybeGetContextualInfo(classLoader); + if (contextualInfo == null) { + return; + } + final String serviceName = contextualInfo.getServiceName(); + if (CAN_SPLIT_SERVICE_NAME_BY_DEPLOYMENT && serviceName != null && !serviceName.isEmpty()) { + final String currentServiceName = span.getServiceName(); + if (currentServiceName == null + || currentServiceName.equals(Lazy.INSTANCE.inferredServiceName)) { + span.setServiceName(serviceName); + ServiceNameCollector.get().addService(serviceName); + } + } + contextualInfo.getTags().forEach(span::setTag); + } +} 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 deleted file mode 100644 index c9d73f7df1d..00000000000 --- a/internal-api/src/main/java/datadog/trace/api/naming/ClassloaderServiceNames.java +++ /dev/null @@ -1,76 +0,0 @@ -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 e7e227fbeb0..60447310128 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,7 +1,7 @@ package datadog.trace.api.naming.v0; +import datadog.trace.api.ClassloaderConfigurationOverrides; import datadog.trace.api.Config; -import datadog.trace.api.naming.ClassloaderServiceNames; import datadog.trace.api.naming.NamingSchema; import datadog.trace.api.remoteconfig.ServiceNameCollector; import java.util.function.Supplier; @@ -16,10 +16,12 @@ private static class ClassloaderDependentNamingSupplier implements Supplier> spanServiceName + } + if (expected) { + 1 * span.setServiceName(contextualServiceName) + } + + where: + splitByDeploymentEnabled | contextualServiceName | spanServiceName | expected + false | null | null | false + false | "test" | null | false + false | null | "test" | false + true | null | null | false + true | "" | null | false + true | "test" | null | true + true | "" | "test" | false + true | null | "test" | false + true | "test" | "test" | false + true | "test" | ddService | true + } + + def "enrich should set tags when present"() { + def span = Mock(AgentSpan) + when: + ClassloaderConfigurationOverrides.maybeCreateContextualInfo(Thread.currentThread().getContextClassLoader()).addTag("key", "value") + ClassloaderConfigurationOverrides.maybeEnrichSpan(span) + then: + 1 * span.setTag("key", "value") + _ * span.getServiceName() + } + + def "no enrichment when contextual info is absent"() { + def span = Mock(AgentSpan) + when: + ClassloaderConfigurationOverrides.addContextualInfo(Thread.currentThread().getContextClassLoader(), null) + then: + ClassloaderConfigurationOverrides.maybeGetContextualInfo() == null + when: + ClassloaderConfigurationOverrides.maybeEnrichSpan(span) + then: + _ + } +} diff --git a/settings.gradle b/settings.gradle index 67178ac923b..9c2f9181340 100644 --- a/settings.gradle +++ b/settings.gradle @@ -505,6 +505,7 @@ include ':dd-java-agent:instrumentation:redisson:redisson-2.0.0' include ':dd-java-agent:instrumentation:redisson:redisson-2.3.0' include ':dd-java-agent:instrumentation:redisson:redisson-3.10.3' include ':dd-java-agent:instrumentation:websphere-jmx' +include ':dd-java-agent:instrumentation:wildfly-9' include ':dd-java-agent:instrumentation:zio' include ':dd-java-agent:instrumentation:zio:zio-2.0' diff --git a/test-published-dependencies/ot-is-shaded/build.gradle b/test-published-dependencies/ot-is-shaded/build.gradle index f46b8994634..1b5451238af 100644 --- a/test-published-dependencies/ot-is-shaded/build.gradle +++ b/test-published-dependencies/ot-is-shaded/build.gradle @@ -114,7 +114,7 @@ tasks.register('checkJarSize') { doLast { // Arbitrary limit to prevent unintentional increases to the dd-trace-ot jar size // Raise or lower as required - assert jarFile.length() <= 7 * 1024 * 1024 + assert jarFile.length() <= 8 * 1024 * 1024 } }