11package dev .braintrust .system ;
22
3- import dev .braintrust .bootstrap .BraintrustBridge ;
4- import dev .braintrust .bootstrap .BraintrustClassLoader ;
53import java .io .File ;
64import java .lang .instrument .Instrumentation ;
75import java .net .URL ;
6+ import java .net .URLClassLoader ;
87import java .util .jar .JarFile ;
98
109/**
@@ -44,18 +43,16 @@ private static synchronized void install(String agentArgs, Instrumentation inst)
4443
4544 if (jvmRunningWithOtelAgent ()) {
4645 log (
47- "ERROR: Braintrust agent is not yet compatible with the OTel javaagent - "
48- + " skipping install." );
46+ "ERROR: Braintrust agent is not yet compatible with the OTel -javaagent. "
47+ + " aborting install." );
4948 return ;
5049 }
51-
52- if (jvmRunningWithDatadogOtel ()) {
53- log (
54- "ERROR: Braintrust agent is not yet compatible with datadog javaagent otel -"
55- + " skipping install." );
50+ if (jvmRunningWithDatadogOtel () && (!isRunningAfterDatadogAgent ())) {
51+ log ("ERROR: Braintrust agent must run _after_ datadog -javaagent. aborting install." );
5652 return ;
5753 }
5854
55+ boolean installOnBootstrap = !jvmRunningWithDatadogOtel ();
5956 try {
6057 // Locate the agent JAR from our own code source
6158 URL agentJarURL =
@@ -67,19 +64,21 @@ private static synchronized void install(String agentArgs, Instrumentation inst)
6764 // are set before anything can trigger GlobalOpenTelemetry.get().
6865 enableOtelSDKAutoconfiguration ();
6966
70- inst .appendToBootstrapClassLoaderSearch (new JarFile (agentJarFile , false ));
71- log ("Added agent JAR to bootstrap classpath." );
72-
73- // Create the isolated braintrust classloader.
74- // Parent is the platform classloader so agent internals can see:
75- // - Bootstrap classes (OTel API/SDK added via appendToBootstrapClassLoaderSearch)
76- // - JDK platform modules (java.net.http, java.sql, etc.)
77- // but NOT application classes (those are on the system/app classloader).
78- BraintrustClassLoader btClassLoader =
79- new BraintrustClassLoader (agentJarURL , ClassLoader .getPlatformClassLoader ());
80- BraintrustBridge .setAgentClassLoaderIfAbsent (btClassLoader );
67+ ClassLoader btClassLoaderParent ;
68+ if (installOnBootstrap ) {
69+ inst .appendToBootstrapClassLoaderSearch (new JarFile (agentJarFile , false ));
70+ btClassLoaderParent = ClassLoader .getPlatformClassLoader ();
71+ log ("Added agent JAR to bootstrap classpath." );
72+ } else {
73+ btClassLoaderParent =
74+ new URLClassLoader (
75+ new URL [] {agentJarFile .toURI ().toURL ()},
76+ ClassLoader .getPlatformClassLoader ());
77+ log ("skipping bootstrap classpath setup" );
78+ }
8179
8280 // Load and invoke the real agent installer through the isolated classloader.
81+ ClassLoader btClassLoader = createBTClassLoader (agentJarURL , btClassLoaderParent );
8382 Class <?> installerClass = btClassLoader .loadClass (AGENT_CLASS );
8483 installerClass
8584 .getMethod (INSTALLER_METHOD , String .class , Instrumentation .class )
@@ -92,6 +91,16 @@ private static synchronized void install(String agentArgs, Instrumentation inst)
9291 }
9392 }
9493
94+ private static ClassLoader createBTClassLoader (URL agentJarURL , ClassLoader btClassLoaderParent )
95+ throws Exception {
96+ // NOTE: not caching because we only invoke this once
97+ var bridgeClass =
98+ btClassLoaderParent .loadClass ("dev.braintrust.bootstrap.BraintrustBridge" );
99+ var createMethod =
100+ bridgeClass .getMethod ("createBraintrustClassLoader" , URL .class , ClassLoader .class );
101+ return (ClassLoader ) createMethod .invoke (null , agentJarURL , btClassLoaderParent );
102+ }
103+
95104 /**
96105 * Checks whether the OpenTelemetry Java agent is present by looking for its premain class on
97106 * the system classloader. Since {@code -javaagent} JARs are always on the system classpath,
@@ -109,16 +118,8 @@ private static boolean jvmRunningWithOtelAgent() {
109118 }
110119 }
111120
112- /**
113- * Checks whether the Datadog agent is present and configured for OTel integration. Must be
114- * callable from the system classloader (no DD compile deps).
115- */
121+ /** Checks whether the Datadog agent is present and configured for OTel integration */
116122 private static boolean jvmRunningWithDatadogOtel () {
117- try {
118- Class .forName ("datadog.trace.bootstrap.Agent" , false , null );
119- } catch (ClassNotFoundException e ) {
120- return false ;
121- }
122123 String sysProp = System .getProperty ("dd.trace.otel.enabled" );
123124 if (sysProp != null ) {
124125 return Boolean .parseBoolean (sysProp );
@@ -131,7 +132,7 @@ private static boolean jvmRunningWithDatadogOtel() {
131132 * Returns true if the Datadog agent's premain has already executed, meaning it was listed
132133 * before the Braintrust agent in the {@code -javaagent} flags.
133134 */
134- static boolean isRunningAfterDatadogAgent () {
135+ private static boolean isRunningAfterDatadogAgent () {
135136 // DD's premain appends its jars to the bootstrap classpath, making
136137 // {@code datadog.trace.bootstrap.Agent} loadable from the bootstrap (null)
137138 // classloader. If that class is not found on bootstrap, DD either isn't
0 commit comments