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
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.config.Config;
import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import java.lang.instrument.Instrumentation;
import net.bytebuddy.agent.builder.AgentBuilder;
/**
* {@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,
* this project uses this SPI to install OpenTelemetry SDK.
* example to install any implementation providers that are used by instrumentations. Can also be
* used to obtain the {@link AutoConfiguredOpenTelemetrySdk}.
*
* <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.
@ -22,18 +23,44 @@ import net.bytebuddy.agent.builder.AgentBuilder;
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
* 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.
* <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.
*
* @deprecated Use {@link #beforeAgent(Config, AutoConfiguredOpenTelemetrySdk)}
*/
@Deprecated
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
* on an {@link Instrumentation}.
*
* @deprecated Use {@link #afterAgent(Config, AutoConfiguredOpenTelemetrySdk)}
*/
@Deprecated
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;
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.loadOrdered;
import static io.opentelemetry.javaagent.tooling.Utils.getResourceName;
import static net.bytebuddy.matcher.ElementMatchers.any;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextStorage;
import io.opentelemetry.context.Scope;
import io.opentelemetry.extension.noopapi.NoopOpenTelemetry;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.javaagent.bootstrap.AgentClassLoader;
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.muzzle.AgentTooling;
import io.opentelemetry.javaagent.tooling.util.Trie;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
import java.util.Collections;
@ -53,7 +57,8 @@ public class AgentInstaller {
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
// condition for delaying the AgentListener initialization is pretty broad and in case it covers
@ -110,7 +115,19 @@ public class AgentInstaller {
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 =
new AgentBuilder.Default()
@ -157,7 +174,11 @@ public class AgentInstaller {
ResettableClassFileTransformer resettableClassFileTransformer = agentBuilder.installOn(inst);
ClassFileTransformerHolder.setClassFileTransformer(resettableClassFileTransformer);
runAfterAgentListeners(agentListeners, config);
if (autoConfiguredSdk != null) {
runAfterAgentListeners(agentListeners, config, autoConfiguredSdk);
}
return resettableClassFileTransformer;
}
@ -178,9 +199,12 @@ public class AgentInstaller {
}
private static void runBeforeAgentListeners(
Iterable<AgentListener> agentListeners, Config config) {
Iterable<AgentListener> agentListeners,
Config config,
AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
for (AgentListener agentListener : agentListeners) {
agentListener.beforeAgent(config);
agentListener.beforeAgent(config, autoConfiguredSdk);
}
}
@ -199,7 +223,9 @@ public class AgentInstaller {
}
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
// class initialization. Some AgentListener implementations may use JRE bootstrap classes
// which touch this class (e.g. JFR classes or some MBeans).
@ -223,10 +249,12 @@ public class AgentInstaller {
&& isAppUsingCustomLogManager()) {
logger.debug("Custom JUL LogManager detected: delaying AgentListener#afterAgent() calls");
registerClassLoadCallback(
"java.util.logging.LogManager", new DelayedAfterAgentCallback(config, agentListeners));
"java.util.logging.LogManager",
new DelayedAfterAgentCallback(config, agentListeners, autoConfiguredSdk));
} else {
for (AgentListener agentListener : agentListeners) {
agentListener.afterAgent(config);
agentListener.afterAgent(config, autoConfiguredSdk);
}
}
}
@ -326,10 +354,15 @@ public class AgentInstaller {
private static class DelayedAfterAgentCallback implements Runnable {
private final Iterable<AgentListener> agentListeners;
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.config = config;
this.autoConfiguredSdk = autoConfiguredSdk;
}
@Override
@ -349,6 +382,7 @@ public class AgentInstaller {
for (AgentListener agentListener : agentListeners) {
try {
agentListener.afterAgent(config);
agentListener.afterAgent(config, autoConfiguredSdk);
} catch (RuntimeException e) {
logger.error("Failed to execute {}", agentListener.getClass().getName(), e);
}

View File

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

View File

@ -5,14 +5,10 @@
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.MeterProvider;
import io.opentelemetry.extension.noopapi.NoopOpenTelemetry;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.javaagent.bootstrap.AgentInitializer;
import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.javaagent.instrumentation.api.OpenTelemetrySdkAccess;
import io.opentelemetry.sdk.OpenTelemetrySdk;
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.metrics.SdkMeterProvider;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@AutoService(AgentListener.class)
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);
}
public class OpenTelemetryInstaller {
/**
* 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")
public static synchronized void installAgentTracer(Config config) {
if (config.getBoolean(JAVAAGENT_ENABLED_CONFIG, true)) {
static AutoConfiguredOpenTelemetrySdk installOpenTelemetrySdk(Config config) {
System.setProperty("io.opentelemetry.context.contextStorageProvider", "default");
if (config.getBoolean(JAVAAGENT_NOOP_CONFIG, false)) {
GlobalOpenTelemetry.set(NoopOpenTelemetry.getInstance());
} else {
System.setProperty("io.opentelemetry.context.contextStorageProvider", "default");
AutoConfiguredOpenTelemetrySdkBuilder builder =
AutoConfiguredOpenTelemetrySdk.builder()
.setResultAsGlobal(true)
.addPropertiesSupplier(config::getAllProperties);
AutoConfiguredOpenTelemetrySdkBuilder builder =
AutoConfiguredOpenTelemetrySdk.builder()
.setResultAsGlobal(true)
.addPropertiesSupplier(config::getAllProperties);
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.");
ClassLoader classLoader = AgentInitializer.getExtensionsClassLoader();
if (classLoader != null) {
// May be null in unit tests.
builder.setServiceClassLoader(classLoader);
}
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 {
void setup() {
GlobalOpenTelemetry.resetForTest()
}
@ -21,40 +20,12 @@ class OpenTelemetryInstallerTest extends Specification {
GlobalOpenTelemetry.resetForTest()
}
def "should initialize noop"() {
given:
def config = Config.builder()
.readProperties([
(OpenTelemetryInstaller.JAVAAGENT_NOOP_CONFIG) : "true",
(OpenTelemetryInstaller.JAVAAGENT_ENABLED_CONFIG): "true"
])
.build()
def "should initialize GlobalOpenTelemetry"() {
when:
def otelInstaller = new OpenTelemetryInstaller()
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)
def otelInstaller = OpenTelemetryInstaller.installOpenTelemetrySdk(Config.builder().build())
then:
otelInstaller != null
GlobalOpenTelemetry.getTracerProvider() != NoopOpenTelemetry.getInstance().getTracerProvider()
}