Add better exception handling in Agent initialization
An invalid statsd hostname causes an exception for jmxfetch.  This should be an error, but not prevent the app from starting.
Also improved log variable name consistency.
MODIFY
dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java
dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java
@@ -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
* 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 {
private static final String SIMPLE_LOGGER_SHOW_DATE_TIME_PROPERTY =
@@ -28,19 +29,19 @@ public class Agent {
"datadog.slf4j.simpleLogger.defaultLogLevel";
// We cannot use lombok here because we need to configure logger first
- private static final Logger LOGGER;
+ private static final Logger log;
static {
// We can configure logger here because datadog.trace.agent.AgentBootstrap doesn't touch it.
configureLogger();
- LOGGER = LoggerFactory.getLogger(Agent.class);
+ log = 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 {
+ public static void start(final Instrumentation inst, final URL bootstrapURL) {
startDatadogAgent(inst, bootstrapURL);
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.
*/
if (appUsingCustomLogManager) {
- LOGGER.debug("Custom logger detected. Delaying JMXFetch initialization.");
- registerLogManagerCallback(new StartJmxFetchCallback(inst, bootstrapURL));
+ log.debug("Custom logger detected. Delaying JMXFetch initialization.");
+ registerLogManagerCallback(new StartJmxFetchCallback(bootstrapURL));
} else {
- startJmxFetch(inst, bootstrapURL);
+ startJmxFetch(bootstrapURL);
}
/*
@@ -71,28 +72,30 @@ public class Agent {
* logging facility.
*/
if (isJavaBefore9WithJFR() && appUsingCustomLogManager) {
- LOGGER.debug("Custom logger detected. Delaying Datadog Tracer initialization.");
- registerLogManagerCallback(new InstallDatadogTracerCallback(inst, bootstrapURL));
+ log.debug("Custom logger detected. Delaying Datadog Tracer initialization.");
+ registerLogManagerCallback(new InstallDatadogTracerCallback(bootstrapURL));
} else {
- installDatadogTracer(inst, bootstrapURL);
+ installDatadogTracer();
}
}
- private static void registerLogManagerCallback(final Runnable callback) throws Exception {
+ private static void registerLogManagerCallback(final ClassLoadCallBack callback) {
+ try {
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);
+ } catch (final Exception ex) {
+ log.error("Error registering callback for " + callback.getName(), ex);
+ }
}
protected abstract static class ClassLoadCallBack implements Runnable {
- final Instrumentation inst;
final URL bootstrapURL;
- ClassLoadCallBack(final Instrumentation inst, final URL bootstrapURL) {
- this.inst = inst;
+ ClassLoadCallBack(final URL bootstrapURL) {
this.bootstrapURL = bootstrapURL;
}
@@ -111,7 +114,7 @@ public class Agent {
try {
execute();
} 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 void execute() throws Exception;
+ public abstract void execute();
}
protected static class StartJmxFetchCallback extends ClassLoadCallBack {
- StartJmxFetchCallback(final Instrumentation inst, final URL bootstrapURL) {
- super(inst, bootstrapURL);
+ StartJmxFetchCallback(final URL bootstrapURL) {
+ super(bootstrapURL);
}
@Override
@@ -136,14 +139,14 @@ public class Agent {
}
@Override
- public void execute() throws Exception {
- startJmxFetch(inst, bootstrapURL);
+ public void execute() {
+ startJmxFetch(bootstrapURL);
}
}
protected static class InstallDatadogTracerCallback extends ClassLoadCallBack {
- InstallDatadogTracerCallback(final Instrumentation inst, final URL bootstrapURL) {
- super(inst, bootstrapURL);
+ InstallDatadogTracerCallback(final URL bootstrapURL) {
+ super(bootstrapURL);
}
@Override
@@ -152,18 +155,18 @@ public class Agent {
}
@Override
- public void execute() throws Exception {
- installDatadogTracer(inst, bootstrapURL);
+ public void execute() {
+ installDatadogTracer();
}
}
private static synchronized void startDatadogAgent(
- final Instrumentation inst, final URL bootstrapURL) throws Exception {
+ final Instrumentation inst, final URL bootstrapURL) {
if (AGENT_CLASSLOADER == null) {
- final ClassLoader agentClassLoader =
- createDatadogClassLoader("agent-tooling-and-instrumentation.isolated", bootstrapURL);
final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
try {
+ final ClassLoader agentClassLoader =
+ createDatadogClassLoader("agent-tooling-and-instrumentation.isolated", bootstrapURL);
Thread.currentThread().setContextClassLoader(agentClassLoader);
final Class<?> agentInstallerClass =
agentClassLoader.loadClass("datadog.trace.agent.tooling.AgentInstaller");
@@ -171,14 +174,15 @@ public class Agent {
agentInstallerClass.getMethod("installBytebuddyAgent", Instrumentation.class);
agentInstallerMethod.invoke(null, inst);
AGENT_CLASSLOADER = agentClassLoader;
+ } catch (final Throwable ex) {
+ log.error("Throwable thrown while installing the Datadog Agent", ex);
} finally {
Thread.currentThread().setContextClassLoader(contextLoader);
}
}
}
- private static synchronized void installDatadogTracer(
- final Instrumentation inst, final URL bootstrapURL) throws Exception {
+ private static synchronized void installDatadogTracer() {
if (AGENT_CLASSLOADER == null) {
throw new IllegalStateException("Datadog agent should have been started already");
}
@@ -194,24 +198,27 @@ public class Agent {
tracerInstallerMethod.invoke(null);
final Method logVersionInfoMethod = tracerInstallerClass.getMethod("logVersionInfo");
logVersionInfoMethod.invoke(null);
+ } catch (final Throwable ex) {
+ log.error("Throwable thrown while installing the Datadog Tracer", ex);
} finally {
Thread.currentThread().setContextClassLoader(contextLoader);
}
}
- private static synchronized void startJmxFetch(final Instrumentation inst, final URL bootstrapURL)
- throws Exception {
+ private static synchronized void startJmxFetch(final URL bootstrapURL) {
if (JMXFETCH_CLASSLOADER == null) {
- final ClassLoader jmxFetchClassLoader =
- createDatadogClassLoader("agent-jmxfetch.isolated", bootstrapURL);
final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
try {
+ final ClassLoader jmxFetchClassLoader =
+ createDatadogClassLoader("agent-jmxfetch.isolated", bootstrapURL);
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;
+ } catch (final Throwable ex) {
+ log.error("Throwable thrown while starting JmxFetch", ex);
} finally {
Thread.currentThread().setContextClassLoader(contextLoader);
}
@@ -305,8 +312,8 @@ public class Agent {
System.getenv(tracerCustomLogManSysprop.replace('.', '_').toUpperCase());
if (customLogManagerProp != null || customLogManagerEnv != null) {
- LOGGER.debug("Prop - customlogmanager: " + customLogManagerProp);
- LOGGER.debug("Env - customlogmanager: " + customLogManagerEnv);
+ log.debug("Prop - customlogmanager: " + customLogManagerProp);
+ log.debug("Env - customlogmanager: " + customLogManagerEnv);
// Allow setting to skip these automatic checks:
return Boolean.parseBoolean(customLogManagerProp)
|| Boolean.parseBoolean(customLogManagerEnv);
@@ -314,7 +321,7 @@ public class Agent {
final String jbossHome = System.getenv("JBOSS_HOME");
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.
// Originally we were checking for the presence of a jboss class,
// but it seems some non-jboss applications have jboss classes on the classpath.
@@ -327,8 +334,8 @@ public class Agent {
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);
+ log.debug("Prop - logging.manager: " + logManagerProp);
+ log.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.

MODIFY
dd-java-agent/src/main/java/datadog/trace/bootstrap/AgentBootstrap.java
dd-java-agent/src/main/java/datadog/trace/bootstrap/AgentBootstrap.java
@@ -41,12 +41,12 @@ import java.util.regex.Pattern;
*/
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);
}
- public static void agentmain(final String agentArgs, final Instrumentation inst)
- throws Exception {
+ public static void agentmain(final String agentArgs, final Instrumentation inst) {
+ try {
final URL bootstrapURL = installBootstrapJar(inst);
@@ -54,6 +54,10 @@ public class AgentBootstrap {
ClassLoader.getSystemClassLoader().loadClass("datadog.trace.bootstrap.Agent");
final Method startMethod = agentClass.getMethod("start", Instrumentation.class, URL.class);
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)
@@ -106,8 +110,12 @@ public class AgentBootstrap {
throw new RuntimeException("Unable to parse javaagent parameter: " + agentArgument);
}
- bootstrapURL = new URL("file:" + matcher.group(1));
- inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(bootstrapURL.toURI())));
+ final File javaagentFile = new File(matcher.group(1));
+ 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;
}

MODIFY
dd-java-agent/src/test/groovy/datadog/trace/agent/JMXFetchTest.groovy
dd-java-agent/src/test/groovy/datadog/trace/agent/JMXFetchTest.groovy
@@ -2,6 +2,7 @@ package datadog.trace.agent
import datadog.trace.agent.test.IntegrationTestUtils
import datadog.trace.api.Config
+import jvmbootstraptest.AgentLoadedChecker
import org.junit.Rule
import org.junit.contrib.java.lang.system.RestoreSystemProperties
import spock.lang.Specification
@@ -49,6 +50,16 @@ class JMXFetchTest extends Specification {
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"() {
setup:
names.each {

Add better exception handling in Agent initialization

An invalid statsd hostname causes an exception for jmxfetch.  This should be an error, but not prevent the app from starting.

Also improved log variable name consistency.