Merge pull request #1146 from DataDog/tyler/boostrap-log-exception
Add better exception handling in Agent initialization
This commit is contained in:
commit
b3f15ca133
|
@ -16,6 +16,7 @@ import org.slf4j.LoggerFactory;
|
||||||
* <p>The intention is for this class to be loaded by bootstrap classloader to make sure we have
|
* <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.
|
* unimpeded access to the rest of Datadog's agent parts.
|
||||||
*/
|
*/
|
||||||
|
// We cannot use lombok here because we need to configure logger first
|
||||||
public class Agent {
|
public class Agent {
|
||||||
|
|
||||||
private static final String SIMPLE_LOGGER_SHOW_DATE_TIME_PROPERTY =
|
private static final String SIMPLE_LOGGER_SHOW_DATE_TIME_PROPERTY =
|
||||||
|
@ -28,19 +29,19 @@ public class Agent {
|
||||||
"datadog.slf4j.simpleLogger.defaultLogLevel";
|
"datadog.slf4j.simpleLogger.defaultLogLevel";
|
||||||
|
|
||||||
// We cannot use lombok here because we need to configure logger first
|
// We cannot use lombok here because we need to configure logger first
|
||||||
private static final Logger LOGGER;
|
private static final Logger log;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// We can configure logger here because datadog.trace.agent.AgentBootstrap doesn't touch it.
|
// We can configure logger here because datadog.trace.agent.AgentBootstrap doesn't touch it.
|
||||||
configureLogger();
|
configureLogger();
|
||||||
LOGGER = LoggerFactory.getLogger(Agent.class);
|
log = LoggerFactory.getLogger(Agent.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fields must be managed under class lock
|
// fields must be managed under class lock
|
||||||
private static ClassLoader AGENT_CLASSLOADER = null;
|
private static ClassLoader AGENT_CLASSLOADER = null;
|
||||||
private static ClassLoader JMXFETCH_CLASSLOADER = null;
|
private static ClassLoader JMXFETCH_CLASSLOADER = null;
|
||||||
|
|
||||||
public static void start(final Instrumentation inst, final URL bootstrapURL) throws Exception {
|
public static void start(final Instrumentation inst, final URL bootstrapURL) {
|
||||||
startDatadogAgent(inst, bootstrapURL);
|
startDatadogAgent(inst, bootstrapURL);
|
||||||
|
|
||||||
final boolean appUsingCustomLogManager = isAppUsingCustomLogManager();
|
final boolean appUsingCustomLogManager = isAppUsingCustomLogManager();
|
||||||
|
@ -59,10 +60,10 @@ public class Agent {
|
||||||
* the global log manager and jmxfetch won't be able to touch it due to classloader locking.
|
* the global log manager and jmxfetch won't be able to touch it due to classloader locking.
|
||||||
*/
|
*/
|
||||||
if (appUsingCustomLogManager) {
|
if (appUsingCustomLogManager) {
|
||||||
LOGGER.debug("Custom logger detected. Delaying JMXFetch initialization.");
|
log.debug("Custom logger detected. Delaying JMXFetch initialization.");
|
||||||
registerLogManagerCallback(new StartJmxFetchCallback(inst, bootstrapURL));
|
registerLogManagerCallback(new StartJmxFetchCallback(bootstrapURL));
|
||||||
} else {
|
} else {
|
||||||
startJmxFetch(inst, bootstrapURL);
|
startJmxFetch(bootstrapURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -71,28 +72,30 @@ public class Agent {
|
||||||
* logging facility.
|
* logging facility.
|
||||||
*/
|
*/
|
||||||
if (isJavaBefore9WithJFR() && appUsingCustomLogManager) {
|
if (isJavaBefore9WithJFR() && appUsingCustomLogManager) {
|
||||||
LOGGER.debug("Custom logger detected. Delaying Datadog Tracer initialization.");
|
log.debug("Custom logger detected. Delaying Datadog Tracer initialization.");
|
||||||
registerLogManagerCallback(new InstallDatadogTracerCallback(inst, bootstrapURL));
|
registerLogManagerCallback(new InstallDatadogTracerCallback(bootstrapURL));
|
||||||
} else {
|
} else {
|
||||||
installDatadogTracer(inst, bootstrapURL);
|
installDatadogTracer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void registerLogManagerCallback(final Runnable callback) throws Exception {
|
private static void registerLogManagerCallback(final ClassLoadCallBack callback) {
|
||||||
final Class<?> agentInstallerClass =
|
try {
|
||||||
AGENT_CLASSLOADER.loadClass("datadog.trace.agent.tooling.AgentInstaller");
|
final Class<?> agentInstallerClass =
|
||||||
final Method registerCallbackMethod =
|
AGENT_CLASSLOADER.loadClass("datadog.trace.agent.tooling.AgentInstaller");
|
||||||
agentInstallerClass.getMethod("registerClassLoadCallback", String.class, Runnable.class);
|
final Method registerCallbackMethod =
|
||||||
registerCallbackMethod.invoke(null, "java.util.logging.LogManager", callback);
|
agentInstallerClass.getMethod("registerClassLoadCallback", String.class, Runnable.class);
|
||||||
|
registerCallbackMethod.invoke(null, "java.util.logging.LogManager", callback);
|
||||||
|
} catch (final Exception ex) {
|
||||||
|
log.error("Error registering callback for " + callback.getName(), ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract static class ClassLoadCallBack implements Runnable {
|
protected abstract static class ClassLoadCallBack implements Runnable {
|
||||||
|
|
||||||
final Instrumentation inst;
|
|
||||||
final URL bootstrapURL;
|
final URL bootstrapURL;
|
||||||
|
|
||||||
ClassLoadCallBack(final Instrumentation inst, final URL bootstrapURL) {
|
ClassLoadCallBack(final URL bootstrapURL) {
|
||||||
this.inst = inst;
|
|
||||||
this.bootstrapURL = bootstrapURL;
|
this.bootstrapURL = bootstrapURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +114,7 @@ public class Agent {
|
||||||
try {
|
try {
|
||||||
execute();
|
execute();
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
LOGGER.error("Failed to run class loader callback {}", getName(), e);
|
log.error("Failed to run class loader callback {}", getName(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -122,12 +125,12 @@ public class Agent {
|
||||||
|
|
||||||
public abstract String getName();
|
public abstract String getName();
|
||||||
|
|
||||||
public abstract void execute() throws Exception;
|
public abstract void execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class StartJmxFetchCallback extends ClassLoadCallBack {
|
protected static class StartJmxFetchCallback extends ClassLoadCallBack {
|
||||||
StartJmxFetchCallback(final Instrumentation inst, final URL bootstrapURL) {
|
StartJmxFetchCallback(final URL bootstrapURL) {
|
||||||
super(inst, bootstrapURL);
|
super(bootstrapURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -136,14 +139,14 @@ public class Agent {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute() throws Exception {
|
public void execute() {
|
||||||
startJmxFetch(inst, bootstrapURL);
|
startJmxFetch(bootstrapURL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class InstallDatadogTracerCallback extends ClassLoadCallBack {
|
protected static class InstallDatadogTracerCallback extends ClassLoadCallBack {
|
||||||
InstallDatadogTracerCallback(final Instrumentation inst, final URL bootstrapURL) {
|
InstallDatadogTracerCallback(final URL bootstrapURL) {
|
||||||
super(inst, bootstrapURL);
|
super(bootstrapURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -152,18 +155,18 @@ public class Agent {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute() throws Exception {
|
public void execute() {
|
||||||
installDatadogTracer(inst, bootstrapURL);
|
installDatadogTracer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static synchronized void startDatadogAgent(
|
private static synchronized void startDatadogAgent(
|
||||||
final Instrumentation inst, final URL bootstrapURL) throws Exception {
|
final Instrumentation inst, final URL bootstrapURL) {
|
||||||
if (AGENT_CLASSLOADER == null) {
|
if (AGENT_CLASSLOADER == null) {
|
||||||
final ClassLoader agentClassLoader =
|
|
||||||
createDatadogClassLoader("agent-tooling-and-instrumentation.isolated", bootstrapURL);
|
|
||||||
final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
|
final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
|
||||||
try {
|
try {
|
||||||
|
final ClassLoader agentClassLoader =
|
||||||
|
createDatadogClassLoader("agent-tooling-and-instrumentation.isolated", bootstrapURL);
|
||||||
Thread.currentThread().setContextClassLoader(agentClassLoader);
|
Thread.currentThread().setContextClassLoader(agentClassLoader);
|
||||||
final Class<?> agentInstallerClass =
|
final Class<?> agentInstallerClass =
|
||||||
agentClassLoader.loadClass("datadog.trace.agent.tooling.AgentInstaller");
|
agentClassLoader.loadClass("datadog.trace.agent.tooling.AgentInstaller");
|
||||||
|
@ -171,14 +174,15 @@ public class Agent {
|
||||||
agentInstallerClass.getMethod("installBytebuddyAgent", Instrumentation.class);
|
agentInstallerClass.getMethod("installBytebuddyAgent", Instrumentation.class);
|
||||||
agentInstallerMethod.invoke(null, inst);
|
agentInstallerMethod.invoke(null, inst);
|
||||||
AGENT_CLASSLOADER = agentClassLoader;
|
AGENT_CLASSLOADER = agentClassLoader;
|
||||||
|
} catch (final Throwable ex) {
|
||||||
|
log.error("Throwable thrown while installing the Datadog Agent", ex);
|
||||||
} finally {
|
} finally {
|
||||||
Thread.currentThread().setContextClassLoader(contextLoader);
|
Thread.currentThread().setContextClassLoader(contextLoader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static synchronized void installDatadogTracer(
|
private static synchronized void installDatadogTracer() {
|
||||||
final Instrumentation inst, final URL bootstrapURL) throws Exception {
|
|
||||||
if (AGENT_CLASSLOADER == null) {
|
if (AGENT_CLASSLOADER == null) {
|
||||||
throw new IllegalStateException("Datadog agent should have been started already");
|
throw new IllegalStateException("Datadog agent should have been started already");
|
||||||
}
|
}
|
||||||
|
@ -194,24 +198,27 @@ public class Agent {
|
||||||
tracerInstallerMethod.invoke(null);
|
tracerInstallerMethod.invoke(null);
|
||||||
final Method logVersionInfoMethod = tracerInstallerClass.getMethod("logVersionInfo");
|
final Method logVersionInfoMethod = tracerInstallerClass.getMethod("logVersionInfo");
|
||||||
logVersionInfoMethod.invoke(null);
|
logVersionInfoMethod.invoke(null);
|
||||||
|
} catch (final Throwable ex) {
|
||||||
|
log.error("Throwable thrown while installing the Datadog Tracer", ex);
|
||||||
} finally {
|
} finally {
|
||||||
Thread.currentThread().setContextClassLoader(contextLoader);
|
Thread.currentThread().setContextClassLoader(contextLoader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static synchronized void startJmxFetch(final Instrumentation inst, final URL bootstrapURL)
|
private static synchronized void startJmxFetch(final URL bootstrapURL) {
|
||||||
throws Exception {
|
|
||||||
if (JMXFETCH_CLASSLOADER == null) {
|
if (JMXFETCH_CLASSLOADER == null) {
|
||||||
final ClassLoader jmxFetchClassLoader =
|
|
||||||
createDatadogClassLoader("agent-jmxfetch.isolated", bootstrapURL);
|
|
||||||
final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
|
final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
|
||||||
try {
|
try {
|
||||||
|
final ClassLoader jmxFetchClassLoader =
|
||||||
|
createDatadogClassLoader("agent-jmxfetch.isolated", bootstrapURL);
|
||||||
Thread.currentThread().setContextClassLoader(jmxFetchClassLoader);
|
Thread.currentThread().setContextClassLoader(jmxFetchClassLoader);
|
||||||
final Class<?> jmxFetchAgentClass =
|
final Class<?> jmxFetchAgentClass =
|
||||||
jmxFetchClassLoader.loadClass("datadog.trace.agent.jmxfetch.JMXFetch");
|
jmxFetchClassLoader.loadClass("datadog.trace.agent.jmxfetch.JMXFetch");
|
||||||
final Method jmxFetchInstallerMethod = jmxFetchAgentClass.getMethod("run");
|
final Method jmxFetchInstallerMethod = jmxFetchAgentClass.getMethod("run");
|
||||||
jmxFetchInstallerMethod.invoke(null);
|
jmxFetchInstallerMethod.invoke(null);
|
||||||
JMXFETCH_CLASSLOADER = jmxFetchClassLoader;
|
JMXFETCH_CLASSLOADER = jmxFetchClassLoader;
|
||||||
|
} catch (final Throwable ex) {
|
||||||
|
log.error("Throwable thrown while starting JmxFetch", ex);
|
||||||
} finally {
|
} finally {
|
||||||
Thread.currentThread().setContextClassLoader(contextLoader);
|
Thread.currentThread().setContextClassLoader(contextLoader);
|
||||||
}
|
}
|
||||||
|
@ -305,8 +312,8 @@ public class Agent {
|
||||||
System.getenv(tracerCustomLogManSysprop.replace('.', '_').toUpperCase());
|
System.getenv(tracerCustomLogManSysprop.replace('.', '_').toUpperCase());
|
||||||
|
|
||||||
if (customLogManagerProp != null || customLogManagerEnv != null) {
|
if (customLogManagerProp != null || customLogManagerEnv != null) {
|
||||||
LOGGER.debug("Prop - customlogmanager: " + customLogManagerProp);
|
log.debug("Prop - customlogmanager: " + customLogManagerProp);
|
||||||
LOGGER.debug("Env - customlogmanager: " + customLogManagerEnv);
|
log.debug("Env - customlogmanager: " + customLogManagerEnv);
|
||||||
// Allow setting to skip these automatic checks:
|
// Allow setting to skip these automatic checks:
|
||||||
return Boolean.parseBoolean(customLogManagerProp)
|
return Boolean.parseBoolean(customLogManagerProp)
|
||||||
|| Boolean.parseBoolean(customLogManagerEnv);
|
|| Boolean.parseBoolean(customLogManagerEnv);
|
||||||
|
@ -314,7 +321,7 @@ public class Agent {
|
||||||
|
|
||||||
final String jbossHome = System.getenv("JBOSS_HOME");
|
final String jbossHome = System.getenv("JBOSS_HOME");
|
||||||
if (jbossHome != null) {
|
if (jbossHome != null) {
|
||||||
LOGGER.debug("Env - jboss: " + jbossHome);
|
log.debug("Env - jboss: " + jbossHome);
|
||||||
// JBoss/Wildfly is known to set a custom log manager after startup.
|
// JBoss/Wildfly is known to set a custom log manager after startup.
|
||||||
// Originally we were checking for the presence of a jboss class,
|
// Originally we were checking for the presence of a jboss class,
|
||||||
// but it seems some non-jboss applications have jboss classes on the classpath.
|
// but it seems some non-jboss applications have jboss classes on the classpath.
|
||||||
|
@ -327,8 +334,8 @@ public class Agent {
|
||||||
if (logManagerProp != null) {
|
if (logManagerProp != null) {
|
||||||
final boolean onSysClasspath =
|
final boolean onSysClasspath =
|
||||||
ClassLoader.getSystemResource(logManagerProp.replaceAll("\\.", "/") + ".class") != null;
|
ClassLoader.getSystemResource(logManagerProp.replaceAll("\\.", "/") + ".class") != null;
|
||||||
LOGGER.debug("Prop - logging.manager: " + logManagerProp);
|
log.debug("Prop - logging.manager: " + logManagerProp);
|
||||||
LOGGER.debug("logging.manager on system classpath: " + onSysClasspath);
|
log.debug("logging.manager on system classpath: " + onSysClasspath);
|
||||||
// Some applications set java.util.logging.manager but never actually initialize the logger.
|
// 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.
|
// 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.
|
// If so, it should be safe to initialize jmxfetch which will setup the log manager.
|
||||||
|
|
|
@ -41,19 +41,23 @@ import java.util.regex.Pattern;
|
||||||
*/
|
*/
|
||||||
public class AgentBootstrap {
|
public class AgentBootstrap {
|
||||||
|
|
||||||
public static void premain(final String agentArgs, final Instrumentation inst) throws Exception {
|
public static void premain(final String agentArgs, final Instrumentation inst) {
|
||||||
agentmain(agentArgs, inst);
|
agentmain(agentArgs, inst);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void agentmain(final String agentArgs, final Instrumentation inst)
|
public static void agentmain(final String agentArgs, final Instrumentation inst) {
|
||||||
throws Exception {
|
try {
|
||||||
|
|
||||||
final URL bootstrapURL = installBootstrapJar(inst);
|
final URL bootstrapURL = installBootstrapJar(inst);
|
||||||
|
|
||||||
final Class<?> agentClass =
|
final Class<?> agentClass =
|
||||||
ClassLoader.getSystemClassLoader().loadClass("datadog.trace.bootstrap.Agent");
|
ClassLoader.getSystemClassLoader().loadClass("datadog.trace.bootstrap.Agent");
|
||||||
final Method startMethod = agentClass.getMethod("start", Instrumentation.class, URL.class);
|
final Method startMethod = agentClass.getMethod("start", Instrumentation.class, URL.class);
|
||||||
startMethod.invoke(null, inst, bootstrapURL);
|
startMethod.invoke(null, inst, bootstrapURL);
|
||||||
|
} catch (final Throwable ex) {
|
||||||
|
// Don't rethrow. We don't have a log manager here, so just print.
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static synchronized URL installBootstrapJar(final Instrumentation inst)
|
private static synchronized URL installBootstrapJar(final Instrumentation inst)
|
||||||
|
@ -106,8 +110,12 @@ public class AgentBootstrap {
|
||||||
throw new RuntimeException("Unable to parse javaagent parameter: " + agentArgument);
|
throw new RuntimeException("Unable to parse javaagent parameter: " + agentArgument);
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrapURL = new URL("file:" + matcher.group(1));
|
final File javaagentFile = new File(matcher.group(1));
|
||||||
inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(bootstrapURL.toURI())));
|
if (!(javaagentFile.exists() || javaagentFile.isFile())) {
|
||||||
|
throw new RuntimeException("Unable to find javaagent file: " + javaagentFile);
|
||||||
|
}
|
||||||
|
bootstrapURL = javaagentFile.toURI().toURL();
|
||||||
|
inst.appendToBootstrapClassLoaderSearch(new JarFile(javaagentFile));
|
||||||
|
|
||||||
return bootstrapURL;
|
return bootstrapURL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import jvmbootstraptest.AgentLoadedChecker
|
||||||
import spock.lang.Specification
|
import spock.lang.Specification
|
||||||
|
|
||||||
class AgentLoadedIntoBootstrapTest extends Specification {
|
class AgentLoadedIntoBootstrapTest extends Specification {
|
||||||
|
|
||||||
def "Agent loads in when separate jvm is launched"() {
|
def "Agent loads in when separate jvm is launched"() {
|
||||||
expect:
|
expect:
|
||||||
IntegrationTestUtils.runOnSeparateJvm(AgentLoadedChecker.getName()
|
IntegrationTestUtils.runOnSeparateJvm(AgentLoadedChecker.getName()
|
||||||
|
|
|
@ -2,6 +2,7 @@ package datadog.trace.agent
|
||||||
|
|
||||||
import datadog.trace.agent.test.IntegrationTestUtils
|
import datadog.trace.agent.test.IntegrationTestUtils
|
||||||
import datadog.trace.api.Config
|
import datadog.trace.api.Config
|
||||||
|
import jvmbootstraptest.AgentLoadedChecker
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.contrib.java.lang.system.RestoreSystemProperties
|
import org.junit.contrib.java.lang.system.RestoreSystemProperties
|
||||||
import spock.lang.Specification
|
import spock.lang.Specification
|
||||||
|
@ -49,6 +50,16 @@ class JMXFetchTest extends Specification {
|
||||||
Thread.currentThread().setContextClassLoader(currentContextLoader)
|
Thread.currentThread().setContextClassLoader(currentContextLoader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def "Agent loads when JmxFetch is misconfigured"() {
|
||||||
|
// verify the agent starts up correctly with a bogus address.
|
||||||
|
expect:
|
||||||
|
IntegrationTestUtils.runOnSeparateJvm(AgentLoadedChecker.getName()
|
||||||
|
, ["-Ddd.jmxfetch.enabled=true", "-Ddd.jmxfetch.statsd.host=example.local"] as String[]
|
||||||
|
, "" as String[]
|
||||||
|
, [:]
|
||||||
|
, true) == 0
|
||||||
|
}
|
||||||
|
|
||||||
def "test jmxfetch config"() {
|
def "test jmxfetch config"() {
|
||||||
setup:
|
setup:
|
||||||
names.each {
|
names.each {
|
||||||
|
|
Loading…
Reference in New Issue