---
docs/contributing/debugging.md | 2 +-
.../javaagent/bootstrap/AgentInitializer.java | 170 +---------------
.../javaagent/spi/ComponentInstaller.java | 32 +++
.../javaagent/tooling/AgentInstaller.java | 192 +++++++++++++++---
...aller.java => OpenTelemetryInstaller.java} | 45 +++-
.../instrumentation/test/AgentTestRunner.java | 28 +--
.../test/TestOpenTelemetryInstaller.java | 40 ++++
testing-common/testing-common.gradle | 3 +-
8 files changed, 294 insertions(+), 218 deletions(-)
create mode 100644 javaagent-spi/src/main/java/io/opentelemetry/javaagent/spi/ComponentInstaller.java
rename javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/{TracerInstaller.java => OpenTelemetryInstaller.java} (81%)
create mode 100644 testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/TestOpenTelemetryInstaller.java
diff --git a/docs/contributing/debugging.md b/docs/contributing/debugging.md
index 8bc98d87e98e..28a28aec4cac 100644
--- a/docs/contributing/debugging.md
+++ b/docs/contributing/debugging.md
@@ -24,7 +24,7 @@ Thread.dumpStack()
#### Agent initialization code
If you want to debug agent initialization code (e.g. `OpenTelemetryAgent`, `AgentInitializer`,
-`AgentInstaller`, `TracerInstaller`, etc.) then it's important to specify the `-agentlib:` JVM arg
+`AgentInstaller`, `OpenTelemetryInstaller`, etc.) then it's important to specify the `-agentlib:` JVM arg
before the `-javaagent:` JVM arg and use `suspend=y` (see full example below).
#### Enabling debugging
diff --git a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/AgentInitializer.java b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/AgentInitializer.java
index f427bfba2d29..35e620140f36 100644
--- a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/AgentInitializer.java
+++ b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/AgentInitializer.java
@@ -48,90 +48,6 @@ public class AgentInitializer {
public static void initialize(Instrumentation inst, URL bootstrapUrl) {
startAgent(inst, bootstrapUrl);
-
- boolean appUsingCustomLogManager = isAppUsingCustomLogManager();
-
- /*
- * java.util.logging.LogManager maintains a final static LogManager, which is created during class initialization.
- *
- * JMXFetch uses jre bootstrap classes which touch this class. This means applications which require a custom log
- * manager may not have a chance to set the global log manager if jmxfetch runs first. JMXFetch will incorrectly
- * set the global log manager in cases where the app sets the log manager system property or when the log manager
- * class is not on the system classpath.
- *
- * Our solution is to delay the initialization of jmxfetch when we detect a custom log manager being used.
- *
- * Once we see the LogManager class loading, it's safe to start jmxfetch because the application is already setting
- * the global log manager and jmxfetch won't be able to touch it due to classloader locking.
- */
-
- /*
- * Similar thing happens with AgentTracer on (at least) zulu-8 because it uses OkHttp which indirectly loads JFR
- * events which in turn loads LogManager. This is not a problem on newer JDKs because there JFR uses different
- * logging facility.
- */
- if (isJavaBefore9WithJfr() && appUsingCustomLogManager) {
- log.debug("Custom logger detected. Delaying Agent Tracer initialization.");
- registerLogManagerCallback(new InstallAgentTracerCallback());
- } else {
- installAgentTracer();
- }
- }
-
- private static void registerLogManagerCallback(ClassLoadCallBack callback) {
- try {
- Class> agentInstallerClass =
- AGENT_CLASSLOADER.loadClass("io.opentelemetry.javaagent.tooling.AgentInstaller");
- Method registerCallbackMethod =
- agentInstallerClass.getMethod("registerClassLoadCallback", String.class, Runnable.class);
- registerCallbackMethod.invoke(null, "java.util.logging.LogManager", callback);
- } catch (Exception ex) {
- log.error("Error registering callback for " + callback.getName(), ex);
- }
- }
-
- protected abstract static class ClassLoadCallBack implements Runnable {
-
- @Override
- public void run() {
- /*
- * This callback is called from within bytecode transformer. This can be a problem if callback tries
- * to load classes being transformed. To avoid this we start a thread here that calls the callback.
- * This seems to resolve this problem.
- */
- Thread thread =
- new Thread(
- new Runnable() {
- @Override
- public void run() {
- try {
- execute();
- } catch (Exception e) {
- log.error("Failed to run class loader callback {}", getName(), e);
- }
- }
- });
- thread.setName("agent-startup-" + getName());
- thread.setDaemon(true);
- thread.start();
- }
-
- public abstract String getName();
-
- public abstract void execute();
- }
-
- protected static class InstallAgentTracerCallback extends ClassLoadCallBack {
-
- @Override
- public String getName() {
- return "agent-tracer";
- }
-
- @Override
- public void execute() {
- installAgentTracer();
- }
}
private static synchronized void startAgent(Instrumentation inst, URL bootstrapUrl) {
@@ -142,7 +58,13 @@ private static synchronized void startAgent(Instrumentation inst, URL bootstrapU
agentClassLoader.loadClass("io.opentelemetry.javaagent.tooling.AgentInstaller");
Method agentInstallerMethod =
agentInstallerClass.getMethod("installBytebuddyAgent", Instrumentation.class);
- agentInstallerMethod.invoke(null, inst);
+ ClassLoader savedContextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(AGENT_CLASSLOADER);
+ agentInstallerMethod.invoke(null, inst);
+ } finally {
+ Thread.currentThread().setContextClassLoader(savedContextClassLoader);
+ }
AGENT_CLASSLOADER = agentClassLoader;
} catch (Throwable ex) {
log.error("Throwable thrown while installing the agent", ex);
@@ -150,25 +72,6 @@ private static synchronized void startAgent(Instrumentation inst, URL bootstrapU
}
}
- private static synchronized void installAgentTracer() {
- if (AGENT_CLASSLOADER == null) {
- throw new IllegalStateException("Agent should have been started already");
- }
- // TracerInstaller.installAgentTracer can be called multiple times without any problem
- // so there is no need to have a 'agentTracerInstalled' flag here.
- try {
- // install global tracer
- Class> tracerInstallerClass =
- AGENT_CLASSLOADER.loadClass("io.opentelemetry.javaagent.tooling.TracerInstaller");
- Method tracerInstallerMethod = tracerInstallerClass.getMethod("installAgentTracer");
- tracerInstallerMethod.invoke(null);
- Method logVersionInfoMethod = tracerInstallerClass.getMethod("logVersionInfo");
- logVersionInfoMethod.invoke(null);
- } catch (Throwable ex) {
- log.error("Throwable thrown while installing the agent tracer", ex);
- }
- }
-
private static void configureLogger() {
setSystemPropertyDefault(SIMPLE_LOGGER_SHOW_DATE_TIME_PROPERTY, "true");
setSystemPropertyDefault(
@@ -246,64 +149,7 @@ private static boolean isDebugMode() {
return false;
}
- /**
- * Search for java or agent-tracer sysprops which indicate that a custom log manager will be used.
- * Also search for any app classes known to set a custom log manager.
- *
- * @return true if we detect a custom log manager being used.
- */
- private static boolean isAppUsingCustomLogManager() {
- String tracerCustomLogManSysprop = "otel.app.customlogmanager";
- String customLogManagerProp = System.getProperty(tracerCustomLogManSysprop);
- String customLogManagerEnv =
- System.getenv(tracerCustomLogManSysprop.replace('.', '_').toUpperCase());
-
- if (customLogManagerProp != null || customLogManagerEnv != null) {
- log.debug("Prop - customlogmanager: " + customLogManagerProp);
- log.debug("Env - customlogmanager: " + customLogManagerEnv);
- // Allow setting to skip these automatic checks:
- return Boolean.parseBoolean(customLogManagerProp)
- || Boolean.parseBoolean(customLogManagerEnv);
- }
-
- String jbossHome = System.getenv("JBOSS_HOME");
- if (jbossHome != null) {
- 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.
- // This would cause jmxfetch initialization to be delayed indefinitely.
- // Checking for an environment variable required by jboss instead.
- return true;
- }
-
- String logManagerProp = System.getProperty("java.util.logging.manager");
- if (logManagerProp != null) {
- boolean onSysClasspath =
- ClassLoader.getSystemResource(logManagerProp.replaceAll("\\.", "/") + ".class") != null;
- 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.
- return !onSysClasspath;
- }
-
- return false;
- }
-
- private static boolean isJavaBefore9() {
+ public static boolean isJavaBefore9() {
return System.getProperty("java.version").startsWith("1.");
}
-
- private static boolean isJavaBefore9WithJfr() {
- if (!isJavaBefore9()) {
- return false;
- }
- // FIXME: this is quite a hack because there maybe jfr classes on classpath somehow that have
- // nothing to do with JDK but this should be safe because only thing this does is to delay
- // tracer install
- String jfrClassResourceName = "jdk.jfr.Recording".replace('.', '/') + ".class";
- return Thread.currentThread().getContextClassLoader().getResource(jfrClassResourceName) != null;
- }
}
diff --git a/javaagent-spi/src/main/java/io/opentelemetry/javaagent/spi/ComponentInstaller.java b/javaagent-spi/src/main/java/io/opentelemetry/javaagent/spi/ComponentInstaller.java
new file mode 100644
index 000000000000..c867b88fbabb
--- /dev/null
+++ b/javaagent-spi/src/main/java/io/opentelemetry/javaagent/spi/ComponentInstaller.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.spi;
+
+/**
+ * {@link ComponentInstaller} can be used to install any implementation providers that are used by
+ * instrumentations. For instance Java agent uses this to install OpenTelemetry SDK. The
+ * instrumentation uses shaded OpenTelemetry API that lives in the bootstrap classlaoder and the
+ * implementation (SDK) is installed via service loader from agent's classloader. This way the
+ * application does not have a direct access to the OpenTelemetry SDK classes. The same approach can
+ * be done for other APIs used by custom instrumentations.
+ *
+ * This is a service provider interface that requires implementations to be registered in {@code
+ * META-INF/services} folder.
+ */
+public interface ComponentInstaller {
+
+ /**
+ * Runs before instrumentations are installed to ByteBuddy. Execute only a minimal code because
+ * any classes loaded before the instrumentations are installed will have to be retransformed,
+ * which takes extra time, and more importantly means that fields can't be added to those classes
+ * and InstrumentationContext falls back to the less performant Map implementation for those
+ * classes.
+ */
+ void beforeByteBuddyAgent();
+
+ /** Runs after instrumentations are added to ByteBuddy. */
+ void afterByteBuddyAgent();
+}
diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java
index 16168e272606..e471c5366bdf 100644
--- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java
+++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java
@@ -12,16 +12,15 @@
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.none;
-import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.internal.BootstrapPackagePrefixesHolder;
-import io.opentelemetry.javaagent.instrumentation.api.OpenTelemetrySdkAccess;
+import io.opentelemetry.javaagent.bootstrap.AgentInitializer;
import io.opentelemetry.javaagent.instrumentation.api.SafeServiceLoader;
import io.opentelemetry.javaagent.spi.BootstrapPackagesProvider;
import io.opentelemetry.javaagent.spi.ByteBuddyAgentCustomizer;
+import io.opentelemetry.javaagent.spi.ComponentInstaller;
import io.opentelemetry.javaagent.tooling.config.ConfigInitializer;
import io.opentelemetry.javaagent.tooling.context.FieldBackedProvider;
-import io.opentelemetry.sdk.OpenTelemetrySdk;
import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
import java.util.Arrays;
@@ -65,7 +64,8 @@ public static Instrumentation getInstrumentation() {
public static void installBytebuddyAgent(Instrumentation inst) {
if (Config.get().getBooleanProperty(JAVAAGENT_ENABLED_CONFIG, true)) {
- installBytebuddyAgent(inst, false);
+ Iterable componentInstallers = loadComponentProviders();
+ installBytebuddyAgent(inst, false, componentInstallers);
} else {
log.debug("Tracing is disabled, not installing instrumentations.");
}
@@ -81,30 +81,10 @@ public static void installBytebuddyAgent(Instrumentation inst) {
public static ResettableClassFileTransformer installBytebuddyAgent(
Instrumentation inst,
boolean skipAdditionalLibraryMatcher,
+ Iterable componentInstallers,
AgentBuilder.Listener... listeners) {
- ClassLoader savedContextClassLoader = Thread.currentThread().getContextClassLoader();
- try {
- // calling (shaded) OpenTelemetry.getGlobalTracerProvider() with context class loader set to
- // the
- // agent class loader, so that SPI finds the agent's (isolated) SDK, and (shaded)
- // OpenTelemetry registers it, and then when instrumentation calls (shaded)
- // OpenTelemetry.getGlobalTracerProvider() later, they get back the agent's (isolated) SDK
- //
- // but if we don't trigger this early registration, then if instrumentation is the first to
- // call (shaded) OpenTelemetry.getGlobalTracerProvider(), then SPI can't see the agent class
- // loader,
- // and so (shaded) OpenTelemetry registers the no-op TracerFactory, and it cannot be replaced
- // later
- Thread.currentThread().setContextClassLoader(AgentInstaller.class.getClassLoader());
- OpenTelemetry.getGlobalTracerProvider();
- } finally {
- Thread.currentThread().setContextClassLoader(savedContextClassLoader);
- }
-
- OpenTelemetrySdkAccess.internalSetForceFlush(
- (timeout, unit) ->
- OpenTelemetrySdk.getGlobalTracerManagement().forceFlush().join(timeout, unit));
+ installComponentsBeforeByteBuddy(componentInstallers);
INSTRUMENTATION = inst;
@@ -160,7 +140,51 @@ public static ResettableClassFileTransformer installBytebuddyAgent(
agentBuilder = customizeByteBuddyAgent(agentBuilder);
log.debug("Installed {} instrumenter(s)", numInstrumenters);
- return agentBuilder.installOn(inst);
+ ResettableClassFileTransformer resettableClassFileTransformer = agentBuilder.installOn(inst);
+ installComponentsAfterByteBuddy(componentInstallers);
+ return resettableClassFileTransformer;
+ }
+
+ private static void installComponentsBeforeByteBuddy(
+ Iterable componentInstallers) {
+ Thread.currentThread().setContextClassLoader(AgentInstaller.class.getClassLoader());
+ for (ComponentInstaller componentInstaller : componentInstallers) {
+ componentInstaller.beforeByteBuddyAgent();
+ }
+ }
+
+ private static void installComponentsAfterByteBuddy(
+ Iterable componentInstallers) {
+ /*
+ * java.util.logging.LogManager maintains a final static LogManager, which is created during class initialization.
+ *
+ * JMXFetch uses jre bootstrap classes which touch this class. This means applications which require a custom log
+ * manager may not have a chance to set the global log manager if jmxfetch runs first. JMXFetch will incorrectly
+ * set the global log manager in cases where the app sets the log manager system property or when the log manager
+ * class is not on the system classpath.
+ *
+ * Our solution is to delay the initialization of jmxfetch when we detect a custom log manager being used.
+ *
+ * Once we see the LogManager class loading, it's safe to start jmxfetch because the application is already setting
+ * the global log manager and jmxfetch won't be able to touch it due to classloader locking.
+ */
+
+ /*
+ * Similar thing happens with AgentTracer on (at least) zulu-8 because it uses OkHttp which indirectly loads JFR
+ * events which in turn loads LogManager. This is not a problem on newer JDKs because there JFR uses different
+ * logging facility.
+ */
+ boolean appUsingCustomLogManager = isAppUsingCustomLogManager();
+ if (isJavaBefore9WithJfr() && appUsingCustomLogManager) {
+ log.debug("Custom logger detected. Delaying Agent Tracer initialization.");
+ registerClassLoadCallback(
+ "java.util.logging.LogManager",
+ new InstallComponentAfterByteBuddyCallback(componentInstallers));
+ } else {
+ for (ComponentInstaller componentInstaller : componentInstallers) {
+ componentInstaller.afterByteBuddyAgent();
+ }
+ }
}
private static AgentBuilder customizeByteBuddyAgent(AgentBuilder agentBuilder) {
@@ -172,6 +196,10 @@ private static AgentBuilder customizeByteBuddyAgent(AgentBuilder agentBuilder) {
return agentBuilder;
}
+ private static Iterable loadComponentProviders() {
+ return ServiceLoader.load(ComponentInstaller.class, AgentInstaller.class.getClassLoader());
+ }
+
private static Iterable loadByteBuddyAgentCustomizers() {
return ServiceLoader.load(
ByteBuddyAgentCustomizer.class, AgentInstaller.class.getClassLoader());
@@ -340,6 +368,59 @@ public static void registerClassLoadCallback(String className, Runnable callback
}
}
+ protected static class InstallComponentAfterByteBuddyCallback extends ClassLoadCallBack {
+
+ private final Iterable componentInstallers;
+
+ protected InstallComponentAfterByteBuddyCallback(
+ Iterable componentInstallers) {
+ this.componentInstallers = componentInstallers;
+ }
+
+ @Override
+ public String getName() {
+ return componentInstallers.getClass().getName();
+ }
+
+ @Override
+ public void execute() {
+ for (ComponentInstaller componentInstaller : componentInstallers) {
+ componentInstaller.afterByteBuddyAgent();
+ }
+ }
+ }
+
+ protected abstract static class ClassLoadCallBack implements Runnable {
+
+ @Override
+ public void run() {
+ /*
+ * This callback is called from within bytecode transformer. This can be a problem if callback tries
+ * to load classes being transformed. To avoid this we start a thread here that calls the callback.
+ * This seems to resolve this problem.
+ */
+ Thread thread =
+ new Thread(
+ new Runnable() {
+ @Override
+ public void run() {
+ try {
+ execute();
+ } catch (Exception e) {
+ log.error("Failed to run class loader callback {}", getName(), e);
+ }
+ }
+ });
+ thread.setName("agent-startup-" + getName());
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+ public abstract String getName();
+
+ public abstract void execute();
+ }
+
private static class ClassLoadListener implements AgentBuilder.Listener {
@Override
public void onDiscovery(
@@ -378,5 +459,62 @@ public void onComplete(
}
}
+ /**
+ * Search for java or agent-tracer sysprops which indicate that a custom log manager will be used.
+ * Also search for any app classes known to set a custom log manager.
+ *
+ * @return true if we detect a custom log manager being used.
+ */
+ private static boolean isAppUsingCustomLogManager() {
+ String tracerCustomLogManSysprop = "otel.app.customlogmanager";
+ String customLogManagerProp = System.getProperty(tracerCustomLogManSysprop);
+ String customLogManagerEnv =
+ System.getenv(tracerCustomLogManSysprop.replace('.', '_').toUpperCase());
+
+ if (customLogManagerProp != null || customLogManagerEnv != null) {
+ log.debug("Prop - customlogmanager: " + customLogManagerProp);
+ log.debug("Env - customlogmanager: " + customLogManagerEnv);
+ // Allow setting to skip these automatic checks:
+ return Boolean.parseBoolean(customLogManagerProp)
+ || Boolean.parseBoolean(customLogManagerEnv);
+ }
+
+ String jbossHome = System.getenv("JBOSS_HOME");
+ if (jbossHome != null) {
+ 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.
+ // This would cause jmxfetch initialization to be delayed indefinitely.
+ // Checking for an environment variable required by jboss instead.
+ return true;
+ }
+
+ String logManagerProp = System.getProperty("java.util.logging.manager");
+ if (logManagerProp != null) {
+ boolean onSysClasspath =
+ ClassLoader.getSystemResource(logManagerProp.replaceAll("\\.", "/") + ".class") != null;
+ 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.
+ return !onSysClasspath;
+ }
+
+ return false;
+ }
+
+ private static boolean isJavaBefore9WithJfr() {
+ if (!AgentInitializer.isJavaBefore9()) {
+ return false;
+ }
+ // FIXME: this is quite a hack because there maybe jfr classes on classpath somehow that have
+ // nothing to do with JDK but this should be safe because only thing this does is to delay
+ // tracer install
+ String jfrClassResourceName = "jdk.jfr.Recording".replace('.', '/') + ".class";
+ return Thread.currentThread().getContextClassLoader().getResource(jfrClassResourceName) != null;
+ }
+
private AgentInstaller() {}
}
diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/TracerInstaller.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/OpenTelemetryInstaller.java
similarity index 81%
rename from javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/TracerInstaller.java
rename to javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/OpenTelemetryInstaller.java
index e3491ddf67e1..3930ddb8bc02 100644
--- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/TracerInstaller.java
+++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/OpenTelemetryInstaller.java
@@ -5,7 +5,11 @@
package io.opentelemetry.javaagent.tooling;
+import com.google.auto.service.AutoService;
+import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.config.Config;
+import io.opentelemetry.javaagent.instrumentation.api.OpenTelemetrySdkAccess;
+import io.opentelemetry.javaagent.spi.ComponentInstaller;
import io.opentelemetry.javaagent.spi.TracerCustomizer;
import io.opentelemetry.javaagent.spi.exporter.MetricExporterFactory;
import io.opentelemetry.javaagent.spi.exporter.MetricServer;
@@ -30,8 +34,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class TracerInstaller {
- private static final Logger log = LoggerFactory.getLogger(TracerInstaller.class);
+@AutoService(ComponentInstaller.class)
+public class OpenTelemetryInstaller implements ComponentInstaller {
+ private static final Logger log = LoggerFactory.getLogger(OpenTelemetryInstaller.class);
private static final String EXPORTER_JAR_CONFIG = "otel.exporter.jar";
private static final String EXPORTERS_CONFIG = "otel.exporter"; // this name is from spec
@@ -39,6 +44,30 @@ public class TracerInstaller {
private static final String JAVAAGENT_ENABLED_CONFIG = "otel.javaagent.enabled";
private static final List DEFAULT_EXPORTERS = Collections.singletonList("otlp");
+ @Override
+ public void beforeByteBuddyAgent() {
+ // calling (shaded) OpenTelemetry.getGlobalTracerProvider() with context class loader set to
+ // the
+ // agent class loader, so that SPI finds the agent's (isolated) SDK, and (shaded)
+ // OpenTelemetry registers it, and then when instrumentation calls (shaded)
+ // OpenTelemetry.getGlobalTracerProvider() later, they get back the agent's (isolated) SDK
+ //
+ // but if we don't trigger this early registration, then if instrumentation is the first to
+ // call (shaded) OpenTelemetry.getGlobalTracerProvider(), then SPI can't see the agent class
+ // loader,
+ // and so (shaded) OpenTelemetry registers the no-op TracerFactory, and it cannot be replaced
+ // later
+ OpenTelemetry.getGlobalTracerProvider();
+ OpenTelemetrySdkAccess.internalSetForceFlush(
+ (timeout, unit) ->
+ OpenTelemetrySdk.getGlobalTracerManagement().forceFlush().join(timeout, unit));
+ }
+
+ @Override
+ public void afterByteBuddyAgent() {
+ installAgentTracer();
+ }
+
/** Register agent tracer if no agent tracer is already registered. */
@SuppressWarnings("unused")
public static synchronized void installAgentTracer() {
@@ -88,7 +117,8 @@ private static synchronized void installExporters(List exporters, Proper
private static MetricExporterFactory findMetricExporterFactory(String exporterName) {
ServiceLoader serviceLoader =
- ServiceLoader.load(MetricExporterFactory.class, TracerInstaller.class.getClassLoader());
+ ServiceLoader.load(
+ MetricExporterFactory.class, OpenTelemetryInstaller.class.getClassLoader());
for (MetricExporterFactory metricExporterFactory : serviceLoader) {
if (metricExporterFactory.getNames().contains(exporterName)) {
@@ -100,7 +130,7 @@ private static MetricExporterFactory findMetricExporterFactory(String exporterNa
private static MetricServer findMetricServer(String exporterName) {
ServiceLoader serviceLoader =
- ServiceLoader.load(MetricServer.class, TracerInstaller.class.getClassLoader());
+ ServiceLoader.load(MetricServer.class, OpenTelemetryInstaller.class.getClassLoader());
for (MetricServer metricServer : serviceLoader) {
if (metricServer.getNames().contains(exporterName)) {
@@ -112,7 +142,8 @@ private static MetricServer findMetricServer(String exporterName) {
private static SpanExporterFactory findSpanExporterFactory(String exporterName) {
ServiceLoader serviceLoader =
- ServiceLoader.load(SpanExporterFactory.class, TracerInstaller.class.getClassLoader());
+ ServiceLoader.load(
+ SpanExporterFactory.class, OpenTelemetryInstaller.class.getClassLoader());
for (SpanExporterFactory spanExporterFactory : serviceLoader) {
if (spanExporterFactory.getNames().contains(exporterName)) {
@@ -132,7 +163,7 @@ private static synchronized void installExportersFromJar(String exporterJar, Pro
return;
}
ExporterClassLoader exporterLoader =
- new ExporterClassLoader(url, TracerInstaller.class.getClassLoader());
+ new ExporterClassLoader(url, OpenTelemetryInstaller.class.getClassLoader());
SpanExporterFactory spanExporterFactory =
getExporterFactory(SpanExporterFactory.class, exporterLoader);
@@ -200,7 +231,7 @@ private static void configure(Properties config) {
// Execute any user-provided (usually vendor-provided) configuration logic.
ServiceLoader serviceLoader =
- ServiceLoader.load(TracerCustomizer.class, TracerInstaller.class.getClassLoader());
+ ServiceLoader.load(TracerCustomizer.class, OpenTelemetryInstaller.class.getClassLoader());
for (TracerCustomizer customizer : serviceLoader) {
customizer.configure(tracerManagement);
}
diff --git a/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/AgentTestRunner.java b/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/AgentTestRunner.java
index ce2069937b5b..be6fc130a21b 100644
--- a/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/AgentTestRunner.java
+++ b/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/AgentTestRunner.java
@@ -17,18 +17,17 @@
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
-import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
-import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.instrumentation.test.asserts.InMemoryExporterAssert;
import io.opentelemetry.instrumentation.test.utils.ConfigUtils;
+import io.opentelemetry.javaagent.spi.ComponentInstaller;
import io.opentelemetry.javaagent.tooling.AgentInstaller;
import io.opentelemetry.javaagent.tooling.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.matcher.AdditionalLibraryIgnoresMatcher;
-import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.trace.data.SpanData;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -79,7 +78,9 @@ public abstract class AgentTestRunner extends Specification {
*
* Before the start of each test the reported traces will be reset.
*/
- public static final InMemoryExporter TEST_WRITER;
+ public static final InMemoryExporter TEST_WRITER = new InMemoryExporter();
+
+ private static final ComponentInstaller COMPONENT_INSTALLER;
protected static final Tracer TEST_TRACER;
@@ -101,21 +102,7 @@ public abstract class AgentTestRunner extends Specification {
((Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).setLevel(Level.WARN);
((Logger) LoggerFactory.getLogger("io.opentelemetry")).setLevel(Level.DEBUG);
- TEST_WRITER = new InMemoryExporter();
- // TODO this is probably temporary until default propagators are supplied by SDK
- // https://github.com/open-telemetry/opentelemetry-java/issues/1742
- // currently checking against no-op implementation so that it won't override aws-lambda
- // propagator configuration
- if (OpenTelemetry.getGlobalPropagators()
- .getTextMapPropagator()
- .getClass()
- .getSimpleName()
- .equals("NoopTextMapPropagator")) {
- // Workaround https://github.com/open-telemetry/opentelemetry-java/pull/2096
- OpenTelemetry.setGlobalPropagators(
- ContextPropagators.create(W3CTraceContextPropagator.getInstance()));
- }
- OpenTelemetrySdk.getGlobalTracerManagement().addSpanProcessor(TEST_WRITER);
+ COMPONENT_INSTALLER = new TestOpenTelemetryInstaller(TEST_WRITER);
TEST_TRACER = OpenTelemetry.getGlobalTracer("io.opentelemetry.auto");
}
@@ -172,7 +159,8 @@ public void setupBeforeTests() {
if (activeTransformer == null) {
activeTransformer =
- AgentInstaller.installBytebuddyAgent(INSTRUMENTATION, true, TEST_LISTENER);
+ AgentInstaller.installBytebuddyAgent(
+ INSTRUMENTATION, true, Collections.singleton(COMPONENT_INSTALLER), TEST_LISTENER);
}
TEST_LISTENER.activateTest(this);
}
diff --git a/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/TestOpenTelemetryInstaller.java b/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/TestOpenTelemetryInstaller.java
new file mode 100644
index 000000000000..a4de5041a7ab
--- /dev/null
+++ b/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/TestOpenTelemetryInstaller.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.test;
+
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
+import io.opentelemetry.context.propagation.ContextPropagators;
+import io.opentelemetry.javaagent.tooling.OpenTelemetryInstaller;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.trace.SpanProcessor;
+
+public class TestOpenTelemetryInstaller extends OpenTelemetryInstaller {
+
+ private final SpanProcessor spanProcessor;
+
+ public TestOpenTelemetryInstaller(SpanProcessor spanProcessor) {
+ this.spanProcessor = spanProcessor;
+ }
+
+ @Override
+ public void afterByteBuddyAgent() {
+ // TODO this is probably temporary until default propagators are supplied by SDK
+ // https://github.com/open-telemetry/opentelemetry-java/issues/1742
+ // currently checking against no-op implementation so that it won't override aws-lambda
+ // propagator configuration
+ if (OpenTelemetry.getGlobalPropagators()
+ .getTextMapPropagator()
+ .getClass()
+ .getSimpleName()
+ .equals("NoopTextMapPropagator")) {
+ // Workaround https://github.com/open-telemetry/opentelemetry-java/pull/2096
+ OpenTelemetry.setGlobalPropagators(
+ ContextPropagators.create(W3CTraceContextPropagator.getInstance()));
+ }
+ OpenTelemetrySdk.getGlobalTracerManagement().addSpanProcessor(spanProcessor);
+ }
+}
diff --git a/testing-common/testing-common.gradle b/testing-common/testing-common.gradle
index 4a9002397816..5be845416940 100644
--- a/testing-common/testing-common.gradle
+++ b/testing-common/testing-common.gradle
@@ -35,8 +35,9 @@ dependencies {
implementation project(':javaagent-bootstrap')
implementation project(':javaagent-api')
+ api project(':javaagent-spi')
implementation project(':instrumentation-api')
- implementation(project(':javaagent-tooling')) {
+ api(project(':javaagent-tooling')) {
// including :opentelemetry-sdk-shaded-for-testing above instead
exclude group: 'io.opentelemetry', module: 'opentelemetry-sdk'
}