diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt index df26146497b..c464bbdd2e2 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt @@ -1,2 +1,4 @@ Comparing source compatibility of against -No changes. \ No newline at end of file +*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer addMetricReaderCustomizer(java.util.function.BiFunction) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure.txt index df26146497b..04f3e512be6 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure.txt @@ -1,2 +1,4 @@ Comparing source compatibility of against -No changes. \ No newline at end of file +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder addMetricReaderCustomizer(java.util.function.BiFunction) diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/AutoConfigurationCustomizer.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/AutoConfigurationCustomizer.java index 03edca120bc..d64ec24f49d 100644 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/AutoConfigurationCustomizer.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/AutoConfigurationCustomizer.java @@ -11,6 +11,7 @@ import io.opentelemetry.sdk.logs.export.LogRecordExporter; import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; import io.opentelemetry.sdk.metrics.export.MetricExporter; +import io.opentelemetry.sdk.metrics.export.MetricReader; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; import io.opentelemetry.sdk.trace.SpanProcessor; @@ -150,12 +151,25 @@ default AutoConfigurationCustomizer addMeterProviderCustomizer( * *

Multiple calls will execute the customizers in order. */ + @SuppressWarnings("UnusedReturnValue") default AutoConfigurationCustomizer addMetricExporterCustomizer( BiFunction exporterCustomizer) { return this; } + /** + * Adds a {@link BiFunction} to invoke with the autoconfigured {@link MetricReader} to allow + * customization. The return value of the {@link BiFunction} will replace the passed-in argument. + * + *

Multiple calls will execute the customizers in order. + */ + @SuppressWarnings("UnusedReturnValue") + default AutoConfigurationCustomizer addMetricReaderCustomizer( + BiFunction readerCustomizer) { + return this; + } + /** * Adds a {@link BiFunction} to invoke the with the {@link SdkLoggerProviderBuilder} to allow * customization. The return value of the {@link BiFunction} will replace the passed-in argument. diff --git a/sdk-extensions/autoconfigure/build.gradle.kts b/sdk-extensions/autoconfigure/build.gradle.kts index 62fcd639c79..04fbde232fc 100644 --- a/sdk-extensions/autoconfigure/build.gradle.kts +++ b/sdk-extensions/autoconfigure/build.gradle.kts @@ -53,6 +53,7 @@ testing { implementation(project(":exporters:logging-otlp")) implementation(project(":exporters:otlp:all")) implementation(project(":exporters:prometheus")) + implementation("io.prometheus:prometheus-metrics-exporter-httpserver") implementation(project(":exporters:zipkin")) implementation(project(":sdk:testing")) implementation(project(":sdk:trace-shaded-deps")) diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java index 8e332c2a2b7..ac9d39a2a71 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java @@ -28,6 +28,7 @@ import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; import io.opentelemetry.sdk.metrics.export.MetricExporter; +import io.opentelemetry.sdk.metrics.export.MetricReader; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; @@ -83,6 +84,8 @@ public final class AutoConfiguredOpenTelemetrySdkBuilder implements AutoConfigur meterProviderCustomizer = (a, unused) -> a; private BiFunction metricExporterCustomizer = (a, unused) -> a; + private BiFunction + metricReaderCustomizer = (a, unused) -> a; private BiFunction loggerProviderCustomizer = (a, unused) -> a; @@ -283,6 +286,20 @@ public AutoConfiguredOpenTelemetrySdkBuilder addMetricExporterCustomizer( return this; } + /** + * Adds a {@link BiFunction} to invoke with the autoconfigured {@link MetricReader} to allow + * customization. The return value of the {@link BiFunction} will replace the passed-in argument. + * + *

Multiple calls will execute the customizers in order. + */ + @Override + public AutoConfiguredOpenTelemetrySdkBuilder addMetricReaderCustomizer( + BiFunction readerCustomizer) { + requireNonNull(readerCustomizer, "readerCustomizer"); + this.metricReaderCustomizer = mergeCustomizer(this.metricReaderCustomizer, readerCustomizer); + return this; + } + /** * Adds a {@link BiFunction} to invoke the with the {@link SdkLoggerProviderBuilder} to allow * customization. The return value of the {@link BiFunction} will replace the passed-in argument. @@ -406,7 +423,12 @@ public AutoConfiguredOpenTelemetrySdk build() { SdkMeterProviderBuilder meterProviderBuilder = SdkMeterProvider.builder(); meterProviderBuilder.setResource(resource); MeterProviderConfiguration.configureMeterProvider( - meterProviderBuilder, config, spiHelper, metricExporterCustomizer, closeables); + meterProviderBuilder, + config, + spiHelper, + metricReaderCustomizer, + metricExporterCustomizer, + closeables); meterProviderBuilder = meterProviderCustomizer.apply(meterProviderBuilder, config); SdkMeterProvider meterProvider = meterProviderBuilder.build(); closeables.add(meterProvider); 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 525dc54974a..3689161b906 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 @@ -29,6 +29,8 @@ static void configureMeterProvider( SdkMeterProviderBuilder meterProviderBuilder, ConfigProperties config, SpiHelper spiHelper, + BiFunction + metricReaderCustomizer, BiFunction metricExporterCustomizer, List closeables) { @@ -56,7 +58,8 @@ static void configureMeterProvider( throw new ConfigurationException("otel.experimental.metrics.cardinality.limit must be >= 1"); } - configureMetricReaders(config, spiHelper, metricExporterCustomizer, closeables) + configureMetricReaders( + config, spiHelper, metricReaderCustomizer, metricExporterCustomizer, closeables) .forEach( reader -> SdkMeterProviderUtil.registerMetricReaderWithCardinalitySelector( @@ -66,6 +69,8 @@ static void configureMeterProvider( static List configureMetricReaders( ConfigProperties config, SpiHelper spiHelper, + BiFunction + metricReaderCustomizer, BiFunction metricExporterCustomizer, List closeables) { @@ -85,7 +90,12 @@ static List configureMetricReaders( .map( exporterName -> MetricExporterConfiguration.configureReader( - exporterName, config, spiHelper, metricExporterCustomizer, closeables)) + exporterName, + config, + spiHelper, + metricReaderCustomizer, + metricExporterCustomizer, + closeables)) .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 2aa968a82bb..3b4a1da1658 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 @@ -42,6 +42,8 @@ static MetricReader configureReader( String name, ConfigProperties config, SpiHelper spiHelper, + BiFunction + metricReaderCustomizer, BiFunction metricExporterCustomizer, List closeables) { @@ -56,7 +58,14 @@ static MetricReader configureReader( MetricReader metricReader = configureMetricReader(name, spiMetricReadersManager); if (metricReader != null) { closeables.add(metricReader); - return metricReader; + + // Customize metric reader + MetricReader customizedMetricReader = metricReaderCustomizer.apply(metricReader, config); + if (customizedMetricReader != metricReader) { + closeables.add(customizedMetricReader); + } + + return customizedMetricReader; } // No exporter or reader with the name throw new ConfigurationException("Unrecognized value for otel.metrics.exporter: " + name); @@ -74,7 +83,11 @@ static MetricReader configureReader( .setInterval(config.getDuration("otel.metric.export.interval", DEFAULT_EXPORT_INTERVAL)) .build(); closeables.add(reader); - return reader; + MetricReader customizedMetricReader = metricReaderCustomizer.apply(reader, config); + if (customizedMetricReader != reader) { + closeables.add(customizedMetricReader); + } + return customizedMetricReader; } // Visible for testing diff --git a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfigurationTest.java b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfigurationTest.java index c501d28e3cb..900ba509d71 100644 --- a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfigurationTest.java +++ b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfigurationTest.java @@ -54,6 +54,7 @@ private static ObjectAssert assertExemplarFilter(Map a, + (a, b) -> a, new ArrayList<>()); return assertThat(builder) .extracting("exemplarFilter", as(InstanceOfAssertFactories.type(ExemplarFilter.class))); diff --git a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java index 6bf5c74ccc5..f1f880441e6 100644 --- a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java +++ b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java @@ -5,12 +5,16 @@ package io.opentelemetry.sdk.autoconfigure; +import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import com.linecorp.armeria.client.WebClient; import io.github.netmikey.logunit.api.LogCapturer; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.events.GlobalEventEmitterProvider; +import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; import io.opentelemetry.sdk.OpenTelemetrySdk; import java.lang.reflect.Field; import org.junit.jupiter.api.BeforeEach; @@ -31,6 +35,25 @@ void setUp() { GlobalEventEmitterProvider.resetForTest(); } + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test + void build_addMetricReaderCustomizerPrometheus() { + AutoConfiguredOpenTelemetrySdkBuilder builder = AutoConfiguredOpenTelemetrySdk.builder(); + builder.addPropertiesSupplier(() -> singletonMap("otel.metrics.exporter", "prometheus")); + + int port = FreePortFinder.getFreePort(); + builder.addMetricReaderCustomizer( + (reader, config) -> { + assertThat(reader).isInstanceOf(PrometheusHttpServer.class); + return PrometheusHttpServer.builder().setPort(port).build(); + }); + + try (OpenTelemetrySdk ignored = builder.build().getOpenTelemetrySdk()) { + WebClient client = WebClient.builder("http://localhost:" + port).build(); + assertThatCode(() -> client.get("/metrics")).doesNotThrowAnyException(); + } + } + @Test void initializeAndGet() { try (OpenTelemetrySdk sdk = AutoConfiguredOpenTelemetrySdk.initialize().getOpenTelemetrySdk()) { 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 18797f0058b..bc32ebc84a4 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 @@ -87,7 +87,7 @@ void configureMetricReaders_multipleWithNone() { assertThatThrownBy( () -> MeterProviderConfiguration.configureMetricReaders( - config, spiHelper, (a, unused) -> a, closeables)) + config, spiHelper, (a, unused) -> a, (a, unused) -> a, closeables)) .isInstanceOf(ConfigurationException.class) .hasMessageContaining("otel.metrics.exporter contains none along with other exporters"); cleanup.addCloseables(closeables); @@ -102,7 +102,11 @@ void configureMetricReaders_defaultExporter() { List metricReaders = MeterProviderConfiguration.configureMetricReaders( - config, spiHelper, (metricExporter, unused) -> metricExporter, closeables); + config, + spiHelper, + (a, unused) -> a, + (metricExporter, unused) -> metricExporter, + closeables); cleanup.addCloseables(closeables); assertThat(metricReaders) @@ -125,7 +129,11 @@ void configureMetricReaders_multipleExporters() { List metricReaders = MeterProviderConfiguration.configureMetricReaders( - config, spiHelper, (metricExporter, unused) -> metricExporter, closeables); + config, + spiHelper, + (a, unused) -> a, + (metricExporter, unused) -> metricExporter, + closeables); cleanup.addCloseables(closeables); assertThat(metricReaders).hasSize(2).hasOnlyElementsOfType(PeriodicMetricReader.class); diff --git a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/FreePortFinder.java b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/FreePortFinder.java new file mode 100644 index 00000000000..92c2b7640c7 --- /dev/null +++ b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/FreePortFinder.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure; + +import java.io.IOException; +import java.net.ServerSocket; + +final class FreePortFinder { + + static int getFreePort() { + try (ServerSocket socket = new ServerSocket(0)) { + return socket.getLocalPort(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private FreePortFinder() {} +} diff --git a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfigurationTest.java b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfigurationTest.java index c5df15c73bb..b2062c20dd5 100644 --- a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfigurationTest.java +++ b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfigurationTest.java @@ -50,6 +50,7 @@ void configureMeterProvider_InvalidCardinalityLimit() { "0")), spiHelper, (a, b) -> a, + (a, b) -> a, closeables); }) .isInstanceOf(ConfigurationException.class) @@ -69,6 +70,7 @@ void configureMeterProvider_ConfiguresCardinalityLimit() { Collections.singletonMap("otel.metrics.exporter", "logging")), spiHelper, (a, b) -> a, + (a, b) -> a, closeables); cleanup.addCloseables(closeables); assertCardinalityLimit(builder, 2000); @@ -85,6 +87,7 @@ void configureMeterProvider_ConfiguresCardinalityLimit() { "100")), spiHelper, (a, b) -> a, + (a, b) -> a, closeables); cleanup.addCloseables(closeables); assertCardinalityLimit(builder, 100); 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 6db4b23f82e..c8bd0937e63 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; @@ -21,11 +22,16 @@ import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.metrics.export.MetricReader; +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; +import io.prometheus.metrics.exporter.httpserver.HTTPServer; import java.io.Closeable; +import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.BiFunction; import java.util.stream.Stream; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; @@ -49,13 +55,55 @@ void configureReader_PrometheusOnClasspath() { MetricReader reader = MetricExporterConfiguration.configureReader( - "prometheus", CONFIG_PROPERTIES, spiHelper, (a, b) -> a, closeables); + "prometheus", CONFIG_PROPERTIES, spiHelper, (a, b) -> a, (a, b) -> a, closeables); cleanup.addCloseables(closeables); assertThat(reader).isInstanceOf(PrometheusHttpServer.class); assertThat(closeables).hasSize(1); } + @Test + void configureReader_customizeReader_prometheus() { + List closeables = new ArrayList<>(); + + int port = FreePortFinder.getFreePort(); + BiFunction readerCustomizer = + (existingReader, config) -> PrometheusHttpServer.builder().setPort(port).build(); + MetricReader reader = + MetricExporterConfiguration.configureReader( + "prometheus", CONFIG_PROPERTIES, spiHelper, readerCustomizer, (a, b) -> a, closeables); + cleanup.addCloseables(closeables); + + assertThat(reader).isInstanceOf(PrometheusHttpServer.class); + assertThat(closeables).hasSize(2); + PrometheusHttpServer prometheusHttpServer = (PrometheusHttpServer) reader; + assertThat(prometheusHttpServer) + .extracting("httpServer", as(InstanceOfAssertFactories.type(HTTPServer.class))) + .satisfies(httpServer -> assertThat(httpServer.getPort()).isEqualTo(port)); + } + + @Test + void configureReader_customizeReader_otlp() { + List closeables = new ArrayList<>(); + + BiFunction readerCustomizer = + (existingReader, config) -> + PeriodicMetricReader.builder(OtlpGrpcMetricExporter.builder().build()) + .setInterval(Duration.ofSeconds(123)) + .build(); + MetricReader reader = + MetricExporterConfiguration.configureReader( + "otlp", CONFIG_PROPERTIES, spiHelper, readerCustomizer, (a, b) -> a, closeables); + cleanup.addCloseables(closeables); + + assertThat(reader).isInstanceOf(PeriodicMetricReader.class); + assertThat(closeables).hasSize(3); + PeriodicMetricReader periodicMetricReader = (PeriodicMetricReader) reader; + assertThat(periodicMetricReader) + .extracting("intervalNanos") + .isEqualTo(Duration.ofSeconds(123).toNanos()); + } + @ParameterizedTest @MethodSource("knownExporters") void configureExporter_KnownSpiExportersOnClasspath(