diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index f9e5649743..efbc03a7cb 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -31,6 +31,10 @@ Use subheadings with the "=====" level for adding notes for unreleased changes: === Unreleased +[float] +===== Bug fixes +* Prevent NPE in OpenTelemetry metrics bridge in case of asynchronous agent start - {pull}3880[#3880] + [[release-notes-1.x]] === Java Agent version 1.x diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/src/main/java/co/elastic/apm/agent/embeddedotel/EmbeddedSdkManager.java b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/src/main/java/co/elastic/apm/agent/embeddedotel/EmbeddedSdkManager.java index 95c57c60c3..3533bb4761 100644 --- a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/src/main/java/co/elastic/apm/agent/embeddedotel/EmbeddedSdkManager.java +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/src/main/java/co/elastic/apm/agent/embeddedotel/EmbeddedSdkManager.java @@ -18,11 +18,12 @@ */ package co.elastic.apm.agent.embeddedotel; -import co.elastic.apm.agent.tracer.AbstractLifecycleListener; import co.elastic.apm.agent.embeddedotel.proxy.ProxyMeterProvider; import co.elastic.apm.agent.sdk.logging.Logger; import co.elastic.apm.agent.sdk.logging.LoggerFactory; +import co.elastic.apm.agent.tracer.AbstractLifecycleListener; import co.elastic.apm.agent.tracer.Tracer; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; @@ -65,6 +66,10 @@ public ProxyMeterProvider getMeterProvider() { if (sdkInstance == null) { startSdk(); } + if (sdkInstance == null) { + logger.warn("Returning NoOp-MeterProvider because OpenTelemetry metrics SDK could not be initialized!"); + return new ProxyMeterProvider(MeterProvider.noop()); + } return new ProxyMeterProvider(sdkInstance); } @@ -78,7 +83,11 @@ synchronized void reset() { } private synchronized void startSdk() { - if (isShutdown || sdkInstance != null || tracer == null) { + if (isShutdown || sdkInstance != null) { + return; + } + if (tracer == null) { + logger.warn("Cannot initialize OpenTelemetry metrics SDK because tracer has not started yet"); return; } logger.debug("Starting embedded OpenTelemetry metrics SDK"); diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/src/test/java/co/elastic/apm/agent/embeddedotel/EmbeddedSdkManagerTest.java b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/src/test/java/co/elastic/apm/agent/embeddedotel/EmbeddedSdkManagerTest.java new file mode 100644 index 0000000000..0dca26b609 --- /dev/null +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/src/test/java/co/elastic/apm/agent/embeddedotel/EmbeddedSdkManagerTest.java @@ -0,0 +1,22 @@ +package co.elastic.apm.agent.embeddedotel; + +import co.elastic.apm.agent.embeddedotel.proxy.ProxyMeter; +import co.elastic.apm.agent.tracer.Tracer; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EmbeddedSdkManagerTest { + + /** + * The instrumentation of the agent is performed before {@link EmbeddedSdkManager#init(Tracer)} is invoked. + * This means if the agent is started asynchronously, it can happen that {@link EmbeddedSdkManager#getMeterProvider()} + * is invoked before the tracer has been provided. + * This test verifies that in that case no exception occurs and a noop-meter implementation is used. + */ + @Test + public void ensureNoExceptionOnMissingTracer() throws Exception { + ProxyMeter meter = new EmbeddedSdkManager().getMeterProvider().get("foobar"); + assertThat(meter.getDelegate()).isInstanceOf(Class.forName("io.opentelemetry.api.metrics.DefaultMeter")); + } +}