Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions exporters/prometheus/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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}.
*
* <p>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;
});
}

private 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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.opentelemetry.exporter.prometheus.internal.PrometheusCustomizerProvider
2 changes: 0 additions & 2 deletions sdk-extensions/autoconfigure/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ dependencies {

implementation(project(":semconv"))

compileOnly(project(":exporters:prometheus"))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last exporter compile dependency is gone! Woot!


annotationProcessor("com.google.auto.value:auto-value")

testImplementation(project(":sdk:trace-shaded-deps"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,18 @@

final class ClasspathUtil {

@SuppressWarnings("UnusedException")
static void checkClassExists(String className, String featureName, String requiredLibrary) {
static boolean checkClassExists(String className) {
Comment thread
jack-berg marked this conversation as resolved.
Outdated
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException unused) {
return false;
}
}

@SuppressWarnings("UnusedException")
static void checkClassExists(String className, String featureName, String requiredLibrary) {
if (!checkClassExists(className)) {
throw new ConfigurationException(
featureName
+ " enabled but "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -70,6 +71,7 @@ static List<MetricReader> configureMetricReaders(
exporterName ->
MetricExporterConfiguration.configureReader(
exporterName, config, serviceClassLoader, metricExporterCustomizer))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,6 +15,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
import javax.annotation.Nullable;

final class MetricExporterConfiguration {

Expand All @@ -30,22 +29,34 @@ final class MetricExporterConfiguration {
EXPORTER_ARTIFACT_ID_BY_NAME.put("otlp", "opentelemetry-exporter-otlp");
}

@Nullable
static MetricReader configureReader(
String name,
ConfigProperties config,
ClassLoader serviceClassLoader,
BiFunction<? super MetricExporter, ConfigProperties, ? extends MetricExporter>
metricExporterCustomizer) {
if (name.equals("prometheus")) {
return configurePrometheusMetricReader(config);
if (!ClasspathUtil.checkClassExists(
"io.opentelemetry.exporter.prometheus.PrometheusHttpServer")) {
// 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.
throw missingExporterException("prometheus", "opentelemetry-exporter-prometheus");
}
return null;
}

NamedSpiManager<MetricExporter> spiExportersManager =
metricExporterSpiManager(config, serviceClassLoader);

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
Expand All @@ -66,42 +77,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() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,8 @@ void prometheus() {
(a, unused) -> a))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining(
"Prometheus Metrics Server enabled but opentelemetry-exporter-prometheus not found on "
+ "classpath");
"otel.metrics.exporter set to \"prometheus\" but opentelemetry-exporter-prometheus not found on classpath."
+ " Make sure to add it as a dependency.");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand All @@ -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));
}
}