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:
parent
3715a3df53
commit
15c1e67334
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue