From 1cec50b65c2fd147bdd0a487e22b9aaeef42732f Mon Sep 17 00:00:00 2001 From: David Sondermann Date: Thu, 11 Dec 2025 14:33:14 +0100 Subject: [PATCH] feat: Add metricsHandlerPath and registerHealthHandler configuration options to HTTPServer Signed-off-by: David Sondermann --- .../exporter/httpserver/DefaultHandler.java | 27 ++++++----- .../exporter/httpserver/HTTPServer.java | 45 ++++++++++++++++--- .../exporter/httpserver/HTTPServerTest.java | 39 ++++++++++++++++ 3 files changed, 94 insertions(+), 17 deletions(-) diff --git a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/DefaultHandler.java b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/DefaultHandler.java index 103383b47..a4a343d54 100644 --- a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/DefaultHandler.java +++ b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/DefaultHandler.java @@ -11,24 +11,27 @@ public class DefaultHandler implements HttpHandler { private final byte[] responseBytes; private final String contentType; - public DefaultHandler() { + public DefaultHandler(String metricsPath) { + String metrics = metricsPath.startsWith("/") ? metricsPath.substring(1) : metricsPath; String responseString = "\n" + "Prometheus Java Client\n" + "\n" + "

Prometheus Java Client

\n" + "

Metrics Path

\n" - + "The metrics path is /metrics.\n" + + String.format("The metrics path is %s.\n", metrics, metricsPath) + "

Name Filter

\n" + "If you want to scrape only specific metrics, " + "use the name[] parameter like this:\n" + "\n" + "You can also use multiple name[] parameters to query multiple metrics:\n" + "\n" + "The name[] parameter can be used by the Prometheus server for scraping. " + "Add the following snippet to your scrape job configuration in " @@ -50,13 +53,17 @@ public DefaultHandler() { + "The Prometheus Java metrics library supports a debug query parameter " + "for viewing the different formats in a Web browser:\n" + "\n" + "Note that the debug parameter is only for viewing different formats in a " diff --git a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HTTPServer.java b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HTTPServer.java index 218bf82d1..41fdec76d 100644 --- a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HTTPServer.java +++ b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HTTPServer.java @@ -57,24 +57,29 @@ private HTTPServer( PrometheusRegistry registry, @Nullable Authenticator authenticator, @Nullable String authenticatedSubjectAttributeName, - @Nullable HttpHandler defaultHandler) { + @Nullable HttpHandler defaultHandler, + @Nullable String metricsHandlerPath, + @Nullable Boolean registerHealthHandler) { if (httpServer.getAddress() == null) { throw new IllegalArgumentException("HttpServer hasn't been bound to an address"); } this.server = httpServer; this.executorService = executorService; + String metricsPath = getMetricsPath(metricsHandlerPath); registerHandler( "/", - defaultHandler == null ? new DefaultHandler() : defaultHandler, + defaultHandler == null ? new DefaultHandler(metricsPath) : defaultHandler, authenticator, authenticatedSubjectAttributeName); registerHandler( - "/metrics", + metricsPath, new MetricsHandler(config, registry), authenticator, authenticatedSubjectAttributeName); - registerHandler( - "/-/healthy", new HealthyHandler(), authenticator, authenticatedSubjectAttributeName); + if (registerHealthHandler == null || registerHealthHandler) { + registerHandler( + "/-/healthy", new HealthyHandler(), authenticator, authenticatedSubjectAttributeName); + } try { // HttpServer.start() starts the HttpServer in a new background thread. // If we call HttpServer.start() from a thread of the executorService, @@ -88,6 +93,16 @@ private HTTPServer( } } + private String getMetricsPath(@Nullable String metricsHandlerPath) { + if (metricsHandlerPath == null) { + return "/metrics"; + } + if (!metricsHandlerPath.startsWith("/")) { + return "/" + metricsHandlerPath; + } + return metricsHandlerPath; + } + private void registerHandler( String path, HttpHandler handler, @@ -179,9 +194,11 @@ public static class Builder { @Nullable private ExecutorService executorService = null; @Nullable private PrometheusRegistry registry = null; @Nullable private Authenticator authenticator = null; + @Nullable private String authenticatedSubjectAttributeName = null; @Nullable private HttpsConfigurator httpsConfigurator = null; @Nullable private HttpHandler defaultHandler = null; - @Nullable private String authenticatedSubjectAttributeName = null; + @Nullable private String metricsHandlerPath = null; + @Nullable private Boolean registerHealthHandler = null; private Builder(PrometheusProperties config) { this.config = config; @@ -254,6 +271,18 @@ public Builder defaultHandler(HttpHandler defaultHandler) { return this; } + /** Optional: Override default path for the metrics endpoint. Default is {@code /metrics}. */ + public Builder metricsHandlerPath(String metricsHandlerPath) { + this.metricsHandlerPath = metricsHandlerPath; + return this; + } + + /** Optional: Override if the health handler should be registered. Default is {@code true}. */ + public Builder registerHealthHandler(boolean registerHealthHandler) { + this.registerHealthHandler = registerHealthHandler; + return this; + } + /** Build and start the HTTPServer. */ public HTTPServer buildAndStart() throws IOException { if (registry == null) { @@ -275,7 +304,9 @@ public HTTPServer buildAndStart() throws IOException { registry, authenticator, authenticatedSubjectAttributeName, - defaultHandler); + defaultHandler, + metricsHandlerPath, + registerHealthHandler); } private InetSocketAddress makeInetSocketAddress() { diff --git a/prometheus-metrics-exporter-httpserver/src/test/java/io/prometheus/metrics/exporter/httpserver/HTTPServerTest.java b/prometheus-metrics-exporter-httpserver/src/test/java/io/prometheus/metrics/exporter/httpserver/HTTPServerTest.java index f939d6bd2..9b7f658de 100644 --- a/prometheus-metrics-exporter-httpserver/src/test/java/io/prometheus/metrics/exporter/httpserver/HTTPServerTest.java +++ b/prometheus-metrics-exporter-httpserver/src/test/java/io/prometheus/metrics/exporter/httpserver/HTTPServerTest.java @@ -105,6 +105,19 @@ void metrics() throws IOException { "/metrics"); } + @Test + void metricsCustomPath() throws IOException { + run( + HTTPServer.builder() + .port(0) + .registry(new PrometheusRegistry()) + .metricsHandlerPath("/my-metrics") + .executorService(Executors.newFixedThreadPool(1)) + .buildAndStart(), + "200", + "/my-metrics"); + } + @Test void registryThrows() throws IOException { HTTPServer server = @@ -147,4 +160,30 @@ void config() throws NoSuchAlgorithmException, IOException { void health() throws IOException { run(HTTPServer.builder().port(0).buildAndStart(), "200", "/-/healthy"); } + + @Test + void healthEnabled() throws IOException { + HttpHandler handler = exchange -> exchange.sendResponseHeaders(204, -1); + run( + HTTPServer.builder() + .port(0) + .defaultHandler(handler) + .registerHealthHandler(true) + .buildAndStart(), + "200", + "/-/healthy"); + } + + @Test + void healthDisabled() throws IOException { + HttpHandler handler = exchange -> exchange.sendResponseHeaders(204, -1); + run( + HTTPServer.builder() + .port(0) + .defaultHandler(handler) + .registerHealthHandler(false) + .buildAndStart(), + "204", + "/-/healthy"); + } }