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:
parent
f7f3f5685f
commit
45dca4fc5d
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue