Skip to content

Commit 2d8cf20

Browse files
committed
feat: Add metricsHandlerPath and registerHealthHandler configuration options to HTTPServer
Signed-off-by: David Sondermann <[email protected]>
1 parent 04ab3d5 commit 2d8cf20

File tree

3 files changed

+96
-29
lines changed

3 files changed

+96
-29
lines changed

prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/DefaultHandler.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,27 @@ public class DefaultHandler implements HttpHandler {
1111
private final byte[] responseBytes;
1212
private final String contentType;
1313

14-
public DefaultHandler() {
14+
public DefaultHandler(String metricsPath) {
15+
String metrics = metricsPath.startsWith("/") ? metricsPath.substring(1) : metricsPath;
1516
String responseString =
1617
"<html>\n"
1718
+ "<head><title>Prometheus Java Client</title></head>\n"
1819
+ "<body>\n"
1920
+ "<h1>Prometheus Java Client</h1>\n"
2021
+ "<h2>Metrics Path</h2>\n"
21-
+ "The metrics path is <a href=\"metrics\">/metrics</a>.\n"
22+
+ String.format("The metrics path is <a href=\"%s\">%s</a>.\n", metrics, metricsPath)
2223
+ "<h2>Name Filter</h2>\n"
2324
+ "If you want to scrape only specific metrics, "
2425
+ "use the <tt>name[]</tt> parameter like this:\n"
2526
+ "<ul>\n"
26-
+ "<li><a href=\"metrics?name[]=my_metric\">/metrics?name[]=my_metric</a></li>\n"
27+
+ String.format(
28+
"<li><a href=\"%s?name[]=my_metric\">%s?name[]=my_metric</a></li>\n",
29+
metrics, metricsPath)
2730
+ "</ul>\n"
2831
+ "You can also use multiple <tt>name[]</tt> parameters to query multiple metrics:\n"
2932
+ "<ul>\n"
30-
+ "<li><a href=\"metrics?name[]=my_metric_a&name=my_metrics_b\">"
31-
+ "/metrics?name[]=my_metric_a&amp;name=[]=my_metric_b</a></li>\n"
33+
+ String.format("<li><a href=\"%s?name[]=my_metric_a&name[]=my_metric_b\">", metrics)
34+
+ String.format("%s?name[]=my_metric_a&amp;name[]=my_metric_b</a></li>\n", metricsPath)
3235
+ "</ul>\n"
3336
+ "The <tt>name[]</tt> parameter can be used by the Prometheus server for scraping. "
3437
+ "Add the following snippet to your scrape job configuration in "
@@ -50,13 +53,17 @@ public DefaultHandler() {
5053
+ "The Prometheus Java metrics library supports a <tt>debug</tt> query parameter "
5154
+ "for viewing the different formats in a Web browser:\n"
5255
+ "<ul>\n"
53-
+ "<li><a href=\"metrics?debug=openmetrics\">/metrics?debug=openmetrics</a>: "
56+
+ String.format(
57+
"<li><a href=\"%s?debug=openmetrics\">%s?debug=openmetrics</a>: ",
58+
metrics, metricsPath)
5459
+ "View OpenMetrics text format.</li>\n"
55-
+ "<li><a href=\"metrics?debug=text\">/metrics?debug=text</a>: "
60+
+ String.format(
61+
"<li><a href=\"%s?debug=text\">%s?debug=text</a>: ", metrics, metricsPath)
5662
+ "View Prometheus text format (this is the default when accessing the "
57-
+ "<a href=\"metrics\">/metrics</a> endpoint with a Web browser).</li>\n"
58-
+ "<li><a href=\"metrics?debug=prometheus-protobuf\">"
59-
+ "/metrics?debug=prometheus-protobuf</a>: "
63+
+ String.format(
64+
"<a href=\"%s\">%s</a> endpoint with a Web browser).</li>\n", metrics, metricsPath)
65+
+ String.format("<li><a href=\"%s?debug=prometheus-protobuf\">", metrics)
66+
+ String.format("%s?debug=prometheus-protobuf</a>: ", metricsPath)
6067
+ "View a text representation of the Prometheus protobuf format.</li>\n"
6168
+ "</ul>\n"
6269
+ "Note that the <tt>debug</tt> parameter is only for viewing different formats in a "

prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HTTPServer.java

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
package io.prometheus.metrics.exporter.httpserver;
22

3-
import com.sun.net.httpserver.Authenticator;
4-
import com.sun.net.httpserver.HttpContext;
5-
import com.sun.net.httpserver.HttpExchange;
6-
import com.sun.net.httpserver.HttpHandler;
7-
import com.sun.net.httpserver.HttpServer;
8-
import com.sun.net.httpserver.HttpsConfigurator;
9-
import com.sun.net.httpserver.HttpsServer;
3+
import com.sun.net.httpserver.*;
104
import io.prometheus.metrics.config.PrometheusProperties;
115
import io.prometheus.metrics.model.registry.PrometheusRegistry;
126
import java.io.Closeable;
@@ -16,11 +10,7 @@
1610
import java.net.InetSocketAddress;
1711
import java.security.PrivilegedActionException;
1812
import java.security.PrivilegedExceptionAction;
19-
import java.util.concurrent.ExecutionException;
20-
import java.util.concurrent.ExecutorService;
21-
import java.util.concurrent.SynchronousQueue;
22-
import java.util.concurrent.ThreadPoolExecutor;
23-
import java.util.concurrent.TimeUnit;
13+
import java.util.concurrent.*;
2414
import javax.annotation.Nullable;
2515
import javax.security.auth.Subject;
2616

@@ -57,24 +47,29 @@ private HTTPServer(
5747
PrometheusRegistry registry,
5848
@Nullable Authenticator authenticator,
5949
@Nullable String authenticatedSubjectAttributeName,
60-
@Nullable HttpHandler defaultHandler) {
50+
@Nullable HttpHandler defaultHandler,
51+
@Nullable String metricsHandlerPath,
52+
@Nullable Boolean registerHealthHandler) {
6153
if (httpServer.getAddress() == null) {
6254
throw new IllegalArgumentException("HttpServer hasn't been bound to an address");
6355
}
6456
this.server = httpServer;
6557
this.executorService = executorService;
58+
String metricsPath = getMetricsPath(metricsHandlerPath);
6659
registerHandler(
6760
"/",
68-
defaultHandler == null ? new DefaultHandler() : defaultHandler,
61+
defaultHandler == null ? new DefaultHandler(metricsPath) : defaultHandler,
6962
authenticator,
7063
authenticatedSubjectAttributeName);
7164
registerHandler(
72-
"/metrics",
65+
metricsPath,
7366
new MetricsHandler(config, registry),
7467
authenticator,
7568
authenticatedSubjectAttributeName);
76-
registerHandler(
77-
"/-/healthy", new HealthyHandler(), authenticator, authenticatedSubjectAttributeName);
69+
if (registerHealthHandler == null || registerHealthHandler) {
70+
registerHandler(
71+
"/-/healthy", new HealthyHandler(), authenticator, authenticatedSubjectAttributeName);
72+
}
7873
try {
7974
// HttpServer.start() starts the HttpServer in a new background thread.
8075
// If we call HttpServer.start() from a thread of the executorService,
@@ -88,6 +83,16 @@ private HTTPServer(
8883
}
8984
}
9085

86+
private String getMetricsPath(@Nullable String metricsHandlerPath) {
87+
if (metricsHandlerPath == null) {
88+
return "/metrics";
89+
}
90+
if (!metricsHandlerPath.startsWith("/")) {
91+
return "/" + metricsHandlerPath;
92+
}
93+
return metricsHandlerPath;
94+
}
95+
9196
private void registerHandler(
9297
String path,
9398
HttpHandler handler,
@@ -179,9 +184,11 @@ public static class Builder {
179184
@Nullable private ExecutorService executorService = null;
180185
@Nullable private PrometheusRegistry registry = null;
181186
@Nullable private Authenticator authenticator = null;
187+
@Nullable private String authenticatedSubjectAttributeName = null;
182188
@Nullable private HttpsConfigurator httpsConfigurator = null;
183189
@Nullable private HttpHandler defaultHandler = null;
184-
@Nullable private String authenticatedSubjectAttributeName = null;
190+
@Nullable private String metricsHandlerPath = null;
191+
@Nullable private Boolean registerHealthHandler = null;
185192

186193
private Builder(PrometheusProperties config) {
187194
this.config = config;
@@ -254,6 +261,18 @@ public Builder defaultHandler(HttpHandler defaultHandler) {
254261
return this;
255262
}
256263

264+
/** Optional: Override default path for the metrics endpoint. Default is {@code /metrics}. */
265+
public Builder metricsHandlerPath(String metricsHandlerPath) {
266+
this.metricsHandlerPath = metricsHandlerPath;
267+
return this;
268+
}
269+
270+
/** Optional: Override if the health handler should be registered. Default is {@code true}. */
271+
public Builder registerHealthHandler(boolean registerHealthHandler) {
272+
this.registerHealthHandler = registerHealthHandler;
273+
return this;
274+
}
275+
257276
/** Build and start the HTTPServer. */
258277
public HTTPServer buildAndStart() throws IOException {
259278
if (registry == null) {
@@ -275,7 +294,9 @@ public HTTPServer buildAndStart() throws IOException {
275294
registry,
276295
authenticator,
277296
authenticatedSubjectAttributeName,
278-
defaultHandler);
297+
defaultHandler,
298+
metricsHandlerPath,
299+
registerHealthHandler);
279300
}
280301

281302
private InetSocketAddress makeInetSocketAddress() {

prometheus-metrics-exporter-httpserver/src/test/java/io/prometheus/metrics/exporter/httpserver/HTTPServerTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,19 @@ void metrics() throws IOException {
105105
"/metrics");
106106
}
107107

108+
@Test
109+
void metricsCustomPath() throws IOException {
110+
run(
111+
HTTPServer.builder()
112+
.port(0)
113+
.registry(new PrometheusRegistry())
114+
.metricsHandlerPath("/my-metrics")
115+
.executorService(Executors.newFixedThreadPool(1))
116+
.buildAndStart(),
117+
"200",
118+
"/my-metrics");
119+
}
120+
108121
@Test
109122
void registryThrows() throws IOException {
110123
HTTPServer server =
@@ -147,4 +160,30 @@ void config() throws NoSuchAlgorithmException, IOException {
147160
void health() throws IOException {
148161
run(HTTPServer.builder().port(0).buildAndStart(), "200", "/-/healthy");
149162
}
163+
164+
@Test
165+
void healthEnabled() throws IOException {
166+
HttpHandler handler = exchange -> exchange.sendResponseHeaders(204, -1);
167+
run(
168+
HTTPServer.builder()
169+
.port(0)
170+
.defaultHandler(handler)
171+
.registerHealthHandler(true)
172+
.buildAndStart(),
173+
"200",
174+
"/-/healthy");
175+
}
176+
177+
@Test
178+
void healthDisabled() throws IOException {
179+
HttpHandler handler = exchange -> exchange.sendResponseHeaders(204, -1);
180+
run(
181+
HTTPServer.builder()
182+
.port(0)
183+
.defaultHandler(handler)
184+
.registerHealthHandler(false)
185+
.buildAndStart(),
186+
"204",
187+
"/-/healthy");
188+
}
150189
}

0 commit comments

Comments
 (0)