From f69217e2a4ffd8e40b44659806b1192576483262 Mon Sep 17 00:00:00 2001 From: Pavol Loffay Date: Fri, 18 Dec 2020 07:55:24 +0100 Subject: [PATCH] Add ComponentInstaller SPI and use it for OpenTelemetry SDK (#1848) * Add component installer SPI Signed-off-by: Pavol Loffay * Move more code to agent installer Signed-off-by: Pavol Loffay --- 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' }