diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProvider.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProvider.java index ebfaaacf296..eb286b09b94 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProvider.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProvider.java @@ -9,15 +9,19 @@ import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.PROTOCOL_GRPC; import static io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.exporter.internal.ExporterBuilderUtil; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; +import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.autoconfigure.spi.internal.AutoConfigureListener; import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider; import io.opentelemetry.sdk.metrics.export.MetricExporter; +import java.util.concurrent.atomic.AtomicReference; /** * {@link MetricExporter} SPI implementation for {@link OtlpGrpcMetricExporter} and {@link @@ -26,7 +30,11 @@ *

This class is internal and is hence not for public use. Its APIs are unstable and can change * at any time. */ -public class OtlpMetricExporterProvider implements ConfigurableMetricExporterProvider { +public class OtlpMetricExporterProvider + implements ConfigurableMetricExporterProvider, AutoConfigureListener { + + private final AtomicReference meterProviderRef = + new AtomicReference<>(MeterProvider.noop()); @Override public MetricExporter createExporter(ConfigProperties config) { @@ -51,6 +59,7 @@ public MetricExporter createExporter(ConfigProperties config) { config, builder::setAggregationTemporalitySelector); ExporterBuilderUtil.configureOtlpHistogramDefaultAggregation( config, builder::setDefaultAggregationSelector); + builder.setMeterProvider(meterProviderRef::get); return builder.build(); } else if (protocol.equals(PROTOCOL_GRPC)) { @@ -72,6 +81,7 @@ public MetricExporter createExporter(ConfigProperties config) { config, builder::setAggregationTemporalitySelector); ExporterBuilderUtil.configureOtlpHistogramDefaultAggregation( config, builder::setDefaultAggregationSelector); + builder.setMeterProvider(meterProviderRef::get); return builder.build(); } @@ -92,4 +102,9 @@ OtlpHttpMetricExporterBuilder httpBuilder() { OtlpGrpcMetricExporterBuilder grpcBuilder() { return OtlpGrpcMetricExporter.builder(); } + + @Override + public void afterAutoConfigure(OpenTelemetrySdk sdk) { + meterProviderRef.set(sdk.getMeterProvider()); + } } diff --git a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProviderTest.java b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProviderTest.java index e028c6a9571..0bcd064c97d 100644 --- a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProviderTest.java +++ b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpMetricExporterProviderTest.java @@ -5,31 +5,38 @@ package io.opentelemetry.exporter.otlp.internal; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; +import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; +import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.metrics.export.MetricExporter; import java.io.IOException; +import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Path; import java.security.cert.CertificateEncodingException; import java.time.Duration; -import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -94,7 +101,7 @@ void createExporter_UnsupportedProtocol() { () -> provider.createExporter( DefaultConfigProperties.createFromMap( - Collections.singletonMap("otel.exporter.otlp.protocol", "foo")))) + singletonMap("otel.exporter.otlp.protocol", "foo")))) .isInstanceOf(ConfigurationException.class) .hasMessageContaining("Unsupported OTLP metrics protocol: foo"); } @@ -104,13 +111,13 @@ void createExporter_NoMocks() { // Verifies createExporter after resetting the spy overrides Mockito.reset(provider); try (MetricExporter exporter = - provider.createExporter(DefaultConfigProperties.createFromMap(Collections.emptyMap()))) { + provider.createExporter(DefaultConfigProperties.createFromMap(emptyMap()))) { assertThat(exporter).isInstanceOf(OtlpGrpcMetricExporter.class); } try (MetricExporter exporter = provider.createExporter( DefaultConfigProperties.createFromMap( - Collections.singletonMap("otel.exporter.otlp.protocol", "http/protobuf")))) { + singletonMap("otel.exporter.otlp.protocol", "http/protobuf")))) { assertThat(exporter).isInstanceOf(OtlpHttpMetricExporter.class); } } @@ -118,7 +125,7 @@ void createExporter_NoMocks() { @Test void createExporter_GrpcDefaults() { try (MetricExporter exporter = - provider.createExporter(DefaultConfigProperties.createFromMap(Collections.emptyMap()))) { + provider.createExporter(DefaultConfigProperties.createFromMap(emptyMap()))) { assertThat(exporter).isInstanceOf(OtlpGrpcMetricExporter.class); verify(grpcBuilder, times(1)).build(); verify(grpcBuilder).setComponentLoader(any()); @@ -204,8 +211,7 @@ void createExporter_HttpDefaults() { try (MetricExporter exporter = provider.createExporter( DefaultConfigProperties.createFromMap( - Collections.singletonMap( - "otel.exporter.otlp.metrics.protocol", "http/protobuf")))) { + singletonMap("otel.exporter.otlp.metrics.protocol", "http/protobuf")))) { assertThat(exporter).isInstanceOf(OtlpHttpMetricExporter.class); verify(httpBuilder, times(1)).build(); verify(httpBuilder).setComponentLoader(any()); @@ -288,4 +294,107 @@ void createExporter_HttpWithSignalConfiguration() throws CertificateEncodingExce } Mockito.verifyNoInteractions(grpcBuilder); } + + @Test + void meterProviderRef_InitializedWithNoop() throws Exception { + AtomicReference meterProviderRef = getMeterProviderRef(provider); + + assertThat(meterProviderRef.get()).isSameAs(MeterProvider.noop()); + } + + @Test + void afterAutoConfigure_UpdatesMeterProviderRef() throws Exception { + OpenTelemetrySdk sdk = OpenTelemetrySdk.builder().build(); + + AtomicReference meterProviderRef = getMeterProviderRef(provider); + assertThat(meterProviderRef.get()).isSameAs(MeterProvider.noop()); + + provider.afterAutoConfigure(sdk); + + assertThat(meterProviderRef.get()).isSameAs(sdk.getMeterProvider()); + } + + @Test + @SuppressWarnings("unchecked") + void createExporter_GrpcSetsMeterProviderSupplier() { + AtomicReference> capturedSupplier = new AtomicReference<>(); + + OpenTelemetrySdk sdk = OpenTelemetrySdk.builder().build(); + provider.afterAutoConfigure(sdk); + + doAnswer( + invocation -> { + capturedSupplier.set(invocation.getArgument(0)); + return grpcBuilder; + }) + .when(grpcBuilder) + .setMeterProvider(any(Supplier.class)); + + try (MetricExporter exporter = + provider.createExporter(DefaultConfigProperties.createFromMap(emptyMap()))) { + + assertThat(exporter).isInstanceOf(OtlpGrpcMetricExporter.class); + assertThat(capturedSupplier.get()).isNotNull(); + assertThat(capturedSupplier.get().get()).isSameAs(sdk.getMeterProvider()); + } + Mockito.verifyNoInteractions(httpBuilder); + } + + @Test + @SuppressWarnings("unchecked") + void createExporter_HttpSetsMeterProviderSupplier() { + AtomicReference> capturedSupplier = new AtomicReference<>(); + + OpenTelemetrySdk sdk = OpenTelemetrySdk.builder().build(); + provider.afterAutoConfigure(sdk); + + doAnswer( + invocation -> { + capturedSupplier.set(invocation.getArgument(0)); + return httpBuilder; + }) + .when(httpBuilder) + .setMeterProvider(any(Supplier.class)); + + try (MetricExporter exporter = + provider.createExporter( + DefaultConfigProperties.createFromMap( + singletonMap("otel.exporter.otlp.metrics.protocol", "http/protobuf")))) { + + assertThat(exporter).isInstanceOf(OtlpHttpMetricExporter.class); + assertThat(capturedSupplier.get()).isNotNull(); + assertThat(capturedSupplier.get().get()).isSameAs(sdk.getMeterProvider()); + } + Mockito.verifyNoInteractions(grpcBuilder); + } + + @Test + @SuppressWarnings("unchecked") + void meterProviderSupplier_ReturnsNoopBeforeAutoConfigure() { + AtomicReference> capturedSupplier = new AtomicReference<>(); + + doAnswer( + invocation -> { + capturedSupplier.set(invocation.getArgument(0)); + return grpcBuilder; + }) + .when(grpcBuilder) + .setMeterProvider(any(Supplier.class)); + + try (MetricExporter exporter = + provider.createExporter(DefaultConfigProperties.createFromMap(emptyMap()))) { + + assertThat(exporter).isInstanceOf(OtlpGrpcMetricExporter.class); + assertThat(capturedSupplier.get()).isNotNull(); + assertThat(capturedSupplier.get().get()).isSameAs(MeterProvider.noop()); + } + } + + @SuppressWarnings("unchecked") + private static AtomicReference getMeterProviderRef( + OtlpMetricExporterProvider provider) throws Exception { + Field field = OtlpMetricExporterProvider.class.getDeclaredField("meterProviderRef"); + field.setAccessible(true); + return (AtomicReference) field.get(provider); + } } diff --git a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/ConfigurableMetricExporterTest.java b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/ConfigurableMetricExporterTest.java index bc32ebc84a4..e46d0bd7718 100644 --- a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/ConfigurableMetricExporterTest.java +++ b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/ConfigurableMetricExporterTest.java @@ -10,6 +10,7 @@ import com.google.common.collect.ImmutableMap; import io.opentelemetry.exporter.logging.LoggingMetricExporter; +import io.opentelemetry.exporter.otlp.internal.OtlpMetricExporterProvider; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; import io.opentelemetry.internal.testing.CleanupExtension; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; @@ -52,7 +53,8 @@ void configureExporter_spiExporter() { assertThat(spiHelper.getListeners()) .satisfiesExactlyInAnyOrder( listener -> - assertThat(listener).isInstanceOf(TestConfigurableMetricExporterProvider.class)); + assertThat(listener).isInstanceOf(TestConfigurableMetricExporterProvider.class), + listener -> assertThat(listener).isInstanceOf(OtlpMetricExporterProvider.class)); } }