From 4ae56e6789db152712f473cf19ce8d34eedcf78e Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 20 Nov 2024 10:16:59 +0100 Subject: [PATCH] Prevent NPE when using otel metrics bridge asynchronously to agent start --- .../embeddedotel/EmbeddedSdkManager.java | 13 +++++++++-- .../embeddedotel/EmbeddedSdkManagerTest.java | 22 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/src/test/java/co/elastic/apm/agent/embeddedotel/EmbeddedSdkManagerTest.java 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")); + } +}