diff --git a/exporters/prometheus/build.gradle.kts b/exporters/prometheus/build.gradle.kts
index 81ca190fa0b..166e34aa7ca 100644
--- a/exporters/prometheus/build.gradle.kts
+++ b/exporters/prometheus/build.gradle.kts
@@ -11,6 +11,8 @@ otelJava.moduleName.set("io.opentelemetry.exporter.prometheus")
dependencies {
api(project(":sdk:metrics"))
+ implementation(project(":sdk-extensions:autoconfigure-spi"))
+
compileOnly("com.sun.net.httpserver:http")
testImplementation("com.google.guava:guava")
diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusCustomizerProvider.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusCustomizerProvider.java
new file mode 100644
index 00000000000..f657e14eea7
--- /dev/null
+++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusCustomizerProvider.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.prometheus.internal;
+
+import io.opentelemetry.exporter.prometheus.PrometheusHttpServer;
+import io.opentelemetry.exporter.prometheus.PrometheusHttpServerBuilder;
+import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
+import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
+
+/**
+ * SPI implementation for {@link PrometheusHttpServer}.
+ *
+ *
This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
+ */
+public class PrometheusCustomizerProvider implements AutoConfigurationCustomizerProvider {
+
+ @Override
+ public void customize(AutoConfigurationCustomizer autoConfiguration) {
+ autoConfiguration.addMeterProviderCustomizer(
+ (builder, config) -> {
+ boolean prometheusEnabled =
+ config.getList("otel.metrics.exporter").contains("prometheus");
+ if (prometheusEnabled) {
+ builder.registerMetricReader(configurePrometheusHttpServer(config));
+ }
+ return builder;
+ });
+ }
+
+ // Visible for test
+ static PrometheusHttpServer configurePrometheusHttpServer(ConfigProperties config) {
+ PrometheusHttpServerBuilder prometheusBuilder = PrometheusHttpServer.builder();
+
+ Integer port = config.getInt("otel.exporter.prometheus.port");
+ if (port != null) {
+ prometheusBuilder.setPort(port);
+ }
+ String host = config.getString("otel.exporter.prometheus.host");
+ if (host != null) {
+ prometheusBuilder.setHost(host);
+ }
+ return prometheusBuilder.build();
+ }
+}
diff --git a/exporters/prometheus/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider b/exporters/prometheus/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider
new file mode 100644
index 00000000000..1522bd5c348
--- /dev/null
+++ b/exporters/prometheus/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider
@@ -0,0 +1 @@
+io.opentelemetry.exporter.prometheus.internal.PrometheusCustomizerProvider
\ No newline at end of file
diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/internal/PrometheusCustomizerProviderTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/internal/PrometheusCustomizerProviderTest.java
new file mode 100644
index 00000000000..69aa70072a2
--- /dev/null
+++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/internal/PrometheusCustomizerProviderTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.prometheus.internal;
+
+import static org.assertj.core.api.Assertions.as;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+import com.sun.net.httpserver.HttpServer;
+import io.opentelemetry.exporter.prometheus.PrometheusHttpServer;
+import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
+import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
+import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiFunction;
+import org.assertj.core.api.InstanceOfAssertFactories;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+class PrometheusCustomizerProviderTest {
+
+ private static final PrometheusCustomizerProvider provider = new PrometheusCustomizerProvider();
+
+ private SdkMeterProviderBuilder meterProviderBuilder;
+
+ @Mock private ConfigProperties configProperties;
+
+ @Mock private AutoConfigurationCustomizer customizer;
+
+ @BeforeEach
+ void setup() {
+ meterProviderBuilder = SdkMeterProvider.builder();
+ doAnswer(
+ invocation -> {
+ BiFunction
+ meterProviderCustomizer = invocation.getArgument(0);
+ meterProviderBuilder =
+ meterProviderCustomizer.apply(meterProviderBuilder, configProperties);
+ return null;
+ })
+ .when(customizer)
+ .addMeterProviderCustomizer(any());
+ }
+
+ @Test
+ void customize_PrometheusEnabled() {
+ when(configProperties.getList("otel.metrics.exporter"))
+ .thenReturn(Collections.singletonList("prometheus"));
+ provider.customize(customizer);
+
+ try (SdkMeterProvider meterProvider = meterProviderBuilder.build()) {
+ assertThat(meterProvider)
+ .extracting("registeredReaders", as(InstanceOfAssertFactories.list(Object.class)))
+ .satisfiesExactly(
+ registeredReader ->
+ assertThat(registeredReader)
+ .extracting("metricReader")
+ .isInstanceOf(PrometheusHttpServer.class));
+ }
+ }
+
+ @Test
+ void customize_PrometheusDisabled() {
+ when(configProperties.getList("otel.metrics.exporter"))
+ .thenReturn(Collections.singletonList("foo"));
+ provider.customize(customizer);
+
+ try (SdkMeterProvider meterProvider = meterProviderBuilder.build()) {
+ assertThat(meterProvider)
+ .extracting("registeredReaders", as(InstanceOfAssertFactories.list(Object.class)))
+ .isEmpty();
+ }
+ }
+
+ @Test
+ void configurePrometheusHttpServer_Default() {
+ try (PrometheusHttpServer prometheusHttpServer =
+ PrometheusCustomizerProvider.configurePrometheusHttpServer(
+ DefaultConfigProperties.createForTest(Collections.emptyMap()))) {
+ assertThat(prometheusHttpServer)
+ .extracting("server", as(InstanceOfAssertFactories.type(HttpServer.class)))
+ .satisfies(
+ server -> {
+ assertThat(server.getAddress().getHostName()).isEqualTo("0:0:0:0:0:0:0:0");
+ assertThat(server.getAddress().getPort()).isEqualTo(9464);
+ });
+ }
+ }
+
+ @Test
+ void configurePrometheusHttpServer_WithConfiguration() throws IOException {
+ // Find a random unused port. There's a small race if another process takes it before we
+ // initialize. Consider adding retries to this test if it flakes, presumably it never will on
+ // CI since there's no prometheus there blocking the well-known port.
+ int port;
+ try (ServerSocket socket2 = new ServerSocket(0)) {
+ port = socket2.getLocalPort();
+ }
+
+ Map config = new HashMap<>();
+ config.put("otel.exporter.prometheus.host", "localhost");
+ config.put("otel.exporter.prometheus.port", String.valueOf(port));
+
+ try (PrometheusHttpServer prometheusHttpServer =
+ PrometheusCustomizerProvider.configurePrometheusHttpServer(
+ DefaultConfigProperties.createForTest(config))) {
+ assertThat(prometheusHttpServer)
+ .extracting("server", as(InstanceOfAssertFactories.type(HttpServer.class)))
+ .satisfies(
+ server -> {
+ assertThat(server.getAddress().getHostName()).isEqualTo("localhost");
+ assertThat(server.getAddress().getPort()).isEqualTo(port);
+ });
+ }
+ }
+}
diff --git a/sdk-extensions/autoconfigure/build.gradle.kts b/sdk-extensions/autoconfigure/build.gradle.kts
index 834439ef1cd..37134675c47 100644
--- a/sdk-extensions/autoconfigure/build.gradle.kts
+++ b/sdk-extensions/autoconfigure/build.gradle.kts
@@ -14,8 +14,6 @@ dependencies {
implementation(project(":semconv"))
- compileOnly(project(":exporters:prometheus"))
-
annotationProcessor("com.google.auto.value:auto-value")
testImplementation(project(":sdk:trace-shaded-deps"))
@@ -60,7 +58,6 @@ testing {
implementation(project(":exporters:logging"))
implementation(project(":exporters:otlp:all"))
implementation(project(":exporters:otlp:logs"))
- implementation(project(":exporters:prometheus"))
implementation(project(":exporters:zipkin"))
}
}
@@ -129,23 +126,6 @@ testing {
runtimeOnly("io.grpc:grpc-netty-shaded")
}
}
- val testPrometheus by registering(JvmTestSuite::class) {
- dependencies {
- implementation(project(":exporters:prometheus"))
-
- implementation("com.linecorp.armeria:armeria-junit5")
- }
-
- targets {
- all {
- testTask {
- environment("OTEL_TRACES_EXPORTER", "none")
- environment("OTEL_METRICS_EXPORTER", "prometheus")
- environment("OTEL_METRIC_EXPORT_INTERVAL", "10")
- }
- }
- }
- }
}
}
diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/ClasspathUtil.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/ClasspathUtil.java
deleted file mode 100644
index 82d67de1145..00000000000
--- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/ClasspathUtil.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.sdk.autoconfigure;
-
-import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
-
-final class ClasspathUtil {
-
- @SuppressWarnings("UnusedException")
- static void checkClassExists(String className, String featureName, String requiredLibrary) {
- try {
- Class.forName(className);
- } catch (ClassNotFoundException unused) {
- throw new ConfigurationException(
- featureName
- + " enabled but "
- + requiredLibrary
- + " not found on classpath. "
- + "Make sure to add it as a dependency to enable this feature.");
- }
- }
-
- private ClasspathUtil() {}
-}
diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfiguration.java
index fac7c3ac2bf..53f986a8564 100644
--- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfiguration.java
+++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfiguration.java
@@ -15,6 +15,7 @@
import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
@@ -70,6 +71,7 @@ static List configureMetricReaders(
exporterName ->
MetricExporterConfiguration.configureReader(
exporterName, config, serviceClassLoader, metricExporterCustomizer))
+ .filter(Objects::nonNull)
.collect(Collectors.toList());
}
diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/MetricExporterConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/MetricExporterConfiguration.java
index eec05b99efd..cb9411eeb7f 100644
--- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/MetricExporterConfiguration.java
+++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/MetricExporterConfiguration.java
@@ -5,8 +5,6 @@
package io.opentelemetry.sdk.autoconfigure;
-import io.opentelemetry.exporter.prometheus.PrometheusHttpServer;
-import io.opentelemetry.exporter.prometheus.PrometheusHttpServerBuilder;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider;
@@ -17,6 +15,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
+import javax.annotation.Nullable;
final class MetricExporterConfiguration {
@@ -30,6 +29,7 @@ final class MetricExporterConfiguration {
EXPORTER_ARTIFACT_ID_BY_NAME.put("otlp", "opentelemetry-exporter-otlp");
}
+ @Nullable
static MetricReader configureReader(
String name,
ConfigProperties config,
@@ -37,7 +37,16 @@ static MetricReader configureReader(
BiFunction super MetricExporter, ConfigProperties, ? extends MetricExporter>
metricExporterCustomizer) {
if (name.equals("prometheus")) {
- return configurePrometheusMetricReader(config);
+ // PrometheusHttpServer is implemented as MetricReader (not MetricExporter) and uses
+ // the AutoConfigurationCustomizer#addMeterProviderCustomizer SPI hook instead of
+ // ConfigurableMetricExporterProvider. While the prometheus SPI hook is not handled here,
+ // the classpath check here provides uniform exception messages.
+ try {
+ Class.forName("io.opentelemetry.exporter.prometheus.PrometheusHttpServer");
+ return null;
+ } catch (ClassNotFoundException unused) {
+ throw missingExporterException("prometheus", "opentelemetry-exporter-prometheus");
+ }
}
NamedSpiManager spiExportersManager =
@@ -45,7 +54,10 @@ static MetricReader configureReader(
MetricExporter metricExporter = configureExporter(name, spiExportersManager);
metricExporter = metricExporterCustomizer.apply(metricExporter, config);
- return configurePeriodicMetricReader(config, metricExporter);
+
+ return PeriodicMetricReader.builder(metricExporter)
+ .setInterval(config.getDuration("otel.metric.export.interval", DEFAULT_EXPORT_INTERVAL))
+ .build();
}
// Visible for testing
@@ -66,42 +78,21 @@ static MetricExporter configureExporter(
if (metricExporter == null) {
String artifactId = EXPORTER_ARTIFACT_ID_BY_NAME.get(name);
if (artifactId != null) {
- throw new ConfigurationException(
- "otel.metrics.exporter set to \""
- + name
- + "\" but "
- + artifactId
- + " not found on classpath. Make sure to add it as a dependency.");
+ throw missingExporterException(name, artifactId);
}
throw new ConfigurationException("Unrecognized value for otel.metrics.exporter: " + name);
}
return metricExporter;
}
- private static PeriodicMetricReader configurePeriodicMetricReader(
- ConfigProperties config, MetricExporter exporter) {
-
- return PeriodicMetricReader.builder(exporter)
- .setInterval(config.getDuration("otel.metric.export.interval", DEFAULT_EXPORT_INTERVAL))
- .build();
- }
-
- private static PrometheusHttpServer configurePrometheusMetricReader(ConfigProperties config) {
- ClasspathUtil.checkClassExists(
- "io.opentelemetry.exporter.prometheus.PrometheusHttpServer",
- "Prometheus Metrics Server",
- "opentelemetry-exporter-prometheus");
- PrometheusHttpServerBuilder prom = PrometheusHttpServer.builder();
-
- Integer port = config.getInt("otel.exporter.prometheus.port");
- if (port != null) {
- prom.setPort(port);
- }
- String host = config.getString("otel.exporter.prometheus.host");
- if (host != null) {
- prom.setHost(host);
- }
- return prom.build();
+ private static ConfigurationException missingExporterException(
+ String exporterName, String artifactId) {
+ return new ConfigurationException(
+ "otel.metrics.exporter set to \""
+ + exporterName
+ + "\" but "
+ + artifactId
+ + " not found on classpath. Make sure to add it as a dependency.");
}
private MetricExporterConfiguration() {}
diff --git a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/MetricExporterConfigurationTest.java b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/MetricExporterConfigurationTest.java
index 48a9138d420..c5d6f5187d4 100644
--- a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/MetricExporterConfigurationTest.java
+++ b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/MetricExporterConfigurationTest.java
@@ -6,6 +6,7 @@
package io.opentelemetry.sdk.autoconfigure;
import static io.opentelemetry.sdk.autoconfigure.MetricExporterConfiguration.configureExporter;
+import static io.opentelemetry.sdk.autoconfigure.MetricExporterConfiguration.configureReader;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
@@ -20,6 +21,21 @@ class MetricExporterConfigurationTest {
private static final ConfigProperties EMPTY =
DefaultConfigProperties.createForTest(Collections.emptyMap());
+ @Test
+ void configureReader_PrometheusNotOnClasspath() {
+ assertThatThrownBy(
+ () ->
+ configureReader(
+ "prometheus",
+ EMPTY,
+ MetricExporterConfiguration.class.getClassLoader(),
+ (a, b) -> a))
+ .isInstanceOf(ConfigurationException.class)
+ .hasMessage(
+ "otel.metrics.exporter set to \"prometheus\" but opentelemetry-exporter-prometheus"
+ + " not found on classpath. Make sure to add it as a dependency.");
+ }
+
@Test
void configureExporter_KnownSpiExportersNotOnClasspath() {
NamedSpiManager spiExportersManager =
@@ -46,19 +62,4 @@ void configureExporter_KnownSpiExportersNotOnClasspath() {
assertThatThrownBy(() -> configureExporter("foo", spiExportersManager))
.hasMessage("Unrecognized value for otel.metrics.exporter: foo");
}
-
- @Test
- void configureReader_Prometheus() {
- assertThatThrownBy(
- () ->
- MetricExporterConfiguration.configureReader(
- "prometheus",
- EMPTY,
- MetricExporterConfigurationTest.class.getClassLoader(),
- (a, unused) -> a))
- .isInstanceOf(ConfigurationException.class)
- .hasMessageContaining(
- "Prometheus Metrics Server enabled but opentelemetry-exporter-prometheus not found on "
- + "classpath");
- }
}
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 bdfb28aa9c3..319d8c9bd7e 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,7 +10,6 @@
import com.google.common.collect.ImmutableMap;
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
-import io.opentelemetry.exporter.prometheus.PrometheusHttpServer;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
@@ -105,7 +104,7 @@ void defaultExporter() {
void configureMultipleMetricExporters() {
ConfigProperties config =
DefaultConfigProperties.createForTest(
- ImmutableMap.of("otel.metrics.exporter", "otlp,prometheus"));
+ ImmutableMap.of("otel.metrics.exporter", "otlp,logging"));
assertThat(
MeterProviderConfiguration.configureMetricReaders(
@@ -114,7 +113,7 @@ void configureMultipleMetricExporters() {
(metricExporter, unused) -> metricExporter))
.hasSize(2)
.hasAtLeastOneElementOfType(PeriodicMetricReader.class)
- .hasAtLeastOneElementOfType(PrometheusHttpServer.class)
+ .hasAtLeastOneElementOfType(PeriodicMetricReader.class)
.allSatisfy(metricReader -> metricReader.shutdown().join(10, TimeUnit.SECONDS));
}
}
diff --git a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/MetricExporterConfigurationTest.java b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/MetricExporterConfigurationTest.java
index cd00addb2f6..dcec4c24042 100644
--- a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/MetricExporterConfigurationTest.java
+++ b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/MetricExporterConfigurationTest.java
@@ -5,6 +5,7 @@
package io.opentelemetry.sdk.autoconfigure;
+import static org.assertj.core.api.Assertions.as;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -12,20 +13,73 @@
import io.opentelemetry.exporter.logging.LoggingMetricExporter;
import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingMetricExporter;
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
+import io.opentelemetry.exporter.prometheus.PrometheusHttpServer;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
+import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider;
+import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
+import io.opentelemetry.sdk.metrics.export.MetricReader;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
class MetricExporterConfigurationTest {
+ private static final ConfigProperties EMPTY =
+ DefaultConfigProperties.createForTest(Collections.emptyMap());
+
+ @Test
+ void configureReader_PrometheusOnClasspath() {
+ assertThat(
+ MetricExporterConfiguration.configureReader(
+ "prometheus",
+ EMPTY,
+ MetricExporterConfigurationTest.class.getClassLoader(),
+ (a, b) -> a))
+ .isNull();
+ }
+
+ /**
+ * Prometheus uses the {@link AutoConfigurationCustomizerProvider} SPI instead of {@link
+ * ConfigurableMetricExporterProvider} because it is implemented as a {@link MetricReader}. While
+ * {@link MetricExporterConfiguration} does not load this SPI, the test code lives here alongside
+ * tests of the other known SPI metric exporters.
+ */
+ @Test
+ void autoConfiguredOpenTelemetrySdk_PrometheusOnClasspath() {
+ Map config = new HashMap<>();
+ config.put("otel.traces.exporter", "none");
+ config.put("otel.metrics.exporter", "prometheus");
+ config.put("otel.logs.exporter", "none");
+
+ OpenTelemetrySdk sdk =
+ AutoConfiguredOpenTelemetrySdk.builder()
+ .setResultAsGlobal(false)
+ .setConfig(DefaultConfigProperties.createForTest(config))
+ .build()
+ .getOpenTelemetrySdk();
+ try (SdkMeterProvider meterProvider = sdk.getSdkMeterProvider()) {
+ assertThat(meterProvider)
+ .extracting("registeredReaders", as(InstanceOfAssertFactories.list(Object.class)))
+ .satisfiesExactly(
+ registeredReader ->
+ assertThat(registeredReader)
+ .extracting("metricReader")
+ .isInstanceOf(PrometheusHttpServer.class));
+ }
+ }
+
@Test
void configureExporter_KnownSpiExportersOnClasspath() {
NamedSpiManager spiExportersManager =
MetricExporterConfiguration.metricExporterSpiManager(
- DefaultConfigProperties.createForTest(Collections.emptyMap()),
- ConfigurableMetricExporterTest.class.getClassLoader());
+ EMPTY, ConfigurableMetricExporterTest.class.getClassLoader());
assertThat(MetricExporterConfiguration.configureExporter("logging", spiExportersManager))
.isInstanceOf(LoggingMetricExporter.class);
diff --git a/sdk-extensions/autoconfigure/src/testPrometheus/java/io/opentelemetry/sdk/autoconfigure/PrometheusTest.java b/sdk-extensions/autoconfigure/src/testPrometheus/java/io/opentelemetry/sdk/autoconfigure/PrometheusTest.java
deleted file mode 100644
index 0655f731557..00000000000
--- a/sdk-extensions/autoconfigure/src/testPrometheus/java/io/opentelemetry/sdk/autoconfigure/PrometheusTest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.sdk.autoconfigure;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import com.linecorp.armeria.client.WebClient;
-import com.linecorp.armeria.common.AggregatedHttpResponse;
-import io.opentelemetry.api.common.Attributes;
-import io.opentelemetry.sdk.OpenTelemetrySdk;
-import java.io.IOException;
-import java.net.ServerSocket;
-import org.junit.jupiter.api.Test;
-
-class PrometheusTest {
-
- @Test
- void prometheusExporter() throws Exception {
- int port = 9464;
- // Just use prometheus standard port if it's available
- try (ServerSocket unused = new ServerSocket(port)) {
- // Port available
- } catch (IOException e) {
- // Otherwise use a random port. There's a small race if another process takes it before we
- // initialize. Consider adding retries to this test if it flakes, presumably it never will on
- // CI since there's no prometheus there blocking the well-known port.
- try (ServerSocket socket2 = new ServerSocket(0)) {
- port = socket2.getLocalPort();
- }
- }
- System.setProperty("otel.exporter.prometheus.host", "127.0.0.1");
- System.setProperty("otel.exporter.prometheus.port", String.valueOf(port));
- OpenTelemetrySdk openTelemetrySdk =
- AutoConfiguredOpenTelemetrySdk.initialize().getOpenTelemetrySdk();
-
- openTelemetrySdk
- .getMeterProvider()
- .get("test")
- .gaugeBuilder("test")
- .ofLongs()
- .buildWithCallback(result -> result.record(2, Attributes.empty()));
-
- WebClient client = WebClient.of("http://127.0.0.1:" + port);
- AggregatedHttpResponse response = client.get("/metrics").aggregate().join();
- assertThat(response.contentUtf8()).contains("test 2.0");
- }
-}