Fix agent on latest zulu8

The problem was that on zulu8 loading OkHttp touches JFR which in turn
touches log manager - which would break things like JBOSS.

The fix is to delay installing agent (and writer) until log manager
things have settled down - in way similar to jmxfetch.

Unfortunately for 'main' agent this turns out to be more involved
because of classloader shenanigans.
This commit is contained in:
Nikolay Martynov 2019-11-06 10:09:50 -05:00
parent 3715a3df53
commit 15c1e67334
13 changed files with 664 additions and 473 deletions

View File

@ -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.
*
* <p>This class is loaded and called by {@code datadog.trace.agent.AgentBootstrap}
*
* <p>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.");
}
}

View File

@ -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<String, Runnable> classLoadCallbacks = new ConcurrentHashMap<>();
private static final Map<String, List<Runnable>> 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.
*
* <p>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.
* <p>Caveats:
*
* <ul>
* <li>This callback will be invoked by a jvm class transformer.
* <li>Classes filtered out by {@link AgentInstaller}'s skip list will not be matched.
* </ul>
*
* @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<Runnable> 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<String, Runnable> entry : classLoadCallbacks.entrySet()) {
if (entry.getKey().equals(typeName)) {
entry.getValue().run();
synchronized (CLASS_LOAD_CALLBACKS) {
final List<Runnable> callbacks = CLASS_LOAD_CALLBACKS.get(typeName);
if (callbacks != null) {
for (final Runnable callback : callbacks) {
callback.run();
}
}
}
}

View File

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

View File

@ -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 =

View File

@ -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,
)

View File

@ -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:

View File

@ -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.
*
* <p>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.
*
* <p>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.
*
* <p>In order to avoid this we need to make sure we do a few things:
*
* <ul>
* <li>Do as little as possible here
* <li>Never reference this class after we have setup bootstrap and jumped over to 'real' agent
* code
* <li>Do not store any static data in this class
* <li>Do dot touch any logging facilities here so we can configure them later
* </ul>
*/
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<String> 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<String> 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<String>) 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();
}
}

View File

@ -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<String> 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<String> 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<String>) 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();
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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());
}
}
}

View File

@ -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;
}

View File

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