11package datadog .trace .bootstrap ;
22
33import java .lang .instrument .Instrumentation ;
4+ import java .lang .management .ManagementFactory ;
45import java .lang .reflect .Constructor ;
56import java .lang .reflect .InvocationTargetException ;
67import java .lang .reflect .Method ;
@@ -57,6 +58,8 @@ public static void start(final Instrumentation inst, final URL bootstrapURL) {
5758
5859 final boolean appUsingCustomLogManager = isAppUsingCustomLogManager ();
5960
61+ final boolean appUsingCustomJMXBuilder = isAppUsingCustomJMXBuilder ();
62+
6063 /*
6164 * java.util.logging.LogManager maintains a final static LogManager, which is created during class initialization.
6265 *
@@ -69,8 +72,15 @@ public static void start(final Instrumentation inst, final URL bootstrapURL) {
6972 *
7073 * Once we see the LogManager class loading, it's safe to start jmxfetch because the application is already setting
7174 * the global log manager and jmxfetch won't be able to touch it due to classloader locking.
75+ *
76+ * Likewise if a custom JMX builder is configured which is not on the system classpath then we delay starting
77+ * JMXFetch until we detect the custom MBeanServerBuilder is being used. This takes precedence over the custom
78+ * log manager check because any custom log manager will be installed before any custom MBeanServerBuilder.
7279 */
73- if (appUsingCustomLogManager ) {
80+ if (appUsingCustomJMXBuilder ) {
81+ log .debug ("Custom JMX builder detected. Delaying JMXFetch initialization." );
82+ registerMBeanServerBuilderCallback (new StartJmxCallback (bootstrapURL ));
83+ } else if (appUsingCustomLogManager ) {
7484 log .debug ("Custom logger detected. Delaying JMXFetch initialization." );
7585 registerLogManagerCallback (new StartJmxCallback (bootstrapURL ));
7686 } else {
@@ -114,6 +124,18 @@ private static void registerLogManagerCallback(final ClassLoadCallBack callback)
114124 }
115125 }
116126
127+ private static void registerMBeanServerBuilderCallback (final ClassLoadCallBack callback ) {
128+ try {
129+ final Class <?> agentInstallerClass =
130+ AGENT_CLASSLOADER .loadClass ("datadog.trace.agent.tooling.AgentInstaller" );
131+ final Method registerCallbackMethod =
132+ agentInstallerClass .getMethod ("registerClassLoadCallback" , String .class , Runnable .class );
133+ registerCallbackMethod .invoke (null , "javax.management.MBeanServerBuilder" , callback );
134+ } catch (final Exception ex ) {
135+ log .error ("Error registering callback for " + callback .getName (), ex );
136+ }
137+ }
138+
117139 protected abstract static class ClassLoadCallBack implements Runnable {
118140
119141 final URL bootstrapURL ;
@@ -260,6 +282,9 @@ private static synchronized void installDatadogTracer() {
260282 }
261283
262284 private static synchronized void startJmx (final URL bootstrapURL ) {
285+ // load core JMX using the inherited thread-context-classloader
286+ ManagementFactory .getPlatformMBeanServer ();
287+
263288 startJmxFetch (bootstrapURL );
264289
265290 if (AGENT_CLASSLOADER == null ) {
@@ -508,6 +533,41 @@ private static boolean isAppUsingCustomLogManager() {
508533 return false ;
509534 }
510535
536+ /**
537+ * Search for java or datadog-tracer sysprops which indicate that a custom JMX builder will be
538+ * used.
539+ *
540+ * @return true if we detect a custom JMX builder being used.
541+ */
542+ private static boolean isAppUsingCustomJMXBuilder () {
543+ final String tracerCustomJMXBuilderSysprop = "dd.app.customjmxbuilder" ;
544+ final String customJMXBuilderProp = System .getProperty (tracerCustomJMXBuilderSysprop );
545+ final String customJMXBuilderEnv =
546+ System .getenv (tracerCustomJMXBuilderSysprop .replace ('.' , '_' ).toUpperCase ());
547+
548+ if (customJMXBuilderProp != null || customJMXBuilderEnv != null ) {
549+ log .debug ("Prop - customjmxbuilder: " + customJMXBuilderProp );
550+ log .debug ("Env - customjmxbuilder: " + customJMXBuilderEnv );
551+ // Allow setting to skip these automatic checks:
552+ return Boolean .parseBoolean (customJMXBuilderProp )
553+ || Boolean .parseBoolean (customJMXBuilderEnv );
554+ }
555+
556+ final String jmxBuilderProp = System .getProperty ("javax.management.builder.initial" );
557+ if (jmxBuilderProp != null ) {
558+ final boolean onSysClasspath =
559+ ClassLoader .getSystemResource (jmxBuilderProp .replaceAll ("\\ ." , "/" ) + ".class" ) != null ;
560+ log .debug ("Prop - javax.management.builder.initial: " + jmxBuilderProp );
561+ log .debug ("javax.management.builder.initial on system classpath: " + onSysClasspath );
562+ // Some applications set javax.management.builder.initial but never actually initialize JMX.
563+ // Check to see if the configured JMX builder is on the system classpath.
564+ // If so, it should be safe to initialize jmxfetch which will setup JMX.
565+ return !onSysClasspath ;
566+ }
567+
568+ return false ;
569+ }
570+
511571 private static boolean isJavaBefore9 () {
512572 return System .getProperty ("java.version" ).startsWith ("1." );
513573 }
0 commit comments