Make /metrics the only Prometheus metrics endpoint (#6476)

Signed-off-by: Fabian Stäber <fabian@fstab.de>
Co-authored-by: Jack Berg <jberg@newrelic.com>
This commit is contained in:
Fabian Stäber 2024-05-29 23:41:53 +02:00 committed by GitHub
parent 0f99d70d98
commit 7da7037717
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 69 additions and 6 deletions

View File

@ -10,6 +10,7 @@
package io.opentelemetry.exporter.prometheus;
import com.sun.net.httpserver.HttpHandler;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.export.MemoryMode;
import io.opentelemetry.sdk.internal.DaemonThreadFactory;
@ -18,7 +19,6 @@ import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import io.opentelemetry.sdk.metrics.export.CollectionRegistration;
import io.opentelemetry.sdk.metrics.export.MetricReader;
import io.prometheus.metrics.exporter.httpserver.HTTPServer;
import io.prometheus.metrics.exporter.httpserver.MetricsHandler;
import io.prometheus.metrics.model.registry.PrometheusRegistry;
import java.io.IOException;
import java.io.UncheckedIOException;
@ -64,7 +64,8 @@ public final class PrometheusHttpServer implements MetricReader {
PrometheusRegistry prometheusRegistry,
boolean otelScopeEnabled,
@Nullable Predicate<String> allowedResourceAttributesFilter,
MemoryMode memoryMode) {
MemoryMode memoryMode,
@Nullable HttpHandler defaultHandler) {
this.builder = builder;
this.prometheusMetricReader =
new PrometheusMetricReader(otelScopeEnabled, allowedResourceAttributesFilter);
@ -86,7 +87,7 @@ public final class PrometheusHttpServer implements MetricReader {
.port(port)
.executorService(executor)
.registry(prometheusRegistry)
.defaultHandler(new MetricsHandler(prometheusRegistry))
.defaultHandler(defaultHandler)
.buildAndStart();
} catch (IOException e) {
throw new UncheckedIOException("Could not create Prometheus HTTP server", e);

View File

@ -8,6 +8,7 @@ package io.opentelemetry.exporter.prometheus;
import static io.opentelemetry.api.internal.Utils.checkArgument;
import static java.util.Objects.requireNonNull;
import com.sun.net.httpserver.HttpHandler;
import io.opentelemetry.sdk.common.export.MemoryMode;
import io.prometheus.metrics.model.registry.PrometheusRegistry;
import java.util.concurrent.ExecutorService;
@ -29,6 +30,7 @@ public final class PrometheusHttpServerBuilder {
@Nullable private Predicate<String> allowedResourceAttributesFilter;
@Nullable private ExecutorService executor;
private MemoryMode memoryMode = DEFAULT_MEMORY_MODE;
@Nullable private HttpHandler defaultHandler;
PrometheusHttpServerBuilder() {}
@ -107,6 +109,23 @@ public final class PrometheusHttpServerBuilder {
return this;
}
/**
* Override the default handler for serving the "/", "/**" endpoint.
*
* <p>This can be used to serve metrics on additional paths besides the default "/metrics". For
* example: <code>
* PrometheusHttpServer.builder()
* .setPrometheusRegistry(prometheusRegistry)
* .setDefaultHandler(new MetricsHandler(prometheusRegistry))
* .build()
* </code>
*/
public PrometheusHttpServerBuilder setDefaultHandler(HttpHandler defaultHandler) {
requireNonNull(defaultHandler, "defaultHandler");
this.defaultHandler = defaultHandler;
return this;
}
/**
* Returns a new {@link PrometheusHttpServer} with the configuration of this builder which can be
* registered with a {@link io.opentelemetry.sdk.metrics.SdkMeterProvider}.
@ -120,6 +139,7 @@ public final class PrometheusHttpServerBuilder {
prometheusRegistry,
otelScopeEnabled,
allowedResourceAttributesFilter,
memoryMode);
memoryMode,
defaultHandler);
}
}

View File

@ -35,6 +35,7 @@ import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData;
import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData;
import io.opentelemetry.sdk.resources.Resource;
import io.prometheus.metrics.exporter.httpserver.HTTPServer;
import io.prometheus.metrics.exporter.httpserver.MetricsHandler;
import io.prometheus.metrics.model.registry.PrometheusRegistry;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@ -229,7 +230,7 @@ class PrometheusHttpServerTest {
void fetchFiltered() {
AggregatedHttpResponse response =
client
.get("/?name[]=grpc_name_unit_total&name[]=bears_total&name[]=target_info")
.get("/metrics?name[]=grpc_name_unit_total&name[]=bears_total&name[]=target_info")
.aggregate()
.join();
assertThat(response.status()).isEqualTo(HttpStatus.OK);
@ -245,6 +246,47 @@ class PrometheusHttpServerTest {
+ "target_info{kr=\"vr\"} 1\n");
}
@Test
void fetchOverrideDefaultHandler() {
PrometheusRegistry registry = new PrometheusRegistry();
try (PrometheusHttpServer prometheusServer =
PrometheusHttpServer.builder()
.setHost("localhost")
.setPort(0)
.setPrometheusRegistry(registry)
// Set the default handler to serve metrics on /**
.setDefaultHandler(new MetricsHandler(registry))
.build()) {
prometheusServer.register(
new CollectionRegistration() {
@Override
public Collection<MetricData> collectAllMetrics() {
return metricData.get();
}
});
WebClient client =
WebClient.builder("http://localhost:" + prometheusServer.getAddress().getPort())
.decorator(RetryingClient.newDecorator(RetryRule.failsafe()))
.build();
// Fetch metrics from / instead of /metrics
AggregatedHttpResponse response = client.get("/").aggregate().join();
assertThat(response.status()).isEqualTo(HttpStatus.OK);
assertThat(response.headers().get(HttpHeaderNames.CONTENT_TYPE))
.isEqualTo("text/plain; version=0.0.4; charset=utf-8");
assertThat(response.contentUtf8())
.isEqualTo(
"# HELP grpc_name_unit_total long_description\n"
+ "# TYPE grpc_name_unit_total counter\n"
+ "grpc_name_unit_total{kp=\"vp\",otel_scope_name=\"grpc\",otel_scope_version=\"version\"} 5.0\n"
+ "# HELP http_name_unit_total double_description\n"
+ "# TYPE http_name_unit_total counter\n"
+ "http_name_unit_total{kp=\"vp\",otel_scope_name=\"http\",otel_scope_version=\"version\"} 3.5\n"
+ "# TYPE target_info gauge\n"
+ "target_info{kr=\"vr\"} 1\n");
}
}
@SuppressWarnings("resource")
@Test
void fetchPrometheusCompressed() throws IOException {
@ -275,7 +317,7 @@ class PrometheusHttpServerTest {
@SuppressWarnings("resource")
@Test
void fetchHead() {
AggregatedHttpResponse response = client.head("/").aggregate().join();
AggregatedHttpResponse response = client.head("/metrics").aggregate().join();
assertThat(response.status()).isEqualTo(HttpStatus.OK);
assertThat(response.headers().get(HttpHeaderNames.CONTENT_TYPE))
.isEqualTo("text/plain; version=0.0.4; charset=utf-8");