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: + * + *

* * @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",