Expose AutoConfiguredOpenTelemetrySdk to AgentListener (#4831)

* Expose AutoConfiguredOpenTelemetrySdk to AgentListener

* Only call AgentListener if noop is disabled, deprecate AgentListener methods

* Call AgentListener in DelayedAfterAgentCallback
This commit is contained in:
jack-berg 2021-12-10 14:55:57 -06:00 committed by GitHub
parent f7f3f5685f
commit 45dca4fc5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 102 deletions

View File

@ -16,4 +16,6 @@ dependencies {
// metrics are unstable, do not expose as api // metrics are unstable, do not expose as api
implementation("io.opentelemetry:opentelemetry-sdk-metrics") implementation("io.opentelemetry:opentelemetry-sdk-metrics")
// autoconfigure is unstable, do not expose as api
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
} }

View File

@ -8,13 +8,14 @@ package io.opentelemetry.javaagent.extension;
import io.opentelemetry.instrumentation.api.cache.Cache; import io.opentelemetry.instrumentation.api.cache.Cache;
import io.opentelemetry.instrumentation.api.config.Config; import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.field.VirtualField; import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import java.lang.instrument.Instrumentation; import java.lang.instrument.Instrumentation;
import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.agent.builder.AgentBuilder;
/** /**
* {@link AgentListener} can be used to execute code before/after Java agent installation, for * {@link AgentListener} can be used to execute code before/after Java agent installation, for
* example to install any implementation providers that are used by instrumentations. For instance, * example to install any implementation providers that are used by instrumentations. Can also be
* this project uses this SPI to install OpenTelemetry SDK. * used to obtain the {@link AutoConfiguredOpenTelemetrySdk}.
* *
* <p>This is a service provider interface that requires implementations to be registered in a * <p>This is a service provider interface that requires implementations to be registered in a
* provider-configuration file stored in the {@code META-INF/services} resource directory. * provider-configuration file stored in the {@code META-INF/services} resource directory.
@ -22,18 +23,44 @@ import net.bytebuddy.agent.builder.AgentBuilder;
public interface AgentListener extends Ordered { public interface AgentListener extends Ordered {
/** /**
* Runs before the {@link AgentBuilder} construction, before any instrumentation is added. * Runs before {@link AgentBuilder} construction, before any instrumentation is added.
* *
* <p>Execute only a minimal code because any classes loaded before the agent installation will * <p>Execute only minimal code because any classes loaded before the agent installation will have
* have to be retransformed, which takes extra time, and more importantly means that fields can't * to be retransformed, which takes extra time, and more importantly means that fields can't be
* be added to those classes - which causes {@link VirtualField} to fall back to the less * added to those classes - which causes {@link VirtualField} to fall back to the less performant
* performant {@link Cache} implementation for those classes. * {@link Cache} implementation for those classes.
*
* @deprecated Use {@link #beforeAgent(Config, AutoConfiguredOpenTelemetrySdk)}
*/ */
@Deprecated
default void beforeAgent(Config config) {} default void beforeAgent(Config config) {}
/**
* Runs before {@link AgentBuilder} construction, before any instrumentation is added. Not called
* if noop api enabled via {@code otel.javaagent.experimental.use-noop-api}.
*
* <p>Execute only minimal code because any classes loaded before the agent installation will have
* to be retransformed, which takes extra time, and more importantly means that fields can't be
* added to those classes - which causes {@link VirtualField} to fall back to the less performant
* {@link Cache} implementation for those classes.
*/
default void beforeAgent(
Config config, AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {}
/** /**
* Runs after instrumentations are added to {@link AgentBuilder} and after the agent is installed * Runs after instrumentations are added to {@link AgentBuilder} and after the agent is installed
* on an {@link Instrumentation}. * on an {@link Instrumentation}.
*
* @deprecated Use {@link #afterAgent(Config, AutoConfiguredOpenTelemetrySdk)}
*/ */
@Deprecated
default void afterAgent(Config config) {} default void afterAgent(Config config) {}
/**
* Runs after instrumentations are added to {@link AgentBuilder} and after the agent is installed
* on an {@link Instrumentation}. Not called if noop api enabled via {@code
* otel.javaagent.experimental.use-noop-api}.
*/
default void afterAgent(
Config config, AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {}
} }

View File

@ -6,14 +6,17 @@
package io.opentelemetry.javaagent.tooling; package io.opentelemetry.javaagent.tooling;
import static io.opentelemetry.javaagent.bootstrap.AgentInitializer.isJavaBefore9; import static io.opentelemetry.javaagent.bootstrap.AgentInitializer.isJavaBefore9;
import static io.opentelemetry.javaagent.tooling.OpenTelemetryInstaller.installOpenTelemetrySdk;
import static io.opentelemetry.javaagent.tooling.SafeServiceLoader.load; import static io.opentelemetry.javaagent.tooling.SafeServiceLoader.load;
import static io.opentelemetry.javaagent.tooling.SafeServiceLoader.loadOrdered; import static io.opentelemetry.javaagent.tooling.SafeServiceLoader.loadOrdered;
import static io.opentelemetry.javaagent.tooling.Utils.getResourceName; import static io.opentelemetry.javaagent.tooling.Utils.getResourceName;
import static net.bytebuddy.matcher.ElementMatchers.any; import static net.bytebuddy.matcher.ElementMatchers.any;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.context.Context; import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextStorage; import io.opentelemetry.context.ContextStorage;
import io.opentelemetry.context.Scope; import io.opentelemetry.context.Scope;
import io.opentelemetry.extension.noopapi.NoopOpenTelemetry;
import io.opentelemetry.instrumentation.api.config.Config; import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.javaagent.bootstrap.AgentClassLoader; import io.opentelemetry.javaagent.bootstrap.AgentClassLoader;
import io.opentelemetry.javaagent.bootstrap.BootstrapPackagePrefixesHolder; import io.opentelemetry.javaagent.bootstrap.BootstrapPackagePrefixesHolder;
@ -31,6 +34,7 @@ import io.opentelemetry.javaagent.tooling.ignore.IgnoredTypesBuilderImpl;
import io.opentelemetry.javaagent.tooling.ignore.IgnoredTypesMatcher; import io.opentelemetry.javaagent.tooling.ignore.IgnoredTypesMatcher;
import io.opentelemetry.javaagent.tooling.muzzle.AgentTooling; import io.opentelemetry.javaagent.tooling.muzzle.AgentTooling;
import io.opentelemetry.javaagent.tooling.util.Trie; import io.opentelemetry.javaagent.tooling.util.Trie;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import java.lang.instrument.Instrumentation; import java.lang.instrument.Instrumentation;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -53,7 +57,8 @@ public class AgentInstaller {
private static final Logger logger; private static final Logger logger;
private static final String JAVAAGENT_ENABLED_CONFIG = "otel.javaagent.enabled"; static final String JAVAAGENT_ENABLED_CONFIG = "otel.javaagent.enabled";
static final String JAVAAGENT_NOOP_CONFIG = "otel.javaagent.experimental.use-noop-api";
// This property may be set to force synchronous AgentListener#afterAgent() execution: the // This property may be set to force synchronous AgentListener#afterAgent() execution: the
// condition for delaying the AgentListener initialization is pretty broad and in case it covers // condition for delaying the AgentListener initialization is pretty broad and in case it covers
@ -110,7 +115,19 @@ public class AgentInstaller {
setBootstrapPackages(config); setBootstrapPackages(config);
runBeforeAgentListeners(agentListeners, config); // If noop OpenTelemetry is enabled, autoConfiguredSdk will be null and AgentListeners are not
// called
AutoConfiguredOpenTelemetrySdk autoConfiguredSdk = null;
if (config.getBoolean(JAVAAGENT_NOOP_CONFIG, false)) {
logger.info("Tracing and metrics are disabled because noop is enabled.");
GlobalOpenTelemetry.set(NoopOpenTelemetry.getInstance());
} else {
autoConfiguredSdk = installOpenTelemetrySdk(config);
}
if (autoConfiguredSdk != null) {
runBeforeAgentListeners(agentListeners, config, autoConfiguredSdk);
}
AgentBuilder agentBuilder = AgentBuilder agentBuilder =
new AgentBuilder.Default() new AgentBuilder.Default()
@ -157,7 +174,11 @@ public class AgentInstaller {
ResettableClassFileTransformer resettableClassFileTransformer = agentBuilder.installOn(inst); ResettableClassFileTransformer resettableClassFileTransformer = agentBuilder.installOn(inst);
ClassFileTransformerHolder.setClassFileTransformer(resettableClassFileTransformer); ClassFileTransformerHolder.setClassFileTransformer(resettableClassFileTransformer);
runAfterAgentListeners(agentListeners, config);
if (autoConfiguredSdk != null) {
runAfterAgentListeners(agentListeners, config, autoConfiguredSdk);
}
return resettableClassFileTransformer; return resettableClassFileTransformer;
} }
@ -178,9 +199,12 @@ public class AgentInstaller {
} }
private static void runBeforeAgentListeners( private static void runBeforeAgentListeners(
Iterable<AgentListener> agentListeners, Config config) { Iterable<AgentListener> agentListeners,
Config config,
AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
for (AgentListener agentListener : agentListeners) { for (AgentListener agentListener : agentListeners) {
agentListener.beforeAgent(config); agentListener.beforeAgent(config);
agentListener.beforeAgent(config, autoConfiguredSdk);
} }
} }
@ -199,7 +223,9 @@ public class AgentInstaller {
} }
private static void runAfterAgentListeners( private static void runAfterAgentListeners(
Iterable<AgentListener> agentListeners, Config config) { Iterable<AgentListener> agentListeners,
Config config,
AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
// java.util.logging.LogManager maintains a final static LogManager, which is created during // java.util.logging.LogManager maintains a final static LogManager, which is created during
// class initialization. Some AgentListener implementations may use JRE bootstrap classes // class initialization. Some AgentListener implementations may use JRE bootstrap classes
// which touch this class (e.g. JFR classes or some MBeans). // which touch this class (e.g. JFR classes or some MBeans).
@ -223,10 +249,12 @@ public class AgentInstaller {
&& isAppUsingCustomLogManager()) { && isAppUsingCustomLogManager()) {
logger.debug("Custom JUL LogManager detected: delaying AgentListener#afterAgent() calls"); logger.debug("Custom JUL LogManager detected: delaying AgentListener#afterAgent() calls");
registerClassLoadCallback( registerClassLoadCallback(
"java.util.logging.LogManager", new DelayedAfterAgentCallback(config, agentListeners)); "java.util.logging.LogManager",
new DelayedAfterAgentCallback(config, agentListeners, autoConfiguredSdk));
} else { } else {
for (AgentListener agentListener : agentListeners) { for (AgentListener agentListener : agentListeners) {
agentListener.afterAgent(config); agentListener.afterAgent(config);
agentListener.afterAgent(config, autoConfiguredSdk);
} }
} }
} }
@ -326,10 +354,15 @@ public class AgentInstaller {
private static class DelayedAfterAgentCallback implements Runnable { private static class DelayedAfterAgentCallback implements Runnable {
private final Iterable<AgentListener> agentListeners; private final Iterable<AgentListener> agentListeners;
private final Config config; private final Config config;
private final AutoConfiguredOpenTelemetrySdk autoConfiguredSdk;
private DelayedAfterAgentCallback(Config config, Iterable<AgentListener> agentListeners) { private DelayedAfterAgentCallback(
Config config,
Iterable<AgentListener> agentListeners,
AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
this.agentListeners = agentListeners; this.agentListeners = agentListeners;
this.config = config; this.config = config;
this.autoConfiguredSdk = autoConfiguredSdk;
} }
@Override @Override
@ -349,6 +382,7 @@ public class AgentInstaller {
for (AgentListener agentListener : agentListeners) { for (AgentListener agentListener : agentListeners) {
try { try {
agentListener.afterAgent(config); agentListener.afterAgent(config);
agentListener.afterAgent(config, autoConfiguredSdk);
} catch (RuntimeException e) { } catch (RuntimeException e) {
logger.error("Failed to execute {}", agentListener.getClass().getName(), e); logger.error("Failed to execute {}", agentListener.getClass().getName(), e);
} }

View File

@ -5,6 +5,8 @@
package io.opentelemetry.javaagent.tooling; package io.opentelemetry.javaagent.tooling;
import static io.opentelemetry.javaagent.tooling.AgentInstaller.JAVAAGENT_ENABLED_CONFIG;
import com.google.auto.service.AutoService; import com.google.auto.service.AutoService;
import io.opentelemetry.exporter.logging.LoggingSpanExporter; import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.instrumentation.api.config.Config; import io.opentelemetry.instrumentation.api.config.Config;
@ -24,7 +26,7 @@ public class AgentTracerProviderConfigurer implements SdkTracerProviderConfigure
@Override @Override
public void configure( public void configure(
SdkTracerProviderBuilder sdkTracerProviderBuilder, ConfigProperties config) { SdkTracerProviderBuilder sdkTracerProviderBuilder, ConfigProperties config) {
if (!Config.get().getBoolean(OpenTelemetryInstaller.JAVAAGENT_ENABLED_CONFIG, true)) { if (!Config.get().getBoolean(JAVAAGENT_ENABLED_CONFIG, true)) {
return; return;
} }

View File

@ -5,14 +5,10 @@
package io.opentelemetry.javaagent.tooling; package io.opentelemetry.javaagent.tooling;
import com.google.auto.service.AutoService;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.metrics.GlobalMeterProvider; import io.opentelemetry.api.metrics.GlobalMeterProvider;
import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.extension.noopapi.NoopOpenTelemetry;
import io.opentelemetry.instrumentation.api.config.Config; import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.javaagent.bootstrap.AgentInitializer; import io.opentelemetry.javaagent.bootstrap.AgentInitializer;
import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.javaagent.instrumentation.api.OpenTelemetrySdkAccess; import io.opentelemetry.javaagent.instrumentation.api.OpenTelemetrySdkAccess;
import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
@ -20,64 +16,46 @@ import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder;
import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import java.util.Arrays; import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@AutoService(AgentListener.class) public class OpenTelemetryInstaller {
public class OpenTelemetryInstaller implements AgentListener {
private static final Logger logger = LoggerFactory.getLogger(OpenTelemetryInstaller.class);
static final String JAVAAGENT_ENABLED_CONFIG = "otel.javaagent.enabled";
static final String JAVAAGENT_NOOP_CONFIG = "otel.javaagent.experimental.use-noop-api";
@Override
public void beforeAgent(Config config) {
installAgentTracer(config);
}
/** /**
* Register agent tracer if no agent tracer is already registered. * Install the {@link OpenTelemetrySdk} using autoconfigure, and return the {@link
* AutoConfiguredOpenTelemetrySdk}.
* *
* @param config Configuration instance * @return the {@link AutoConfiguredOpenTelemetrySdk}
*/ */
@SuppressWarnings("unused") static AutoConfiguredOpenTelemetrySdk installOpenTelemetrySdk(Config config) {
public static synchronized void installAgentTracer(Config config) { System.setProperty("io.opentelemetry.context.contextStorageProvider", "default");
if (config.getBoolean(JAVAAGENT_ENABLED_CONFIG, true)) {
if (config.getBoolean(JAVAAGENT_NOOP_CONFIG, false)) { AutoConfiguredOpenTelemetrySdkBuilder builder =
GlobalOpenTelemetry.set(NoopOpenTelemetry.getInstance()); AutoConfiguredOpenTelemetrySdk.builder()
} else { .setResultAsGlobal(true)
System.setProperty("io.opentelemetry.context.contextStorageProvider", "default"); .addPropertiesSupplier(config::getAllProperties);
AutoConfiguredOpenTelemetrySdkBuilder builder = ClassLoader classLoader = AgentInitializer.getExtensionsClassLoader();
AutoConfiguredOpenTelemetrySdk.builder() if (classLoader != null) {
.setResultAsGlobal(true) // May be null in unit tests.
.addPropertiesSupplier(config::getAllProperties); builder.setServiceClassLoader(classLoader);
ClassLoader classLoader = AgentInitializer.getExtensionsClassLoader();
if (classLoader != null) {
// May be null in unit tests.
builder.setServiceClassLoader(classLoader);
}
OpenTelemetrySdk sdk = builder.build().getOpenTelemetrySdk();
OpenTelemetrySdkAccess.internalSetForceFlush(
(timeout, unit) -> {
CompletableResultCode traceResult = sdk.getSdkTracerProvider().forceFlush();
MeterProvider meterProvider = GlobalMeterProvider.get();
final CompletableResultCode metricsResult;
if (meterProvider instanceof SdkMeterProvider) {
metricsResult = ((SdkMeterProvider) meterProvider).forceFlush();
} else {
metricsResult = CompletableResultCode.ofSuccess();
}
CompletableResultCode.ofAll(Arrays.asList(traceResult, metricsResult))
.join(timeout, unit);
});
}
} else {
logger.info("Tracing is disabled.");
} }
AutoConfiguredOpenTelemetrySdk autoConfiguredSdk = builder.build();
OpenTelemetrySdk sdk = autoConfiguredSdk.getOpenTelemetrySdk();
OpenTelemetrySdkAccess.internalSetForceFlush(
(timeout, unit) -> {
CompletableResultCode traceResult = sdk.getSdkTracerProvider().forceFlush();
MeterProvider meterProvider = GlobalMeterProvider.get();
final CompletableResultCode metricsResult;
if (meterProvider instanceof SdkMeterProvider) {
metricsResult = ((SdkMeterProvider) meterProvider).forceFlush();
} else {
metricsResult = CompletableResultCode.ofSuccess();
}
CompletableResultCode.ofAll(Arrays.asList(traceResult, metricsResult))
.join(timeout, unit);
});
return autoConfiguredSdk;
} }
} }

View File

@ -12,7 +12,6 @@ import spock.lang.Specification
class OpenTelemetryInstallerTest extends Specification { class OpenTelemetryInstallerTest extends Specification {
void setup() { void setup() {
GlobalOpenTelemetry.resetForTest() GlobalOpenTelemetry.resetForTest()
} }
@ -21,40 +20,12 @@ class OpenTelemetryInstallerTest extends Specification {
GlobalOpenTelemetry.resetForTest() GlobalOpenTelemetry.resetForTest()
} }
def "should initialize GlobalOpenTelemetry"() {
def "should initialize noop"() {
given:
def config = Config.builder()
.readProperties([
(OpenTelemetryInstaller.JAVAAGENT_NOOP_CONFIG) : "true",
(OpenTelemetryInstaller.JAVAAGENT_ENABLED_CONFIG): "true"
])
.build()
when: when:
def otelInstaller = new OpenTelemetryInstaller() def otelInstaller = OpenTelemetryInstaller.installOpenTelemetrySdk(Config.builder().build())
otelInstaller.beforeAgent(config)
then:
GlobalOpenTelemetry.getTracerProvider() == NoopOpenTelemetry.getInstance().getTracerProvider()
}
def "should NOT initialize noop"() {
given:
def config = Config.builder()
.readProperties([
(OpenTelemetryInstaller.JAVAAGENT_NOOP_CONFIG) : "true",
(OpenTelemetryInstaller.JAVAAGENT_ENABLED_CONFIG): "false"
])
.build()
when:
def otelInstaller = new OpenTelemetryInstaller()
otelInstaller.beforeAgent(config)
then: then:
otelInstaller != null
GlobalOpenTelemetry.getTracerProvider() != NoopOpenTelemetry.getInstance().getTracerProvider() GlobalOpenTelemetry.getTracerProvider() != NoopOpenTelemetry.getInstance().getTracerProvider()
} }