diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/agent/Agent.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/agent/Agent.java
new file mode 100644
index 0000000000..fe3be1b629
--- /dev/null
+++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/agent/Agent.java
@@ -0,0 +1,344 @@
+package datadog.trace.agent;
+
+import java.lang.instrument.Instrumentation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Agent start up logic.
+ *
+ *
This class is loaded and called by {@code datadog.trace.agent.AgentBootstrap}
+ *
+ *
The intention is for this class to be loaded by bootstrap classloader to make sure we have
+ * unimpeded access to the rest of Datadog's agent parts.
+ */
+public class Agent {
+
+ private static final String SIMPLE_LOGGER_SHOW_DATE_TIME_PROPERTY =
+ "datadog.slf4j.simpleLogger.showDateTime";
+ private static final String SIMPLE_LOGGER_DATE_TIME_FORMAT_PROPERTY =
+ "datadog.slf4j.simpleLogger.dateTimeFormat";
+ private static final String SIMPLE_LOGGER_DATE_TIME_FORMAT_DEFAULT =
+ "'[dd.trace 'yyyy-MM-dd HH:mm:ss:SSS Z']'";
+ private static final String SIMPLE_LOGGER_DEFAULT_LOG_LEVEL_PROPERTY =
+ "datadog.slf4j.simpleLogger.defaultLogLevel";
+
+ // We cannot use lombok here because we need to configure logger first
+ private static final Logger LOGGER;
+
+ static {
+ // We can configure logger here because datadog.trace.agent.AgentBootstrap doesn't touch it.
+ configureLogger();
+ LOGGER = LoggerFactory.getLogger(Agent.class);
+ }
+
+ // fields must be managed under class lock
+ private static ClassLoader AGENT_CLASSLOADER = null;
+ private static ClassLoader JMXFETCH_CLASSLOADER = null;
+
+ public static void start(final Instrumentation inst, final URL bootstrapURL) throws Exception {
+ startDatadogAgent(inst, bootstrapURL);
+
+ final boolean appUsingCustomLogManager = isAppUsingCustomLogManager();
+
+ /*
+ * java.util.logging.LogManager maintains a final static LogManager, which is created during class initialization.
+ *
+ * JMXFetch uses jre bootstrap classes which touch this class. This means applications which require a custom log
+ * manager may not have a chance to set the global log manager if jmxfetch runs first. JMXFetch will incorrectly
+ * set the global log manager in cases where the app sets the log manager system property or when the log manager
+ * class is not on the system classpath.
+ *
+ * Our solution is to delay the initialization of jmxfetch when we detect a custom log manager being used.
+ *
+ * Once we see the LogManager class loading, it's safe to start jmxfetch because the application is already setting
+ * the global log manager and jmxfetch won't be able to touch it due to classloader locking.
+ */
+ if (appUsingCustomLogManager) {
+ LOGGER.debug("Custom logger detected. Delaying JMXFetch initialization.");
+ registerLogManagerCallback(new StartJmxFetchCallback(inst, bootstrapURL));
+ } else {
+ startJmxFetch(inst, bootstrapURL);
+ }
+
+ /*
+ * Similar thing happens with DatadogTracer on (at least) zulu-8 because it uses OkHttp which indirectly loads JFR
+ * events which in turn loads LogManager. This is not a problem on newer JDKs because there JFR uses different
+ * logging facility.
+ */
+ if (isJavaBefore9() && appUsingCustomLogManager) {
+ LOGGER.debug("Custom logger detected. Delaying Datadog Tracer initialization.");
+ registerLogManagerCallback(new InstallDatadogTracerCallback(inst, bootstrapURL));
+ } else {
+ installDatadogTracer(inst, bootstrapURL);
+ }
+ }
+
+ private static void registerLogManagerCallback(final Runnable callback) throws Exception {
+ final Class> agentInstallerClass =
+ AGENT_CLASSLOADER.loadClass("datadog.trace.agent.tooling.AgentInstaller");
+ final Method registerCallbackMethod =
+ agentInstallerClass.getMethod("registerClassLoadCallback", String.class, Runnable.class);
+ registerCallbackMethod.invoke(null, "java.util.logging.LogManager", callback);
+ }
+
+ protected abstract static class ClassLoadCallBack implements Runnable {
+
+ final Instrumentation inst;
+ final URL bootstrapURL;
+
+ ClassLoadCallBack(final Instrumentation inst, final URL bootstrapURL) {
+ this.inst = inst;
+ this.bootstrapURL = bootstrapURL;
+ }
+
+ @Override
+ public void run() {
+ /*
+ * This callback is called from within bytecode transformer. This can be a problem if callback tries
+ * to load classes being transformed. To avoid this we start a thread here that calls the callback.
+ * This seems to resolve this problem.
+ */
+ final Thread thread =
+ new Thread(
+ new Runnable() {
+ @Override
+ public void run() {
+ try {
+ execute();
+ } catch (final Exception e) {
+ LOGGER.error("Failed to run class loader callback {}", getName(), e);
+ }
+ }
+ });
+ thread.setName("dd-agent-startup-" + getName());
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+ public abstract String getName();
+
+ public abstract void execute() throws Exception;
+ }
+
+ protected static class StartJmxFetchCallback extends ClassLoadCallBack {
+ StartJmxFetchCallback(final Instrumentation inst, final URL bootstrapURL) {
+ super(inst, bootstrapURL);
+ }
+
+ @Override
+ public String getName() {
+ return "jmxfetch";
+ }
+
+ @Override
+ public void execute() throws Exception {
+ startJmxFetch(inst, bootstrapURL);
+ }
+ }
+
+ protected static class InstallDatadogTracerCallback extends ClassLoadCallBack {
+ InstallDatadogTracerCallback(final Instrumentation inst, final URL bootstrapURL) {
+ super(inst, bootstrapURL);
+ }
+
+ @Override
+ public String getName() {
+ return "datadog-tracer";
+ }
+
+ @Override
+ public void execute() throws Exception {
+ installDatadogTracer(inst, bootstrapURL);
+ }
+ }
+
+ private static synchronized void startDatadogAgent(
+ final Instrumentation inst, final URL bootstrapURL) throws Exception {
+ if (AGENT_CLASSLOADER == null) {
+ final ClassLoader agentClassLoader =
+ createDatadogClassLoader("agent-tooling-and-instrumentation.isolated", bootstrapURL);
+ final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(agentClassLoader);
+ final Class> agentInstallerClass =
+ agentClassLoader.loadClass("datadog.trace.agent.tooling.AgentInstaller");
+ final Method agentInstallerMethod =
+ agentInstallerClass.getMethod("installBytebuddyAgent", Instrumentation.class);
+ agentInstallerMethod.invoke(null, inst);
+ AGENT_CLASSLOADER = agentClassLoader;
+ } finally {
+ Thread.currentThread().setContextClassLoader(contextLoader);
+ }
+ }
+ }
+
+ private static synchronized void installDatadogTracer(
+ final Instrumentation inst, final URL bootstrapURL) throws Exception {
+ if (AGENT_CLASSLOADER == null) {
+ throw new IllegalStateException("Datadog agent should have been started already");
+ }
+ final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ // TracerInstaller.installGlobalTracer can be called multiple times without any problem
+ // so there is no need to have a 'datadogTracerInstalled' flag here.
+ try {
+ Thread.currentThread().setContextClassLoader(AGENT_CLASSLOADER);
+ // install global tracer
+ final Class> tracerInstallerClass =
+ AGENT_CLASSLOADER.loadClass("datadog.trace.agent.tooling.TracerInstaller");
+ final Method tracerInstallerMethod = tracerInstallerClass.getMethod("installGlobalTracer");
+ tracerInstallerMethod.invoke(null);
+ final Method logVersionInfoMethod = tracerInstallerClass.getMethod("logVersionInfo");
+ logVersionInfoMethod.invoke(null);
+ } finally {
+ Thread.currentThread().setContextClassLoader(contextLoader);
+ }
+ }
+
+ private static synchronized void startJmxFetch(final Instrumentation inst, final URL bootstrapURL)
+ throws Exception {
+ if (JMXFETCH_CLASSLOADER == null) {
+ final ClassLoader jmxFetchClassLoader =
+ createDatadogClassLoader("agent-jmxfetch.isolated", bootstrapURL);
+ final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(jmxFetchClassLoader);
+ final Class> jmxFetchAgentClass =
+ jmxFetchClassLoader.loadClass("datadog.trace.agent.jmxfetch.JMXFetch");
+ final Method jmxFetchInstallerMethod = jmxFetchAgentClass.getMethod("run");
+ jmxFetchInstallerMethod.invoke(null);
+ JMXFETCH_CLASSLOADER = jmxFetchClassLoader;
+ } finally {
+ Thread.currentThread().setContextClassLoader(contextLoader);
+ }
+ }
+ }
+
+ private static void configureLogger() {
+ setSystemPropertyDefault(SIMPLE_LOGGER_SHOW_DATE_TIME_PROPERTY, "true");
+ setSystemPropertyDefault(
+ SIMPLE_LOGGER_DATE_TIME_FORMAT_PROPERTY, SIMPLE_LOGGER_DATE_TIME_FORMAT_DEFAULT);
+
+ if (isDebugMode()) {
+ setSystemPropertyDefault(SIMPLE_LOGGER_DEFAULT_LOG_LEVEL_PROPERTY, "DEBUG");
+ }
+ }
+
+ private static void setSystemPropertyDefault(final String property, final String value) {
+ if (System.getProperty(property) == null) {
+ System.setProperty(property, value);
+ }
+ }
+
+ /**
+ * Create the datadog classloader. This must be called after the bootstrap jar has been appened to
+ * the bootstrap classpath.
+ *
+ * @param innerJarFilename Filename of internal jar to use for the classpath of the datadog
+ * classloader
+ * @param bootstrapURL
+ * @return Datadog Classloader
+ */
+ private static ClassLoader createDatadogClassLoader(
+ final String innerJarFilename, final URL bootstrapURL) throws Exception {
+ final ClassLoader agentParent;
+ if (isJavaBefore9()) {
+ agentParent = null; // bootstrap
+ } else {
+ // platform classloader is parent of system in java 9+
+ agentParent = getPlatformClassLoader();
+ }
+
+ final Class> loaderClass =
+ ClassLoader.getSystemClassLoader().loadClass("datadog.trace.bootstrap.DatadogClassLoader");
+ final Constructor constructor =
+ loaderClass.getDeclaredConstructor(URL.class, String.class, ClassLoader.class);
+ return (ClassLoader) constructor.newInstance(bootstrapURL, innerJarFilename, agentParent);
+ }
+
+ private static ClassLoader getPlatformClassLoader()
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ /*
+ Must invoke ClassLoader.getPlatformClassLoader by reflection to remain
+ compatible with java 7 + 8.
+ */
+ final Method method = ClassLoader.class.getDeclaredMethod("getPlatformClassLoader");
+ return (ClassLoader) method.invoke(null);
+ }
+
+ /**
+ * Determine if we should log in debug level according to dd.trace.debug
+ *
+ * @return true if we should
+ */
+ private static boolean isDebugMode() {
+ final String tracerDebugLevelSysprop = "dd.trace.debug";
+ final String tracerDebugLevelProp = System.getProperty(tracerDebugLevelSysprop);
+
+ if (tracerDebugLevelProp != null) {
+ return Boolean.parseBoolean(tracerDebugLevelProp);
+ }
+
+ final String tracerDebugLevelEnv =
+ System.getenv(tracerDebugLevelSysprop.replace('.', '_').toUpperCase());
+
+ if (tracerDebugLevelEnv != null) {
+ return Boolean.parseBoolean(tracerDebugLevelEnv);
+ }
+ return false;
+ }
+
+ /**
+ * Search for java or datadog-tracer sysprops which indicate that a custom log manager will be
+ * used. Also search for any app classes known to set a custom log manager.
+ *
+ * @return true if we detect a custom log manager being used.
+ */
+ private static boolean isAppUsingCustomLogManager() {
+ final String tracerCustomLogManSysprop = "dd.app.customlogmanager";
+ final String customLogManagerProp = System.getProperty(tracerCustomLogManSysprop);
+ final String customLogManagerEnv =
+ System.getenv(tracerCustomLogManSysprop.replace('.', '_').toUpperCase());
+
+ if (customLogManagerProp != null || customLogManagerEnv != null) {
+ LOGGER.debug("Prop - customlogmanager: " + customLogManagerProp);
+ LOGGER.debug("Env - customlogmanager: " + customLogManagerEnv);
+ // Allow setting to skip these automatic checks:
+ return Boolean.parseBoolean(customLogManagerProp)
+ || Boolean.parseBoolean(customLogManagerEnv);
+ }
+
+ final String jbossHome = System.getenv("JBOSS_HOME");
+ if (jbossHome != null) {
+ LOGGER.debug("Env - jboss: " + jbossHome);
+ // JBoss/Wildfly is known to set a custom log manager after startup.
+ // Originally we were checking for the presence of a jboss class,
+ // but it seems some non-jboss applications have jboss classes on the classpath.
+ // This would cause jmxfetch initialization to be delayed indefinitely.
+ // Checking for an environment variable required by jboss instead.
+ return true;
+ }
+
+ final String logManagerProp = System.getProperty("java.util.logging.manager");
+ if (logManagerProp != null) {
+ final boolean onSysClasspath =
+ ClassLoader.getSystemResource(logManagerProp.replaceAll("\\.", "/") + ".class") != null;
+ LOGGER.debug("Prop - logging.manager: " + logManagerProp);
+ LOGGER.debug("logging.manager on system classpath: " + onSysClasspath);
+ // Some applications set java.util.logging.manager but never actually initialize the logger.
+ // Check to see if the configured manager is on the system classpath.
+ // If so, it should be safe to initialize jmxfetch which will setup the log manager.
+ return !onSysClasspath;
+ }
+
+ return false;
+ }
+
+ private static boolean isJavaBefore9() {
+ return System.getProperty("java.version").startsWith("1.");
+ }
+}
diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java
index 5533f2e622..fdeb757929 100644
--- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java
+++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java
@@ -12,11 +12,12 @@ import static net.bytebuddy.matcher.ElementMatchers.not;
import datadog.trace.api.Config;
import java.lang.instrument.Instrumentation;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
-import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.ResettableClassFileTransformer;
@@ -28,7 +29,7 @@ import net.bytebuddy.utility.JavaModule;
@Slf4j
public class AgentInstaller {
- private static final Map classLoadCallbacks = new ConcurrentHashMap<>();
+ private static final Map> CLASS_LOAD_CALLBACKS = new HashMap<>();
private static volatile Instrumentation INSTRUMENTATION;
public static Instrumentation getInstrumentation() {
@@ -253,15 +254,25 @@ public class AgentInstaller {
/**
* Register a callback to run when a class is loading.
*
- * Caveats: 1: This callback will be invoked by a jvm class transformer. 2: Classes filtered
- * out by {@link AgentInstaller}'s skip list will not be matched.
+ *
Caveats:
+ *
+ *
+ * - This callback will be invoked by a jvm class transformer.
+ *
- Classes filtered out by {@link AgentInstaller}'s skip list will not be matched.
+ *
*
* @param className name of the class to match against
- * @param classLoadCallback runnable to invoke when class name matches
+ * @param callback runnable to invoke when class name matches
*/
- public static void registerClassLoadCallback(
- final String className, final Runnable classLoadCallback) {
- classLoadCallbacks.put(className, classLoadCallback);
+ public static void registerClassLoadCallback(final String className, final Runnable callback) {
+ synchronized (CLASS_LOAD_CALLBACKS) {
+ List callbacks = CLASS_LOAD_CALLBACKS.get(className);
+ if (callbacks == null) {
+ callbacks = new ArrayList<>();
+ CLASS_LOAD_CALLBACKS.put(className, callbacks);
+ }
+ callbacks.add(callback);
+ }
}
private static class ClassLoadListener implements AgentBuilder.Listener {
@@ -301,9 +312,12 @@ public class AgentInstaller {
final ClassLoader classLoader,
final JavaModule javaModule,
final boolean b) {
- for (final Map.Entry entry : classLoadCallbacks.entrySet()) {
- if (entry.getKey().equals(typeName)) {
- entry.getValue().run();
+ synchronized (CLASS_LOAD_CALLBACKS) {
+ final List callbacks = CLASS_LOAD_CALLBACKS.get(typeName);
+ if (callbacks != null) {
+ for (final Runnable callback : callbacks) {
+ callback.run();
+ }
}
}
}
diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Constants.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Constants.java
index 8c57660228..913c4aca57 100644
--- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Constants.java
+++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Constants.java
@@ -15,7 +15,7 @@ public final class Constants {
*/
public static final String[] BOOTSTRAP_PACKAGE_PREFIXES = {
"datadog.slf4j",
- "datadog.trace.agent.TracingAgent",
+ "datadog.trace.agent",
"datadog.trace.api",
"datadog.trace.bootstrap",
"datadog.trace.context",
diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedProvider.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedProvider.java
index 0605b46e29..3c77fb422e 100644
--- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedProvider.java
+++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedProvider.java
@@ -73,8 +73,8 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
/**
* Note: the value here has to be inside on of the prefixes in
- * datadog.trace.agent.tooling.Utils#BOOTSTRAP_PACKAGE_PREFIXES. This ensures that 'isolating' (or
- * 'module') classloaders like jboss and osgi see injected classes. This works because we
+ * datadog.trace.agent.tooling.Constants#BOOTSTRAP_PACKAGE_PREFIXES. This ensures that 'isolating'
+ * (or 'module') classloaders like jboss and osgi see injected classes. This works because we
* instrument those classloaders to load everything inside bootstrap packages.
*/
private static final String DYNAMIC_CLASSES_PACKAGE =
diff --git a/dd-java-agent/dd-java-agent.gradle b/dd-java-agent/dd-java-agent.gradle
index d47ee5eac6..5b8c88c96b 100644
--- a/dd-java-agent/dd-java-agent.gradle
+++ b/dd-java-agent/dd-java-agent.gradle
@@ -62,9 +62,9 @@ jar {
manifest {
attributes(
- "Main-Class": "datadog.trace.agent.TracingAgent",
- "Agent-Class": "datadog.trace.agent.TracingAgent",
- "Premain-Class": "datadog.trace.agent.TracingAgent",
+ "Main-Class": "datadog.trace.agent.AgentBootstrap",
+ "Agent-Class": "datadog.trace.agent.AgentBootstrap",
+ "Premain-Class": "datadog.trace.agent.AgentBootstrap",
"Can-Redefine-Classes": true,
"Can-Retransform-Classes": true,
)
diff --git a/dd-java-agent/instrumentation/osgi-classloading/src/test/groovy/OSGIClassloadingTest.groovy b/dd-java-agent/instrumentation/osgi-classloading/src/test/groovy/OSGIClassloadingTest.groovy
index 53c7340e61..d3f251fea3 100644
--- a/dd-java-agent/instrumentation/osgi-classloading/src/test/groovy/OSGIClassloadingTest.groovy
+++ b/dd-java-agent/instrumentation/osgi-classloading/src/test/groovy/OSGIClassloadingTest.groovy
@@ -10,7 +10,7 @@ class OSGIClassloadingTest extends AgentTestRunner {
@Rule
public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties()
- static final String BOOT_DELEGATION_ADDITION = "datadog.slf4j.*,datadog.slf4j,datadog.trace.agent.TracingAgent.*,datadog.trace.agent.TracingAgent,datadog.trace.api.*,datadog.trace.api,datadog.trace.bootstrap.*,datadog.trace.bootstrap,datadog.trace.context.*,datadog.trace.context,datadog.trace.instrumentation.api.*,datadog.trace.instrumentation.api,io.opentracing.*,io.opentracing"
+ static final String BOOT_DELEGATION_ADDITION = "datadog.slf4j.*,datadog.slf4j,datadog.trace.agent.*,datadog.trace.api.*,datadog.trace.api,datadog.trace.bootstrap.*,datadog.trace.bootstrap,datadog.trace.context.*,datadog.trace.context,datadog.trace.instrumentation.api.*,datadog.trace.instrumentation.api,io.opentracing.*,io.opentracing"
def "delegation property set on module load"() {
when:
diff --git a/dd-java-agent/src/main/java/datadog/trace/agent/AgentBootstrap.java b/dd-java-agent/src/main/java/datadog/trace/agent/AgentBootstrap.java
new file mode 100644
index 0000000000..da5e2eee7c
--- /dev/null
+++ b/dd-java-agent/src/main/java/datadog/trace/agent/AgentBootstrap.java
@@ -0,0 +1,188 @@
+package datadog.trace.agent;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.instrument.Instrumentation;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.security.CodeSource;
+import java.util.Arrays;
+import java.util.List;
+import java.util.jar.JarFile;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Entry point for initializing the agent.
+ *
+ * The bootstrap process of the agent is somewhat complicated and care has to be taken to make
+ * sure things do not get broken by accident.
+ *
+ *
JVM loads this class onto app's classloader, afterwards agent needs to inject its classes onto
+ * bootstrap classpath. This leads to this class being visible on bootstrap. This in turn means that
+ * this class may be loaded again on bootstrap by accident if we ever reference is after bootstrap
+ * has been setup.
+ *
+ *
In order to avoid this we need to make sure we do a few things:
+ *
+ *
+ * - Do as little as possible here
+ *
- Never reference this class after we have setup bootstrap and jumped over to 'real' agent
+ * code
+ *
- Do not store any static data in this class
+ *
- Do dot touch any logging facilities here so we can configure them later
+ *
+ */
+public class AgentBootstrap {
+
+ public static void premain(final String agentArgs, final Instrumentation inst) throws Exception {
+ agentmain(agentArgs, inst);
+ }
+
+ public static void agentmain(final String agentArgs, final Instrumentation inst)
+ throws Exception {
+
+ final URL bootstrapURL = installBootstrapJar(inst);
+
+ final Class> agentClass =
+ ClassLoader.getSystemClassLoader().loadClass("datadog.trace.agent.Agent");
+ final Method startMethod = agentClass.getMethod("start", Instrumentation.class, URL.class);
+ startMethod.invoke(null, inst, bootstrapURL);
+ }
+
+ private static synchronized URL installBootstrapJar(final Instrumentation inst)
+ throws IOException, URISyntaxException {
+ URL bootstrapURL = null;
+
+ // First try Code Source
+ final CodeSource codeSource = AgentBootstrap.class.getProtectionDomain().getCodeSource();
+
+ if (codeSource != null) {
+ bootstrapURL = codeSource.getLocation();
+ final File bootstrapFile = new File(bootstrapURL.toURI());
+
+ if (!bootstrapFile.isDirectory()) {
+ inst.appendToBootstrapClassLoaderSearch(new JarFile(bootstrapFile));
+ return bootstrapURL;
+ }
+ }
+
+ System.out.println("Could not get bootstrap jar from code source, using -javaagent arg");
+
+ // ManagementFactory indirectly references java.util.logging.LogManager
+ // - On Oracle-based JDKs after 1.8
+ // - On IBM-based JDKs since at least 1.7
+ // This prevents custom log managers from working correctly
+ // Use reflection to bypass the loading of the class
+ final List arguments = getVMArgumentsThroughReflection();
+
+ String agentArgument = null;
+ for (final String arg : arguments) {
+ if (arg.startsWith("-javaagent")) {
+ if (agentArgument == null) {
+ agentArgument = arg;
+ } else {
+ throw new RuntimeException(
+ "Multiple javaagents specified and code source unavailable, not installing tracing agent");
+ }
+ }
+ }
+
+ if (agentArgument == null) {
+ throw new RuntimeException(
+ "Could not find javaagent parameter and code source unavailable, not installing tracing agent");
+ }
+
+ // argument is of the form -javaagent:/path/to/dd-java-agent.jar=optionalargumentstring
+ final Matcher matcher = Pattern.compile("-javaagent:([^=]+).*").matcher(agentArgument);
+
+ if (!matcher.matches()) {
+ throw new RuntimeException("Unable to parse javaagent parameter: " + agentArgument);
+ }
+
+ bootstrapURL = new URL("file:" + matcher.group(1));
+ inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(bootstrapURL.toURI())));
+
+ return bootstrapURL;
+ }
+
+ private static List getVMArgumentsThroughReflection() {
+ try {
+ // Try Oracle-based
+ final Class managementFactoryHelperClass =
+ AgentBootstrap.class.getClassLoader().loadClass("sun.management.ManagementFactoryHelper");
+
+ final Class vmManagementClass =
+ AgentBootstrap.class.getClassLoader().loadClass("sun.management.VMManagement");
+
+ Object vmManagement;
+
+ try {
+ vmManagement =
+ managementFactoryHelperClass.getDeclaredMethod("getVMManagement").invoke(null);
+ } catch (final NoSuchMethodException e) {
+ // Older vm before getVMManagement() existed
+ final Field field = managementFactoryHelperClass.getDeclaredField("jvm");
+ field.setAccessible(true);
+ vmManagement = field.get(null);
+ field.setAccessible(false);
+ }
+
+ return (List) vmManagementClass.getMethod("getVmArguments").invoke(vmManagement);
+
+ } catch (final ReflectiveOperationException e) {
+ try { // Try IBM-based.
+ final Class VMClass = AgentBootstrap.class.getClassLoader().loadClass("com.ibm.oti.vm.VM");
+ final String[] argArray = (String[]) VMClass.getMethod("getVMArgs").invoke(null);
+ return Arrays.asList(argArray);
+ } catch (final ReflectiveOperationException e1) {
+ // Fallback to default
+ System.out.println(
+ "WARNING: Unable to get VM args through reflection. A custom java.util.logging.LogManager may not work correctly");
+
+ return ManagementFactory.getRuntimeMXBean().getInputArguments();
+ }
+ }
+ }
+
+ /**
+ * Main entry point.
+ *
+ * @param args command line agruments
+ */
+ public static void main(final String... args) {
+ try {
+ System.out.println(getAgentVersion());
+ } catch (final Exception e) {
+ System.out.println("Failed to parse agent version");
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Read version file out of the agent jar.
+ *
+ * @return Agent version
+ */
+ public static String getAgentVersion() throws IOException {
+ final StringBuilder sb = new StringBuilder();
+ try (final BufferedReader reader =
+ new BufferedReader(
+ new InputStreamReader(
+ AgentBootstrap.class.getResourceAsStream("/dd-java-agent.version"),
+ StandardCharsets.UTF_8))) {
+
+ for (int c = reader.read(); c != -1; c = reader.read()) {
+ sb.append((char) c);
+ }
+ }
+
+ return sb.toString().trim();
+ }
+}
diff --git a/dd-java-agent/src/main/java/datadog/trace/agent/TracingAgent.java b/dd-java-agent/src/main/java/datadog/trace/agent/TracingAgent.java
deleted file mode 100644
index b2c09c67fd..0000000000
--- a/dd-java-agent/src/main/java/datadog/trace/agent/TracingAgent.java
+++ /dev/null
@@ -1,415 +0,0 @@
-package datadog.trace.agent;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.lang.instrument.Instrumentation;
-import java.lang.management.ManagementFactory;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.security.CodeSource;
-import java.util.Arrays;
-import java.util.List;
-import java.util.jar.JarFile;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/** Entry point for initializing the agent. */
-public class TracingAgent {
-
- private static final String SIMPLE_LOGGER_SHOW_DATE_TIME_PROPERTY =
- "datadog.slf4j.simpleLogger.showDateTime";
- private static final String SIMPLE_LOGGER_DATE_TIME_FORMAT_PROPERTY =
- "datadog.slf4j.simpleLogger.dateTimeFormat";
- private static final String SIMPLE_LOGGER_DATE_TIME_FORMAT_DEFAULT =
- "'[dd.trace 'yyyy-MM-dd HH:mm:ss:SSS Z']'";
- private static final String SIMPLE_LOGGER_DEFAULT_LOG_LEVEL_PROPERTY =
- "datadog.slf4j.simpleLogger.defaultLogLevel";
-
- // fields must be managed under class lock
- private static ClassLoader AGENT_CLASSLOADER = null;
- private static ClassLoader JMXFETCH_CLASSLOADER = null;
-
- public static void premain(final String agentArgs, final Instrumentation inst) throws Exception {
- agentmain(agentArgs, inst);
- }
-
- public static void agentmain(final String agentArgs, final Instrumentation inst)
- throws Exception {
- configureLogger();
-
- final URL bootstrapURL = installBootstrapJar(inst);
-
- startDatadogAgent(inst, bootstrapURL);
- if (isAppUsingCustomLogManager()) {
- System.out.println("Custom logger detected. Delaying JMXFetch initialization.");
- /*
- * java.util.logging.LogManager maintains a final static LogManager, which is created during class initialization.
- *
- * JMXFetch uses jre bootstrap classes which touch this class. This means applications which require a custom log
- * manager may not have a chance to set the global log manager if jmxfetch runs first. JMXFetch will incorrectly
- * set the global log manager in cases where the app sets the log manager system property or when the log manager
- * class is not on the system classpath.
- *
- * Our solution is to delay the initialization of jmxfetch when we detect a custom log manager being used.
- *
- * Once we see the LogManager class loading, it's safe to start jmxfetch because the application is already setting
- * the global log manager and jmxfetch won't be able to touch it due to classloader locking.
- */
- final Class> agentInstallerClass =
- AGENT_CLASSLOADER.loadClass("datadog.trace.agent.tooling.AgentInstaller");
- final Method registerCallbackMethod =
- agentInstallerClass.getMethod("registerClassLoadCallback", String.class, Runnable.class);
- registerCallbackMethod.invoke(
- null, "java.util.logging.LogManager", new LoggingCallback(inst, bootstrapURL));
- } else {
- startJmxFetch(inst, bootstrapURL);
- }
- }
-
- protected static class LoggingCallback implements Runnable {
- private final Instrumentation inst;
- private final URL bootstrapURL;
-
- public LoggingCallback(final Instrumentation inst, final URL bootstrapURL) {
- this.inst = inst;
- this.bootstrapURL = bootstrapURL;
- }
-
- @Override
- public void run() {
- try {
- startJmxFetch(inst, bootstrapURL);
- } catch (final Exception e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- private static synchronized void startDatadogAgent(
- final Instrumentation inst, final URL bootstrapURL) throws Exception {
-
- if (AGENT_CLASSLOADER == null) {
- final ClassLoader agentClassLoader =
- createDatadogClassLoader("agent-tooling-and-instrumentation.isolated", bootstrapURL);
- final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
- try {
- Thread.currentThread().setContextClassLoader(agentClassLoader);
- { // install agent
- final Class> agentInstallerClass =
- agentClassLoader.loadClass("datadog.trace.agent.tooling.AgentInstaller");
- final Method agentInstallerMethod =
- agentInstallerClass.getMethod("installBytebuddyAgent", Instrumentation.class);
- agentInstallerMethod.invoke(null, inst);
- }
- {
- // install global tracer
- final Class> tracerInstallerClass =
- agentClassLoader.loadClass("datadog.trace.agent.tooling.TracerInstaller");
- final Method tracerInstallerMethod =
- tracerInstallerClass.getMethod("installGlobalTracer");
- tracerInstallerMethod.invoke(null);
- final Method logVersionInfoMethod = tracerInstallerClass.getMethod("logVersionInfo");
- logVersionInfoMethod.invoke(null);
- }
- AGENT_CLASSLOADER = agentClassLoader;
- } finally {
- Thread.currentThread().setContextClassLoader(contextLoader);
- }
- }
- }
-
- private static synchronized void startJmxFetch(final Instrumentation inst, final URL bootstrapURL)
- throws Exception {
- if (JMXFETCH_CLASSLOADER == null) {
- final ClassLoader jmxFetchClassLoader =
- createDatadogClassLoader("agent-jmxfetch.isolated", bootstrapURL);
- final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
- try {
- Thread.currentThread().setContextClassLoader(jmxFetchClassLoader);
- final Class> jmxFetchAgentClass =
- jmxFetchClassLoader.loadClass("datadog.trace.agent.jmxfetch.JMXFetch");
- final Method jmxFetchInstallerMethod = jmxFetchAgentClass.getMethod("run");
- jmxFetchInstallerMethod.invoke(null);
- JMXFETCH_CLASSLOADER = jmxFetchClassLoader;
- } finally {
- Thread.currentThread().setContextClassLoader(contextLoader);
- }
- }
- }
-
- private static void configureLogger() {
- setSystemPropertyDefault(SIMPLE_LOGGER_SHOW_DATE_TIME_PROPERTY, "true");
- setSystemPropertyDefault(
- SIMPLE_LOGGER_DATE_TIME_FORMAT_PROPERTY, SIMPLE_LOGGER_DATE_TIME_FORMAT_DEFAULT);
-
- final boolean debugEnabled = isDebugMode();
- if (debugEnabled) {
- setSystemPropertyDefault(SIMPLE_LOGGER_DEFAULT_LOG_LEVEL_PROPERTY, "DEBUG");
- }
- }
-
- private static void setSystemPropertyDefault(final String property, final String value) {
- if (System.getProperty(property) == null) {
- System.setProperty(property, value);
- }
- }
-
- private static synchronized URL installBootstrapJar(final Instrumentation inst)
- throws IOException, URISyntaxException {
- URL bootstrapURL = null;
-
- // First try Code Source
- final CodeSource codeSource = TracingAgent.class.getProtectionDomain().getCodeSource();
-
- if (codeSource != null) {
- bootstrapURL = codeSource.getLocation();
- final File bootstrapFile = new File(bootstrapURL.toURI());
-
- if (!bootstrapFile.isDirectory()) {
- inst.appendToBootstrapClassLoaderSearch(new JarFile(bootstrapFile));
- return bootstrapURL;
- }
- }
-
- System.out.println("Could not get bootstrap jar from code source, using -javaagent arg");
-
- // ManagementFactory indirectly references java.util.logging.LogManager
- // - On Oracle-based JDKs after 1.8
- // - On IBM-based JDKs since at least 1.7
- // This prevents custom log managers from working correctly
- // Use reflection to bypass the loading of the class
- final List arguments = getVMArgumentsThroughReflection();
-
- String agentArgument = null;
- for (final String arg : arguments) {
- if (arg.startsWith("-javaagent")) {
- if (agentArgument == null) {
- agentArgument = arg;
- } else {
- throw new RuntimeException(
- "Multiple javaagents specified and code source unavailable, not installing tracing agent");
- }
- }
- }
-
- if (agentArgument == null) {
- throw new RuntimeException(
- "Could not find javaagent parameter and code source unavailable, not installing tracing agent");
- }
-
- // argument is of the form -javaagent:/path/to/dd-java-agent.jar=optionalargumentstring
- final Matcher matcher = Pattern.compile("-javaagent:([^=]+).*").matcher(agentArgument);
-
- if (!matcher.matches()) {
- throw new RuntimeException("Unable to parse javaagent parameter: " + agentArgument);
- }
-
- bootstrapURL = new URL("file:" + matcher.group(1));
- inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(bootstrapURL.toURI())));
-
- return bootstrapURL;
- }
-
- private static List getVMArgumentsThroughReflection() {
- try {
- // Try Oracle-based
- final Class managementFactoryHelperClass =
- TracingAgent.class.getClassLoader().loadClass("sun.management.ManagementFactoryHelper");
-
- final Class vmManagementClass =
- TracingAgent.class.getClassLoader().loadClass("sun.management.VMManagement");
-
- Object vmManagement;
-
- try {
- vmManagement =
- managementFactoryHelperClass.getDeclaredMethod("getVMManagement").invoke(null);
- } catch (final NoSuchMethodException e) {
- // Older vm before getVMManagement() existed
- final Field field = managementFactoryHelperClass.getDeclaredField("jvm");
- field.setAccessible(true);
- vmManagement = field.get(null);
- field.setAccessible(false);
- }
-
- return (List) vmManagementClass.getMethod("getVmArguments").invoke(vmManagement);
-
- } catch (final ReflectiveOperationException e) {
- try { // Try IBM-based.
- final Class VMClass = TracingAgent.class.getClassLoader().loadClass("com.ibm.oti.vm.VM");
- final String[] argArray = (String[]) VMClass.getMethod("getVMArgs").invoke(null);
- return Arrays.asList(argArray);
- } catch (final ReflectiveOperationException e1) {
- // Fallback to default
- System.out.println(
- "WARNING: Unable to get VM args through reflection. A custom java.util.logging.LogManager may not work correctly");
-
- return ManagementFactory.getRuntimeMXBean().getInputArguments();
- }
- }
- }
-
- /**
- * Create the datadog classloader. This must be called after the bootstrap jar has been appened to
- * the bootstrap classpath.
- *
- * @param innerJarFilename Filename of internal jar to use for the classpath of the datadog
- * classloader
- * @param bootstrapURL
- * @return Datadog Classloader
- */
- private static ClassLoader createDatadogClassLoader(
- final String innerJarFilename, final URL bootstrapURL) throws Exception {
- final ClassLoader agentParent;
- if (isJavaBefore9()) {
- agentParent = null; // bootstrap
- } else {
- // platform classloader is parent of system in java 9+
- agentParent = getPlatformClassLoader();
- }
-
- final Class> loaderClass =
- ClassLoader.getSystemClassLoader().loadClass("datadog.trace.bootstrap.DatadogClassLoader");
- final Constructor constructor =
- loaderClass.getDeclaredConstructor(URL.class, String.class, ClassLoader.class);
- return (ClassLoader) constructor.newInstance(bootstrapURL, innerJarFilename, agentParent);
- }
-
- private static ClassLoader getPlatformClassLoader()
- throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
- /*
- Must invoke ClassLoader.getPlatformClassLoader by reflection to remain
- compatible with java 7 + 8.
- */
- final Method method = ClassLoader.class.getDeclaredMethod("getPlatformClassLoader");
- return (ClassLoader) method.invoke(null);
- }
-
- /**
- * Determine if we should log in debug level according to dd.trace.debug
- *
- * @return true if we should
- */
- private static boolean isDebugMode() {
- final String tracerDebugLevelSysprop = "dd.trace.debug";
- final String tracerDebugLevelProp = System.getProperty(tracerDebugLevelSysprop);
-
- if (tracerDebugLevelProp != null) {
- return Boolean.parseBoolean(tracerDebugLevelProp);
- }
-
- final String tracerDebugLevelEnv =
- System.getenv(tracerDebugLevelSysprop.replace('.', '_').toUpperCase());
-
- if (tracerDebugLevelEnv != null) {
- return Boolean.parseBoolean(tracerDebugLevelEnv);
- }
- return false;
- }
-
- /**
- * Search for java or datadog-tracer sysprops which indicate that a custom log manager will be
- * used. Also search for any app classes known to set a custom log manager.
- *
- * @return true if we detect a custom log manager being used.
- */
- private static boolean isAppUsingCustomLogManager() {
- boolean debugEnabled = false;
- if (System.getProperty(SIMPLE_LOGGER_DEFAULT_LOG_LEVEL_PROPERTY) != null) {
- debugEnabled =
- "debug".equalsIgnoreCase(System.getProperty(SIMPLE_LOGGER_DEFAULT_LOG_LEVEL_PROPERTY));
- } else {
- debugEnabled = isDebugMode();
- }
-
- final String tracerCustomLogManSysprop = "dd.app.customlogmanager";
- final String customLogManagerProp = System.getProperty(tracerCustomLogManSysprop);
- final String customLogManagerEnv =
- System.getenv(tracerCustomLogManSysprop.replace('.', '_').toUpperCase());
-
- if (customLogManagerProp != null || customLogManagerEnv != null) {
- if (debugEnabled) {
- System.out.println("Prop - customlogmanager: " + customLogManagerProp);
- System.out.println("Env - customlogmanager: " + customLogManagerEnv);
- }
- // Allow setting to skip these automatic checks:
- return Boolean.parseBoolean(customLogManagerProp)
- || Boolean.parseBoolean(customLogManagerEnv);
- }
-
- final String jbossHome = System.getenv("JBOSS_HOME");
- if (jbossHome != null) {
- if (debugEnabled) {
- System.out.println("Env - jboss: " + jbossHome);
- }
- // JBoss/Wildfly is known to set a custom log manager after startup.
- // Originally we were checking for the presence of a jboss class,
- // but it seems some non-jboss applications have jboss classes on the classpath.
- // This would cause jmxfetch initialization to be delayed indefinitely.
- // Checking for an environment variable required by jboss instead.
- return true;
- }
-
- final String logManagerProp = System.getProperty("java.util.logging.manager");
- if (logManagerProp != null) {
- final boolean onSysClasspath =
- ClassLoader.getSystemResource(logManagerProp.replaceAll("\\.", "/") + ".class") != null;
- if (debugEnabled) {
- System.out.println("Prop - logging.manager: " + logManagerProp);
- System.out.println("logging.manager on system classpath: " + onSysClasspath);
- }
- // Some applications set java.util.logging.manager but never actually initialize the logger.
- // Check to see if the configured manager is on the system classpath.
- // If so, it should be safe to initialize jmxfetch which will setup the log manager.
- return !onSysClasspath;
- }
-
- return false;
- }
-
- private static boolean isJavaBefore9() {
- return System.getProperty("java.version").startsWith("1.");
- }
-
- /**
- * Main entry point.
- *
- * @param args command line agruments
- */
- public static void main(final String... args) {
- try {
- System.out.println(getAgentVersion());
- } catch (final Exception e) {
- System.out.println("Failed to parse agent version");
- e.printStackTrace();
- }
- }
-
- /**
- * Read version file out of the agent jar.
- *
- * @return Agent version
- */
- public static String getAgentVersion() throws IOException {
- final StringBuilder sb = new StringBuilder();
- try (final BufferedReader reader =
- new BufferedReader(
- new InputStreamReader(
- TracingAgent.class.getResourceAsStream("/dd-java-agent.version"),
- StandardCharsets.UTF_8))) {
-
- for (int c = reader.read(); c != -1; c = reader.read()) {
- sb.append((char) c);
- }
- }
-
- return sb.toString().trim();
- }
-}
diff --git a/dd-java-agent/src/test/groovy/datadog/trace/agent/CustomLogManagerTest.groovy b/dd-java-agent/src/test/groovy/datadog/trace/agent/CustomLogManagerTest.groovy
index f70771d89b..ca1f0be952 100644
--- a/dd-java-agent/src/test/groovy/datadog/trace/agent/CustomLogManagerTest.groovy
+++ b/dd-java-agent/src/test/groovy/datadog/trace/agent/CustomLogManagerTest.groovy
@@ -3,65 +3,64 @@ package datadog.trace.agent
import datadog.trace.agent.test.IntegrationTestUtils
import jvmbootstraptest.LogManagerSetter
import spock.lang.Requires
-import spock.lang.Retry
import spock.lang.Specification
-import spock.lang.Timeout
// Note: this test is fails on IBM JVM, we would need to investigate this at some point
@Requires({ !System.getProperty("java.vm.name").contains("IBM J9 VM") })
-@Retry
-@Timeout(30)
class CustomLogManagerTest extends Specification {
+
+ private static final String DEFAULT_LOG_LEVEL = "debug"
+
// Run all tests using forked jvm because groovy has already set the global log manager
- def "jmxfetch starts up in premain with no custom log manager set"() {
+ def "agent services starts up in premain with no custom log manager set"() {
expect:
IntegrationTestUtils.runOnSeparateJvm(LogManagerSetter.getName()
- , ["-Ddd.jmxfetch.enabled=true", "-Ddd.jmxfetch.refresh-beans-period=1", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=off"] as String[]
+ , ["-Ddd.jmxfetch.enabled=true", "-Ddd.jmxfetch.refresh-beans-period=1", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL"] as String[]
, "" as String[]
, [:]
, true) == 0
}
- def "jmxfetch starts up in premain if configured log manager on system classpath"() {
+ def "agent services starts up in premain if configured log manager on system classpath"() {
expect:
IntegrationTestUtils.runOnSeparateJvm(LogManagerSetter.getName()
- , ["-Ddd.jmxfetch.enabled=true", "-Ddd.jmxfetch.refresh-beans-period=1", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=off", "-Djava.util.logging.manager=jvmbootstraptest.CustomLogManager"] as String[]
+ , ["-Ddd.jmxfetch.enabled=true", "-Ddd.jmxfetch.refresh-beans-period=1", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL", "-Djava.util.logging.manager=jvmbootstraptest.CustomLogManager"] as String[]
, "" as String[]
, [:]
, true) == 0
}
- def "jmxfetch startup is delayed with java.util.logging.manager sysprop"() {
+ def "agent services startup is delayed with java.util.logging.manager sysprop"() {
expect:
IntegrationTestUtils.runOnSeparateJvm(LogManagerSetter.getName()
- , ["-Ddd.jmxfetch.enabled=true", "-Ddd.jmxfetch.refresh-beans-period=1", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=off", "-Djava.util.logging.manager=jvmbootstraptest.MissingLogManager"] as String[]
+ , ["-Ddd.jmxfetch.enabled=true", "-Ddd.jmxfetch.refresh-beans-period=1", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL", "-Djava.util.logging.manager=jvmbootstraptest.MissingLogManager"] as String[]
, "" as String[]
, [:]
, true) == 0
}
- def "jmxfetch startup delayed with tracer custom log manager setting"() {
+ def "agent services startup delayed with tracer custom log manager setting"() {
expect:
IntegrationTestUtils.runOnSeparateJvm(LogManagerSetter.getName()
- , ["-Ddd.jmxfetch.enabled=true", "-Ddd.jmxfetch.refresh-beans-period=1", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=off", "-Ddd.app.customlogmanager=true"] as String[]
+ , ["-Ddd.jmxfetch.enabled=true", "-Ddd.jmxfetch.refresh-beans-period=1", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL", "-Ddd.app.customlogmanager=true"] as String[]
, "" as String[]
, [:]
, true) == 0
}
- def "jmxfetch startup delayed with JBOSS_HOME environment variable"() {
+ def "agent services startup delayed with JBOSS_HOME environment variable"() {
expect:
IntegrationTestUtils.runOnSeparateJvm(LogManagerSetter.getName()
- , ["-Ddd.jmxfetch.enabled=true", "-Ddd.jmxfetch.refresh-beans-period=1", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=off", "-Ddd.app.customlogmanager=true"] as String[]
+ , ["-Ddd.jmxfetch.enabled=true", "-Ddd.jmxfetch.refresh-beans-period=1", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL"] as String[]
, "" as String[]
, ["JBOSS_HOME": "/"]
, true) == 0
}
- def "jmxfetch startup in premain forced by customlogmanager=false"() {
+ def "agent services startup in premain forced by customlogmanager=false"() {
expect:
IntegrationTestUtils.runOnSeparateJvm(LogManagerSetter.getName()
- , ["-Ddd.jmxfetch.enabled=true", "-Ddd.jmxfetch.refresh-beans-period=1", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=off", "-Ddd.app.customlogmanager=false", "-Djava.util.logging.manager=jvmbootstraptest.CustomLogManager"] as String[]
+ , ["-Ddd.jmxfetch.enabled=true", "-Ddd.jmxfetch.refresh-beans-period=1", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=$DEFAULT_LOG_LEVEL", "-Ddd.app.customlogmanager=false", "-Djava.util.logging.manager=jvmbootstraptest.CustomLogManager"] as String[]
, "" as String[]
, ["JBOSS_HOME": "/"]
, true) == 0
diff --git a/dd-java-agent/src/test/java/datadog/trace/agent/test/IntegrationTestUtils.java b/dd-java-agent/src/test/java/datadog/trace/agent/test/IntegrationTestUtils.java
index 36f56292cc..fca5dcd5c5 100644
--- a/dd-java-agent/src/test/java/datadog/trace/agent/test/IntegrationTestUtils.java
+++ b/dd-java-agent/src/test/java/datadog/trace/agent/test/IntegrationTestUtils.java
@@ -1,7 +1,5 @@
package datadog.trace.agent.test;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
import io.opentracing.Tracer;
import io.opentracing.util.GlobalTracer;
import java.io.BufferedReader;
@@ -30,21 +28,20 @@ public class IntegrationTestUtils {
/** Returns the classloader the core agent is running on. */
public static ClassLoader getAgentClassLoader() {
- return getTracingAgentFieldClassloader("AGENT_CLASSLOADER");
+ return getAgentFieldClassloader("AGENT_CLASSLOADER");
}
/** Returns the classloader the jmxfetch is running on. */
public static ClassLoader getJmxFetchClassLoader() {
- return getTracingAgentFieldClassloader("JMXFETCH_CLASSLOADER");
+ return getAgentFieldClassloader("JMXFETCH_CLASSLOADER");
}
- private static ClassLoader getTracingAgentFieldClassloader(final String fieldName) {
+ private static ClassLoader getAgentFieldClassloader(final String fieldName) {
Field classloaderField = null;
try {
- Class> tracingAgentClass =
- tracingAgentClass =
- ClassLoader.getSystemClassLoader().loadClass("datadog.trace.agent.TracingAgent");
- classloaderField = tracingAgentClass.getDeclaredField(fieldName);
+ final Class> agentClass =
+ ClassLoader.getSystemClassLoader().loadClass("datadog.trace.agent.Agent");
+ classloaderField = agentClass.getDeclaredField(fieldName);
classloaderField.setAccessible(true);
return (ClassLoader) classloaderField.get(null);
} catch (final Exception e) {
diff --git a/dd-java-agent/src/test/java/jvmbootstraptest/AgentLoadedChecker.java b/dd-java-agent/src/test/java/jvmbootstraptest/AgentLoadedChecker.java
index 1044e7c4c4..d5eed10bbb 100644
--- a/dd-java-agent/src/test/java/jvmbootstraptest/AgentLoadedChecker.java
+++ b/dd-java-agent/src/test/java/jvmbootstraptest/AgentLoadedChecker.java
@@ -7,12 +7,11 @@ public class AgentLoadedChecker {
public static void main(final String[] args) throws ClassNotFoundException {
// Empty classloader that delegates to bootstrap
final URLClassLoader emptyClassLoader = new URLClassLoader(new URL[] {}, null);
- final Class agentClass = emptyClassLoader.loadClass("datadog.trace.agent.TracingAgent");
+ final Class agentClass = emptyClassLoader.loadClass("datadog.trace.agent.Agent");
if (agentClass.getClassLoader() != null) {
throw new RuntimeException(
- "TracingAgent loaded into classloader other than bootstrap: "
- + agentClass.getClassLoader());
+ "Agent loaded into classloader other than bootstrap: " + agentClass.getClassLoader());
}
}
}
diff --git a/dd-java-agent/src/test/java/jvmbootstraptest/LogManagerSetter.java b/dd-java-agent/src/test/java/jvmbootstraptest/LogManagerSetter.java
index 937a15b89d..1c839e8f19 100644
--- a/dd-java-agent/src/test/java/jvmbootstraptest/LogManagerSetter.java
+++ b/dd-java-agent/src/test/java/jvmbootstraptest/LogManagerSetter.java
@@ -5,6 +5,8 @@ import java.util.logging.LogManager;
public class LogManagerSetter {
public static void main(final String... args) throws Exception {
if (System.getProperty("dd.app.customlogmanager") != null) {
+ System.out.println("dd.app.customlogmanager != null");
+
if (Boolean.valueOf(System.getProperty("dd.app.customlogmanager"))) {
System.setProperty("java.util.logging.manager", CustomLogManager.class.getName());
customAssert(
@@ -15,16 +17,26 @@ public class LogManagerSetter {
"Javaagent should not prevent setting a custom log manager");
} else {
customAssert(
- isJmxfetchStarted(),
+ isTracerInstalled(false),
+ true,
+ "tracer should be installed in premain when customlogmanager=false.");
+ customAssert(
+ isJmxfetchStarted(false),
true,
"jmxfetch should start in premain when customlogmanager=false.");
}
} else if (System.getProperty("java.util.logging.manager") != null) {
+ System.out.println("java.util.logging.manager != null");
+
if (ClassLoader.getSystemResource(
System.getProperty("java.util.logging.manager").replaceAll("\\.", "/") + ".class")
== null) {
customAssert(
- isJmxfetchStarted(),
+ isTracerInstalled(false),
+ false,
+ "tracer install must be delayed when log manager system property is present.");
+ customAssert(
+ isJmxfetchStarted(false),
false,
"jmxfetch startup must be delayed when log manager system property is present.");
// Change back to a valid LogManager.
@@ -35,16 +47,28 @@ public class LogManagerSetter {
.getClassLoader()
.loadClass(System.getProperty("java.util.logging.manager")),
"Javaagent should not prevent setting a custom log manager");
- customAssert(isJmxfetchStarted(), true, "jmxfetch should start after loading LogManager.");
+ customAssert(
+ isTracerInstalled(true), true, "tracer should be installed after loading LogManager.");
+ customAssert(
+ isJmxfetchStarted(true), true, "jmxfetch should start after loading LogManager.");
} else {
customAssert(
- isJmxfetchStarted(),
+ isTracerInstalled(false),
+ true,
+ "tracer should be installed in premain when custom log manager found on classpath.");
+ customAssert(
+ isJmxfetchStarted(false),
true,
"jmxfetch should start in premain when custom log manager found on classpath.");
}
} else if (System.getenv("JBOSS_HOME") != null) {
+ System.out.println("JBOSS_HOME != null");
customAssert(
- isJmxfetchStarted(),
+ isTracerInstalled(false),
+ false,
+ "tracer install must be delayed when JBOSS_HOME property is present.");
+ customAssert(
+ isJmxfetchStarted(false),
false,
"jmxfetch startup must be delayed when JBOSS_HOME property is present.");
@@ -56,10 +80,22 @@ public class LogManagerSetter {
.loadClass(System.getProperty("java.util.logging.manager")),
"Javaagent should not prevent setting a custom log manager");
customAssert(
- isJmxfetchStarted(), true, "jmxfetch should start after loading with JBOSS_HOME SET.");
- } else {
+ isTracerInstalled(true),
+ true,
+ "tracer should be installed after loading with JBOSS_HOME set.");
customAssert(
- isJmxfetchStarted(),
+ isJmxfetchStarted(true),
+ true,
+ "jmxfetch should start after loading with JBOSS_HOME set.");
+ } else {
+ System.out.println("No custom log manager");
+
+ customAssert(
+ isTracerInstalled(false),
+ true,
+ "tracer should be installed in premain when no custom log manager is set");
+ customAssert(
+ isJmxfetchStarted(false),
true,
"jmxfetch should start in premain when no custom log manager is set.");
}
@@ -73,11 +109,40 @@ public class LogManagerSetter {
}
}
- private static boolean isJmxfetchStarted() {
- for (final Thread thread : Thread.getAllStackTraces().keySet()) {
- if ("dd-jmx-collector".equals(thread.getName())) {
+ private static boolean isJmxfetchStarted(final boolean wait) {
+ // Wait up to 10 seconds for jmxfetch thread to appear
+ for (int i = 0; i < 20; i++) {
+ for (final Thread thread : Thread.getAllStackTraces().keySet()) {
+ if ("dd-jmx-collector".equals(thread.getName())) {
+ return true;
+ }
+ }
+ if (!wait) {
+ break;
+ }
+ try {
+ Thread.sleep(500);
+ } catch (final InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ return false;
+ }
+
+ private static boolean isTracerInstalled(final boolean wait) {
+ // Wait up to 10 seconds for tracer to get installed
+ for (int i = 0; i < 20; i++) {
+ if (io.opentracing.util.GlobalTracer.isRegistered()) {
return true;
}
+ if (!wait) {
+ break;
+ }
+ try {
+ Thread.sleep(500);
+ } catch (final InterruptedException e) {
+ e.printStackTrace();
+ }
}
return false;
}
diff --git a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/SpockRunner.java b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/SpockRunner.java
index 97f051659b..c88c38cb2e 100644
--- a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/SpockRunner.java
+++ b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/SpockRunner.java
@@ -32,7 +32,7 @@ public class SpockRunner extends Sputnik {
*/
public static final String[] BOOTSTRAP_PACKAGE_PREFIXES_COPY = {
"datadog.slf4j",
- "datadog.trace.agent.TracingAgent",
+ "datadog.trace.agent",
"datadog.trace.api",
"datadog.trace.bootstrap",
"datadog.trace.context",