From 2687503324b1b3f3335820fcec4026d6b4ac8dae Mon Sep 17 00:00:00 2001 From: Arnab Nandy Date: Wed, 8 Jul 2026 18:35:38 +0530 Subject: [PATCH 1/2] fix: redact sensitive error values Fixes #2286 Signed-off-by: Arnab Nandy --- .../config/ExporterPushgatewayProperties.java | 12 +++++------- .../metrics/config/MetricsProperties.java | 7 ++----- .../io/prometheus/metrics/config/Util.java | 18 +++++++++++------- .../config/ExemplarsPropertiesTest.java | 9 ++++++--- .../ExporterHttpServerPropertiesTest.java | 3 ++- .../config/ExporterPropertiesTest.java | 6 +++--- .../ExporterPushgatewayPropertiesTest.java | 4 ++-- .../metrics/config/MetricsPropertiesTest.java | 19 ++++++++++++------- .../config/OpenMetrics2PropertiesTest.java | 10 +++++----- .../prometheus/metrics/config/UtilTest.java | 4 ++-- .../httpserver/HttpExchangeAdapter.java | 19 +++++++------------ .../exporter/httpserver/HTTPServerTest.java | 6 ++++-- 12 files changed, 61 insertions(+), 56 deletions(-) diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java index 10e8c0f33..e97ade191 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java @@ -94,9 +94,8 @@ static ExporterPushgatewayProperties load(PropertySource propertySource) if (scheme != null) { if (!scheme.equals("http") && !scheme.equals("https")) { throw new PrometheusPropertiesException( - String.format( - "%s.%s: Illegal value. Expecting 'http' or 'https'. Found: %s", - PREFIX, SCHEME, scheme)); + Util.invalidValueMessage( + PREFIX + "." + SCHEME, "Illegal value. Expecting 'http' or 'https'.")); } } @@ -119,10 +118,9 @@ static ExporterPushgatewayProperties load(PropertySource propertySource) return EscapingScheme.DOTS_ESCAPING; default: throw new PrometheusPropertiesException( - String.format( - "%s.%s: Illegal value. Expecting 'allow-utf-8', 'values', 'underscores', " - + "or 'dots'. Found: %s", - PREFIX, ESCAPING_SCHEME, scheme)); + Util.invalidValueMessage( + PREFIX + "." + ESCAPING_SCHEME, + "Illegal value. Expecting 'allow-utf-8', 'values', 'underscores', or 'dots'.")); } } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/MetricsProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/MetricsProperties.java index c2758bd87..99bb3a495 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/MetricsProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/MetricsProperties.java @@ -227,11 +227,8 @@ private void validate(String prefix) throws PrometheusPropertiesException { for (double quantile : summaryQuantiles) { if (quantile < 0 || quantile > 1) { throw new PrometheusPropertiesException( - prefix - + "." - + SUMMARY_QUANTILES - + ": Expecting 0.0 <= quantile <= 1.0. Found: " - + quantile); + Util.invalidValueMessage( + prefix + "." + SUMMARY_QUANTILES, "Expecting 0.0 <= quantile <= 1.0.")); } } } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java index 20bd75699..303900d8e 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java @@ -24,7 +24,7 @@ static Boolean loadBoolean(String prefix, String propertyName, PropertySource pr String fullKey = prefix.isEmpty() ? propertyName : prefix + "." + propertyName; if (!"true".equalsIgnoreCase(property) && !"false".equalsIgnoreCase(property)) { throw new PrometheusPropertiesException( - String.format("%s: Expecting 'true' or 'false'. Found: %s", fullKey, property)); + invalidValueMessage(fullKey, "Expecting 'true' or 'false'.")); } return Boolean.parseBoolean(property); } @@ -88,7 +88,7 @@ static List loadDoubleList( } } catch (NumberFormatException e) { throw new PrometheusPropertiesException( - fullKey + "=" + property + ": Expecting comma separated list of double values"); + invalidValueMessage(fullKey, "Expecting comma separated list of double values")); } } return Arrays.asList(result); @@ -130,7 +130,7 @@ static Integer loadInteger(String prefix, String propertyName, PropertySource pr return Integer.parseInt(property); } catch (NumberFormatException e) { throw new PrometheusPropertiesException( - fullKey + "=" + property + ": Expecting integer value"); + invalidValueMessage(fullKey, "Expecting integer value")); } } return null; @@ -146,7 +146,7 @@ static Double loadDouble(String prefix, String propertyName, PropertySource prop return Double.parseDouble(property); } catch (NumberFormatException e) { throw new PrometheusPropertiesException( - fullKey + "=" + property + ": Expecting double value"); + invalidValueMessage(fullKey, "Expecting double value")); } } return null; @@ -162,7 +162,7 @@ static Long loadLong(String prefix, String propertyName, PropertySource property return Long.parseLong(property); } catch (NumberFormatException e) { throw new PrometheusPropertiesException( - fullKey + "=" + property + ": Expecting long value"); + invalidValueMessage(fullKey, "Expecting long value")); } } return null; @@ -193,8 +193,12 @@ static void assertValue( if (number != null && !predicate.test(number)) { String fullKey = (prefix == null || prefix.isEmpty()) ? propertyName : prefix + "." + propertyName; - String fullMessage = String.format("%s: %s Found: %s", fullKey, message, number); - throw new PrometheusPropertiesException(fullMessage); + throw new PrometheusPropertiesException(invalidValueMessage(fullKey, message)); } } + + static String invalidValueMessage(String fullKey, String message) { + String separator = message.endsWith(".") ? " " : ". "; + return fullKey + ": " + message + separator + "Found: "; + } } diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExemplarsPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExemplarsPropertiesTest.java index aa87ed105..eb3f22dc4 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExemplarsPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExemplarsPropertiesTest.java @@ -25,18 +25,21 @@ void load() { .isThrownBy( () -> load(Map.of("io.prometheus.exemplars.min_retention_period_seconds", "-1"))) .withMessage( - "io.prometheus.exemplars.min_retention_period_seconds: Expecting value > 0. Found: -1"); + "io.prometheus.exemplars.min_retention_period_seconds: Expecting value > 0. Found:" + + " "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> load(Map.of("io.prometheus.exemplars.max_retention_period_seconds", "0"))) .withMessage( - "io.prometheus.exemplars.max_retention_period_seconds: Expecting value > 0. Found: 0"); + "io.prometheus.exemplars.max_retention_period_seconds: Expecting value > 0. Found:" + + " "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> load(Map.of("io.prometheus.exemplars.sample_interval_milliseconds", "-1"))) .withMessage( - "io.prometheus.exemplars.sample_interval_milliseconds: Expecting value > 0. Found: -1"); + "io.prometheus.exemplars.sample_interval_milliseconds: Expecting value > 0. Found:" + + " "); } private static ExemplarsProperties load(Map map) { diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterHttpServerPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterHttpServerPropertiesTest.java index fc4faf141..3ba155f8f 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterHttpServerPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterHttpServerPropertiesTest.java @@ -18,7 +18,8 @@ void load() { assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> load(Map.of("io.prometheus.exporter.http_server.port", "0"))) - .withMessage("io.prometheus.exporter.http_server.port: Expecting value > 0. Found: 0"); + .withMessage( + "io.prometheus.exporter.http_server.port: Expecting value > 0. Found: "); } @Test diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPropertiesTest.java index 514c5ff52..bdfc38880 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPropertiesTest.java @@ -28,7 +28,7 @@ void load() { Map.of("io.prometheus.exporter.include_created_timestamps", "invalid")))) .withMessage( "io.prometheus.exporter.include_created_timestamps: Expecting 'true' or 'false'. Found:" - + " invalid"); + + " "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> @@ -37,7 +37,7 @@ void load() { Map.of("io.prometheus.exporter.exemplars_on_all_metric_types", "invalid")))) .withMessage( "io.prometheus.exporter.exemplars_on_all_metric_types: Expecting 'true' or 'false'." - + " Found: invalid"); + + " Found: "); } private static ExporterProperties load(Map map) { @@ -85,6 +85,6 @@ void prometheusTimestampsInMs() { Map.of("io.prometheus.exporter.prometheus_timestamps_in_ms", "invalid")))) .withMessage( "io.prometheus.exporter.prometheus_timestamps_in_ms: Expecting 'true' or 'false'." - + " Found: invalid"); + + " Found: "); } } diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java index c92e6f2f9..23f0c2b19 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java @@ -27,7 +27,7 @@ void load() { .isThrownBy(() -> load(Map.of("io.prometheus.exporter.pushgateway.scheme", "foo"))) .withMessage( "io.prometheus.exporter.pushgateway.scheme: Illegal value. Expecting 'http' or 'https'." - + " Found: foo"); + + " Found: "); } @Test @@ -60,7 +60,7 @@ void loadWithInvalidEscapingScheme() { () -> load(Map.of("io.prometheus.exporter.pushgateway.escaping_scheme", "invalid"))) .withMessage( "io.prometheus.exporter.pushgateway.escaping_scheme: Illegal value. Expecting" - + " 'allow-utf-8', 'values', 'underscores', or 'dots'. Found: invalid"); + + " 'allow-utf-8', 'values', 'underscores', or 'dots'. Found: "); } @Test diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/MetricsPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/MetricsPropertiesTest.java index 9f7684a8d..8f2d87ab4 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/MetricsPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/MetricsPropertiesTest.java @@ -47,11 +47,11 @@ void builder() { assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().summaryNumberOfAgeBuckets(0).build()) - .withMessage("summary_number_of_age_buckets: Expecting value > 0. Found: 0"); + .withMessage("summary_number_of_age_buckets: Expecting value > 0. Found: "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().summaryQuantiles(2L).build()) - .withMessage(".summary_quantiles: Expecting 0.0 <= quantile <= 1.0. Found: 2.0"); + .withMessage(".summary_quantiles: Expecting 0.0 <= quantile <= 1.0. Found: "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().summaryQuantileErrors(0.9).build()) @@ -114,24 +114,29 @@ void nativeBuilder() { assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().histogramNativeInitialSchema(10).build()) .withMessage( - "histogram_native_initial_schema: Expecting number between -4 and +8. Found: 10"); + "histogram_native_initial_schema: Expecting number between -4 and +8. Found:" + + " "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().histogramNativeMinZeroThreshold(-1.0).build()) - .withMessage("histogram_native_min_zero_threshold: Expecting value >= 0. Found: -1.0"); + .withMessage( + "histogram_native_min_zero_threshold: Expecting value >= 0. Found: "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().histogramNativeMaxZeroThreshold(-1.0).build()) - .withMessage("histogram_native_max_zero_threshold: Expecting value >= 0. Found: -1.0"); + .withMessage( + "histogram_native_max_zero_threshold: Expecting value >= 0. Found: "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().histogramNativeMaxNumberOfBuckets(-1).build()) - .withMessage("histogram_native_max_number_of_buckets: Expecting value >= 0. Found: -1"); + .withMessage( + "histogram_native_max_number_of_buckets: Expecting value >= 0. Found: "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> MetricsProperties.builder().histogramNativeResetDurationSeconds(-1L).build()) - .withMessage("histogram_native_reset_duration_seconds: Expecting value >= 0. Found: -1"); + .withMessage( + "histogram_native_reset_duration_seconds: Expecting value >= 0. Found: "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/OpenMetrics2PropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/OpenMetrics2PropertiesTest.java index e7a273464..5a644f302 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/OpenMetrics2PropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/OpenMetrics2PropertiesTest.java @@ -38,7 +38,7 @@ void loadInvalidValue() { .isThrownBy( () -> load(new HashMap<>(Map.of("io.prometheus.openmetrics2.enabled", "invalid")))) .withMessage( - "io.prometheus.openmetrics2.enabled: Expecting 'true' or 'false'. Found: invalid"); + "io.prometheus.openmetrics2.enabled: Expecting 'true' or 'false'. Found: "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> @@ -47,7 +47,7 @@ void loadInvalidValue() { Map.of("io.prometheus.openmetrics2.content_negotiation", "invalid")))) .withMessage( "io.prometheus.openmetrics2.content_negotiation: Expecting 'true' or 'false'. Found:" - + " invalid"); + + " "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> @@ -56,7 +56,7 @@ void loadInvalidValue() { Map.of("io.prometheus.openmetrics2.composite_values", "invalid")))) .withMessage( "io.prometheus.openmetrics2.composite_values: Expecting 'true' or 'false'. Found:" - + " invalid"); + + " "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> @@ -65,7 +65,7 @@ void loadInvalidValue() { Map.of("io.prometheus.openmetrics2.exemplar_compliance", "invalid")))) .withMessage( "io.prometheus.openmetrics2.exemplar_compliance: Expecting 'true' or 'false'. Found:" - + " invalid"); + + " "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> @@ -74,7 +74,7 @@ void loadInvalidValue() { Map.of("io.prometheus.openmetrics2.native_histograms", "invalid")))) .withMessage( "io.prometheus.openmetrics2.native_histograms: Expecting 'true' or 'false'. Found:" - + " invalid"); + + " "); } private static OpenMetrics2Properties load(Map map) { diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/UtilTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/UtilTest.java index e4d7fa829..53ad3f7c9 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/UtilTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/UtilTest.java @@ -41,7 +41,7 @@ void loadOptionalDuration_negative_throws() { assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> Util.loadOptionalDuration("", "foo", propertySource)) - .withMessage("foo: Expecting value >= 0. Found: -1"); + .withMessage("foo: Expecting value >= 0. Found: "); } @Test @@ -51,6 +51,6 @@ void loadOptionalDuration_invalidNumber_throws() { assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> Util.loadOptionalDuration("", "foo", propertySource)) - .withMessage("foo=abc: Expecting long value"); + .withMessage("foo: Expecting long value. Found: "); } } diff --git a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java index df99837cb..6a9ac1349 100644 --- a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java +++ b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java @@ -6,8 +6,6 @@ import io.prometheus.metrics.exporter.common.PrometheusHttpResponse; import java.io.IOException; import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.StringWriter; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Collections; @@ -92,26 +90,23 @@ public HttpResponse getResponse() { @Override public void handleException(IOException e) throws IOException { - sendErrorResponseWithStackTrace(e); + sendErrorResponse(e); } @Override public void handleException(RuntimeException e) { - sendErrorResponseWithStackTrace(e); + sendErrorResponse(e); } - private void sendErrorResponseWithStackTrace(Exception requestHandlerException) { + private void sendErrorResponse(Exception requestHandlerException) { if (!responseSent) { responseSent = true; try { - StringWriter stringWriter = new StringWriter(); - PrintWriter printWriter = new PrintWriter(stringWriter); - printWriter.write("An Exception occurred while scraping metrics: "); - requestHandlerException.printStackTrace(new PrintWriter(printWriter)); - byte[] stackTrace = stringWriter.toString().getBytes(StandardCharsets.UTF_8); + byte[] message = + "An Exception occurred while scraping metrics.".getBytes(StandardCharsets.UTF_8); httpExchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8"); - httpExchange.sendResponseHeaders(500, stackTrace.length); - httpExchange.getResponseBody().write(stackTrace); + httpExchange.sendResponseHeaders(500, message.length); + httpExchange.getResponseBody().write(message); } catch (IOException errorWriterException) { // We want to avoid logging so that we don't mess with application logs when the HTTPServer // is used in a Java agent. 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 ff2d55048..53fcac3a6 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 @@ -171,7 +171,8 @@ public MetricSnapshots scrape(PrometheusScrapeRequest scrapeRequest) { } }) .buildAndStart(); - run(server, "/metrics", 500, "An Exception occurred while scraping metrics"); + String body = run(server, "/metrics", 500, "An Exception occurred while scraping metrics"); + assertThat(body).doesNotContain("test"); } @Test @@ -234,7 +235,7 @@ void healthDisabled() throws Exception { ""); } - private static void run( + private static String run( HTTPServer server, String path, int expectedStatusCode, String expectedBody) throws Exception { // we cannot use try-with-resources or even client.close(), or the test will fail with Java 17 @@ -248,6 +249,7 @@ private static void run( client.send(request, HttpResponse.BodyHandlers.ofString()); assertThat(response.statusCode()).isEqualTo(expectedStatusCode); assertThat(response.body()).contains(expectedBody); + return response.body(); } finally { server.stop(); } From 0669f12b0b9ec62ebe721f7a4c96b0e8383e3594 Mon Sep 17 00:00:00 2001 From: Arnab Nandy Date: Wed, 8 Jul 2026 18:35:38 +0530 Subject: [PATCH 2/2] fix: redact sensitive error values Fixes #2286 Signed-off-by: Arnab Nandy --- .../metrics/it/exporter/test/ExporterIT.java | 6 +++++- .../it/exporter/test/HttpServerIT.java | 5 +++++ .../config/ExporterPushgatewayProperties.java | 12 +++++------- .../metrics/config/MetricsProperties.java | 7 ++----- .../io/prometheus/metrics/config/Util.java | 18 +++++++++++------- .../config/ExemplarsPropertiesTest.java | 9 ++++++--- .../ExporterHttpServerPropertiesTest.java | 3 ++- .../config/ExporterPropertiesTest.java | 6 +++--- .../ExporterPushgatewayPropertiesTest.java | 4 ++-- .../metrics/config/MetricsPropertiesTest.java | 19 ++++++++++++------- .../config/OpenMetrics2PropertiesTest.java | 10 +++++----- .../prometheus/metrics/config/UtilTest.java | 4 ++-- .../httpserver/HttpExchangeAdapter.java | 19 +++++++------------ .../exporter/httpserver/HTTPServerTest.java | 6 ++++-- 14 files changed, 71 insertions(+), 57 deletions(-) diff --git a/integration-tests/it-exporter/it-exporter-test/src/test/java/io/prometheus/metrics/it/exporter/test/ExporterIT.java b/integration-tests/it-exporter/it-exporter-test/src/test/java/io/prometheus/metrics/it/exporter/test/ExporterIT.java index 5a80d8bdf..6b364e8a0 100644 --- a/integration-tests/it-exporter/it-exporter-test/src/test/java/io/prometheus/metrics/it/exporter/test/ExporterIT.java +++ b/integration-tests/it-exporter/it-exporter-test/src/test/java/io/prometheus/metrics/it/exporter/test/ExporterIT.java @@ -157,7 +157,11 @@ void testErrorHandling() throws IOException { start("error"); Response response = scrape("GET", ""); assertThat(response.status).isEqualTo(500); - assertThat(response.stringBody()).contains("Simulating an error."); + assertThat(response.stringBody()).contains(expectedErrorResponseBody()); + } + + String expectedErrorResponseBody() { + return "Simulating an error."; } @Test diff --git a/integration-tests/it-exporter/it-exporter-test/src/test/java/io/prometheus/metrics/it/exporter/test/HttpServerIT.java b/integration-tests/it-exporter/it-exporter-test/src/test/java/io/prometheus/metrics/it/exporter/test/HttpServerIT.java index 4c7e61472..8b9e3383e 100644 --- a/integration-tests/it-exporter/it-exporter-test/src/test/java/io/prometheus/metrics/it/exporter/test/HttpServerIT.java +++ b/integration-tests/it-exporter/it-exporter-test/src/test/java/io/prometheus/metrics/it/exporter/test/HttpServerIT.java @@ -7,4 +7,9 @@ class HttpServerIT extends ExporterIT { public HttpServerIT() throws IOException, URISyntaxException { super("exporter-httpserver-sample"); } + + @Override + String expectedErrorResponseBody() { + return "An Exception occurred while scraping metrics."; + } } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java index 10e8c0f33..e97ade191 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java @@ -94,9 +94,8 @@ static ExporterPushgatewayProperties load(PropertySource propertySource) if (scheme != null) { if (!scheme.equals("http") && !scheme.equals("https")) { throw new PrometheusPropertiesException( - String.format( - "%s.%s: Illegal value. Expecting 'http' or 'https'. Found: %s", - PREFIX, SCHEME, scheme)); + Util.invalidValueMessage( + PREFIX + "." + SCHEME, "Illegal value. Expecting 'http' or 'https'.")); } } @@ -119,10 +118,9 @@ static ExporterPushgatewayProperties load(PropertySource propertySource) return EscapingScheme.DOTS_ESCAPING; default: throw new PrometheusPropertiesException( - String.format( - "%s.%s: Illegal value. Expecting 'allow-utf-8', 'values', 'underscores', " - + "or 'dots'. Found: %s", - PREFIX, ESCAPING_SCHEME, scheme)); + Util.invalidValueMessage( + PREFIX + "." + ESCAPING_SCHEME, + "Illegal value. Expecting 'allow-utf-8', 'values', 'underscores', or 'dots'.")); } } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/MetricsProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/MetricsProperties.java index c2758bd87..99bb3a495 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/MetricsProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/MetricsProperties.java @@ -227,11 +227,8 @@ private void validate(String prefix) throws PrometheusPropertiesException { for (double quantile : summaryQuantiles) { if (quantile < 0 || quantile > 1) { throw new PrometheusPropertiesException( - prefix - + "." - + SUMMARY_QUANTILES - + ": Expecting 0.0 <= quantile <= 1.0. Found: " - + quantile); + Util.invalidValueMessage( + prefix + "." + SUMMARY_QUANTILES, "Expecting 0.0 <= quantile <= 1.0.")); } } } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java index 20bd75699..303900d8e 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java @@ -24,7 +24,7 @@ static Boolean loadBoolean(String prefix, String propertyName, PropertySource pr String fullKey = prefix.isEmpty() ? propertyName : prefix + "." + propertyName; if (!"true".equalsIgnoreCase(property) && !"false".equalsIgnoreCase(property)) { throw new PrometheusPropertiesException( - String.format("%s: Expecting 'true' or 'false'. Found: %s", fullKey, property)); + invalidValueMessage(fullKey, "Expecting 'true' or 'false'.")); } return Boolean.parseBoolean(property); } @@ -88,7 +88,7 @@ static List loadDoubleList( } } catch (NumberFormatException e) { throw new PrometheusPropertiesException( - fullKey + "=" + property + ": Expecting comma separated list of double values"); + invalidValueMessage(fullKey, "Expecting comma separated list of double values")); } } return Arrays.asList(result); @@ -130,7 +130,7 @@ static Integer loadInteger(String prefix, String propertyName, PropertySource pr return Integer.parseInt(property); } catch (NumberFormatException e) { throw new PrometheusPropertiesException( - fullKey + "=" + property + ": Expecting integer value"); + invalidValueMessage(fullKey, "Expecting integer value")); } } return null; @@ -146,7 +146,7 @@ static Double loadDouble(String prefix, String propertyName, PropertySource prop return Double.parseDouble(property); } catch (NumberFormatException e) { throw new PrometheusPropertiesException( - fullKey + "=" + property + ": Expecting double value"); + invalidValueMessage(fullKey, "Expecting double value")); } } return null; @@ -162,7 +162,7 @@ static Long loadLong(String prefix, String propertyName, PropertySource property return Long.parseLong(property); } catch (NumberFormatException e) { throw new PrometheusPropertiesException( - fullKey + "=" + property + ": Expecting long value"); + invalidValueMessage(fullKey, "Expecting long value")); } } return null; @@ -193,8 +193,12 @@ static void assertValue( if (number != null && !predicate.test(number)) { String fullKey = (prefix == null || prefix.isEmpty()) ? propertyName : prefix + "." + propertyName; - String fullMessage = String.format("%s: %s Found: %s", fullKey, message, number); - throw new PrometheusPropertiesException(fullMessage); + throw new PrometheusPropertiesException(invalidValueMessage(fullKey, message)); } } + + static String invalidValueMessage(String fullKey, String message) { + String separator = message.endsWith(".") ? " " : ". "; + return fullKey + ": " + message + separator + "Found: "; + } } diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExemplarsPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExemplarsPropertiesTest.java index aa87ed105..eb3f22dc4 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExemplarsPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExemplarsPropertiesTest.java @@ -25,18 +25,21 @@ void load() { .isThrownBy( () -> load(Map.of("io.prometheus.exemplars.min_retention_period_seconds", "-1"))) .withMessage( - "io.prometheus.exemplars.min_retention_period_seconds: Expecting value > 0. Found: -1"); + "io.prometheus.exemplars.min_retention_period_seconds: Expecting value > 0. Found:" + + " "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> load(Map.of("io.prometheus.exemplars.max_retention_period_seconds", "0"))) .withMessage( - "io.prometheus.exemplars.max_retention_period_seconds: Expecting value > 0. Found: 0"); + "io.prometheus.exemplars.max_retention_period_seconds: Expecting value > 0. Found:" + + " "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> load(Map.of("io.prometheus.exemplars.sample_interval_milliseconds", "-1"))) .withMessage( - "io.prometheus.exemplars.sample_interval_milliseconds: Expecting value > 0. Found: -1"); + "io.prometheus.exemplars.sample_interval_milliseconds: Expecting value > 0. Found:" + + " "); } private static ExemplarsProperties load(Map map) { diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterHttpServerPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterHttpServerPropertiesTest.java index fc4faf141..3ba155f8f 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterHttpServerPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterHttpServerPropertiesTest.java @@ -18,7 +18,8 @@ void load() { assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> load(Map.of("io.prometheus.exporter.http_server.port", "0"))) - .withMessage("io.prometheus.exporter.http_server.port: Expecting value > 0. Found: 0"); + .withMessage( + "io.prometheus.exporter.http_server.port: Expecting value > 0. Found: "); } @Test diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPropertiesTest.java index 514c5ff52..bdfc38880 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPropertiesTest.java @@ -28,7 +28,7 @@ void load() { Map.of("io.prometheus.exporter.include_created_timestamps", "invalid")))) .withMessage( "io.prometheus.exporter.include_created_timestamps: Expecting 'true' or 'false'. Found:" - + " invalid"); + + " "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> @@ -37,7 +37,7 @@ void load() { Map.of("io.prometheus.exporter.exemplars_on_all_metric_types", "invalid")))) .withMessage( "io.prometheus.exporter.exemplars_on_all_metric_types: Expecting 'true' or 'false'." - + " Found: invalid"); + + " Found: "); } private static ExporterProperties load(Map map) { @@ -85,6 +85,6 @@ void prometheusTimestampsInMs() { Map.of("io.prometheus.exporter.prometheus_timestamps_in_ms", "invalid")))) .withMessage( "io.prometheus.exporter.prometheus_timestamps_in_ms: Expecting 'true' or 'false'." - + " Found: invalid"); + + " Found: "); } } diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java index c92e6f2f9..23f0c2b19 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java @@ -27,7 +27,7 @@ void load() { .isThrownBy(() -> load(Map.of("io.prometheus.exporter.pushgateway.scheme", "foo"))) .withMessage( "io.prometheus.exporter.pushgateway.scheme: Illegal value. Expecting 'http' or 'https'." - + " Found: foo"); + + " Found: "); } @Test @@ -60,7 +60,7 @@ void loadWithInvalidEscapingScheme() { () -> load(Map.of("io.prometheus.exporter.pushgateway.escaping_scheme", "invalid"))) .withMessage( "io.prometheus.exporter.pushgateway.escaping_scheme: Illegal value. Expecting" - + " 'allow-utf-8', 'values', 'underscores', or 'dots'. Found: invalid"); + + " 'allow-utf-8', 'values', 'underscores', or 'dots'. Found: "); } @Test diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/MetricsPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/MetricsPropertiesTest.java index 9f7684a8d..8f2d87ab4 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/MetricsPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/MetricsPropertiesTest.java @@ -47,11 +47,11 @@ void builder() { assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().summaryNumberOfAgeBuckets(0).build()) - .withMessage("summary_number_of_age_buckets: Expecting value > 0. Found: 0"); + .withMessage("summary_number_of_age_buckets: Expecting value > 0. Found: "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().summaryQuantiles(2L).build()) - .withMessage(".summary_quantiles: Expecting 0.0 <= quantile <= 1.0. Found: 2.0"); + .withMessage(".summary_quantiles: Expecting 0.0 <= quantile <= 1.0. Found: "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().summaryQuantileErrors(0.9).build()) @@ -114,24 +114,29 @@ void nativeBuilder() { assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().histogramNativeInitialSchema(10).build()) .withMessage( - "histogram_native_initial_schema: Expecting number between -4 and +8. Found: 10"); + "histogram_native_initial_schema: Expecting number between -4 and +8. Found:" + + " "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().histogramNativeMinZeroThreshold(-1.0).build()) - .withMessage("histogram_native_min_zero_threshold: Expecting value >= 0. Found: -1.0"); + .withMessage( + "histogram_native_min_zero_threshold: Expecting value >= 0. Found: "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().histogramNativeMaxZeroThreshold(-1.0).build()) - .withMessage("histogram_native_max_zero_threshold: Expecting value >= 0. Found: -1.0"); + .withMessage( + "histogram_native_max_zero_threshold: Expecting value >= 0. Found: "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().histogramNativeMaxNumberOfBuckets(-1).build()) - .withMessage("histogram_native_max_number_of_buckets: Expecting value >= 0. Found: -1"); + .withMessage( + "histogram_native_max_number_of_buckets: Expecting value >= 0. Found: "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> MetricsProperties.builder().histogramNativeResetDurationSeconds(-1L).build()) - .withMessage("histogram_native_reset_duration_seconds: Expecting value >= 0. Found: -1"); + .withMessage( + "histogram_native_reset_duration_seconds: Expecting value >= 0. Found: "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/OpenMetrics2PropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/OpenMetrics2PropertiesTest.java index e7a273464..5a644f302 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/OpenMetrics2PropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/OpenMetrics2PropertiesTest.java @@ -38,7 +38,7 @@ void loadInvalidValue() { .isThrownBy( () -> load(new HashMap<>(Map.of("io.prometheus.openmetrics2.enabled", "invalid")))) .withMessage( - "io.prometheus.openmetrics2.enabled: Expecting 'true' or 'false'. Found: invalid"); + "io.prometheus.openmetrics2.enabled: Expecting 'true' or 'false'. Found: "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> @@ -47,7 +47,7 @@ void loadInvalidValue() { Map.of("io.prometheus.openmetrics2.content_negotiation", "invalid")))) .withMessage( "io.prometheus.openmetrics2.content_negotiation: Expecting 'true' or 'false'. Found:" - + " invalid"); + + " "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> @@ -56,7 +56,7 @@ void loadInvalidValue() { Map.of("io.prometheus.openmetrics2.composite_values", "invalid")))) .withMessage( "io.prometheus.openmetrics2.composite_values: Expecting 'true' or 'false'. Found:" - + " invalid"); + + " "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> @@ -65,7 +65,7 @@ void loadInvalidValue() { Map.of("io.prometheus.openmetrics2.exemplar_compliance", "invalid")))) .withMessage( "io.prometheus.openmetrics2.exemplar_compliance: Expecting 'true' or 'false'. Found:" - + " invalid"); + + " "); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> @@ -74,7 +74,7 @@ void loadInvalidValue() { Map.of("io.prometheus.openmetrics2.native_histograms", "invalid")))) .withMessage( "io.prometheus.openmetrics2.native_histograms: Expecting 'true' or 'false'. Found:" - + " invalid"); + + " "); } private static OpenMetrics2Properties load(Map map) { diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/UtilTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/UtilTest.java index e4d7fa829..53ad3f7c9 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/UtilTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/UtilTest.java @@ -41,7 +41,7 @@ void loadOptionalDuration_negative_throws() { assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> Util.loadOptionalDuration("", "foo", propertySource)) - .withMessage("foo: Expecting value >= 0. Found: -1"); + .withMessage("foo: Expecting value >= 0. Found: "); } @Test @@ -51,6 +51,6 @@ void loadOptionalDuration_invalidNumber_throws() { assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> Util.loadOptionalDuration("", "foo", propertySource)) - .withMessage("foo=abc: Expecting long value"); + .withMessage("foo: Expecting long value. Found: "); } } diff --git a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java index df99837cb..6a9ac1349 100644 --- a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java +++ b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java @@ -6,8 +6,6 @@ import io.prometheus.metrics.exporter.common.PrometheusHttpResponse; import java.io.IOException; import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.StringWriter; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Collections; @@ -92,26 +90,23 @@ public HttpResponse getResponse() { @Override public void handleException(IOException e) throws IOException { - sendErrorResponseWithStackTrace(e); + sendErrorResponse(e); } @Override public void handleException(RuntimeException e) { - sendErrorResponseWithStackTrace(e); + sendErrorResponse(e); } - private void sendErrorResponseWithStackTrace(Exception requestHandlerException) { + private void sendErrorResponse(Exception requestHandlerException) { if (!responseSent) { responseSent = true; try { - StringWriter stringWriter = new StringWriter(); - PrintWriter printWriter = new PrintWriter(stringWriter); - printWriter.write("An Exception occurred while scraping metrics: "); - requestHandlerException.printStackTrace(new PrintWriter(printWriter)); - byte[] stackTrace = stringWriter.toString().getBytes(StandardCharsets.UTF_8); + byte[] message = + "An Exception occurred while scraping metrics.".getBytes(StandardCharsets.UTF_8); httpExchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8"); - httpExchange.sendResponseHeaders(500, stackTrace.length); - httpExchange.getResponseBody().write(stackTrace); + httpExchange.sendResponseHeaders(500, message.length); + httpExchange.getResponseBody().write(message); } catch (IOException errorWriterException) { // We want to avoid logging so that we don't mess with application logs when the HTTPServer // is used in a Java agent. 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 ff2d55048..53fcac3a6 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 @@ -171,7 +171,8 @@ public MetricSnapshots scrape(PrometheusScrapeRequest scrapeRequest) { } }) .buildAndStart(); - run(server, "/metrics", 500, "An Exception occurred while scraping metrics"); + String body = run(server, "/metrics", 500, "An Exception occurred while scraping metrics"); + assertThat(body).doesNotContain("test"); } @Test @@ -234,7 +235,7 @@ void healthDisabled() throws Exception { ""); } - private static void run( + private static String run( HTTPServer server, String path, int expectedStatusCode, String expectedBody) throws Exception { // we cannot use try-with-resources or even client.close(), or the test will fail with Java 17 @@ -248,6 +249,7 @@ private static void run( client.send(request, HttpResponse.BodyHandlers.ofString()); assertThat(response.statusCode()).isEqualTo(expectedStatusCode); assertThat(response.body()).contains(expectedBody); + return response.body(); } finally { server.stop(); }