Merge pull request #1146 from DataDog/tyler/boostrap-log-exception

Add better exception handling in Agent initialization
This commit is contained in:
Tyler Benson 2019-12-17 22:03:42 -08:00 committed by GitHub
commit b3f15ca133
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 79 additions and 53 deletions

View File

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

View File

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

View File

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

View File

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