From 9beeaf6f9cf9664403e615996b1093da3aaee137 Mon Sep 17 00:00:00 2001 From: jtama <39991688+jtama@users.noreply.github.com> Date: Tue, 24 Jan 2023 14:57:12 +0100 Subject: [PATCH] Use accept header to choose metics export format Fixes #30343 (cherry picked from commit 1191b165fe771b545fbe21e4bf582273cf8c0d29) --- .../runtime/export/JsonRecorder.java | 2 +- .../runtime/export/PrometheusRecorder.java | 2 + .../export/handlers/PrometheusHandler.java | 21 +++- .../PrometheusMetricsRegistryTest.java | 100 +++++++++++++++++- 4 files changed, 117 insertions(+), 8 deletions(-) diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/JsonRecorder.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/JsonRecorder.java index 9ab0a33fb7d90..af127457e7d9f 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/JsonRecorder.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/JsonRecorder.java @@ -22,7 +22,7 @@ public Consumer route() { return new Consumer() { @Override public void accept(Route route) { - route.order(2).produces("application/json"); + route.order(3).produces("application/json"); } }; } diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/PrometheusRecorder.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/PrometheusRecorder.java index 43add789032a6..f6efecf5d9b3b 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/PrometheusRecorder.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/PrometheusRecorder.java @@ -2,6 +2,7 @@ import java.util.function.Consumer; +import io.prometheus.client.exporter.common.TextFormat; import io.quarkus.micrometer.runtime.export.handlers.PrometheusHandler; import io.quarkus.runtime.annotations.Recorder; import io.vertx.core.Handler; @@ -26,6 +27,7 @@ public Consumer route() { @Override public void accept(Route route) { route.order(1).produces("text/plain"); + route.order(2).produces(TextFormat.CONTENT_TYPE_OPENMETRICS_100); } }; } diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/handlers/PrometheusHandler.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/handlers/PrometheusHandler.java index 391452fb929a3..71409f51d82d7 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/handlers/PrometheusHandler.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/export/handlers/PrometheusHandler.java @@ -34,12 +34,13 @@ public void handle(RoutingContext routingContext) { .setStatusMessage("Unable to resolve Prometheus registry instance"); } else { ManagedContext requestContext = Arc.container().requestContext(); + var acceptHeader = chooseContentType(routingContext.request().getHeader("Accept")); if (requestContext.isActive()) { - doHandle(response); + doHandle(response, acceptHeader); } else { requestContext.activate(); try { - doHandle(response); + doHandle(response, acceptHeader); } finally { requestContext.terminate(); } @@ -47,9 +48,19 @@ public void handle(RoutingContext routingContext) { } } - private void doHandle(HttpServerResponse response) { - response.putHeader("Content-Type", TextFormat.CONTENT_TYPE_OPENMETRICS_100) - .end(Buffer.buffer(registry.scrape(TextFormat.CONTENT_TYPE_OPENMETRICS_100))); + private String chooseContentType(String acceptHeader) { + if (acceptHeader == null) { + return TextFormat.CONTENT_TYPE_OPENMETRICS_100; + } + if (acceptHeader.startsWith("text/plain")) { + return TextFormat.CONTENT_TYPE_004; + } + return TextFormat.CONTENT_TYPE_OPENMETRICS_100; + } + + private void doHandle(HttpServerResponse response, String acceptHeader) { + response.putHeader("Content-Type", acceptHeader) + .end(Buffer.buffer(registry.scrape(acceptHeader))); } private void setup() { diff --git a/integration-tests/micrometer-prometheus/src/test/java/io/quarkus/it/micrometer/prometheus/PrometheusMetricsRegistryTest.java b/integration-tests/micrometer-prometheus/src/test/java/io/quarkus/it/micrometer/prometheus/PrometheusMetricsRegistryTest.java index 944c7ab8dce23..ce9ceef711fbc 100644 --- a/integration-tests/micrometer-prometheus/src/test/java/io/quarkus/it/micrometer/prometheus/PrometheusMetricsRegistryTest.java +++ b/integration-tests/micrometer-prometheus/src/test/java/io/quarkus/it/micrometer/prometheus/PrometheusMetricsRegistryTest.java @@ -9,7 +9,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; +import io.prometheus.client.exporter.common.TextFormat; import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; /** * Test functioning prometheus endpoint. @@ -86,8 +88,102 @@ void testTemplatedPathOnClass() { @Test @Order(10) - void testPrometheusScrapeEndpoint() { - when().get("/q/metrics").then().statusCode(200) + void testPrometheusScrapeEndpointTextPlain() { + RestAssured.given().header("Accept", TextFormat.CONTENT_TYPE_004) + .when().get("/q/metrics") + .then().statusCode(200) + + // Prometheus body has ALL THE THINGS in no particular order + + .body(containsString("registry=\"prometheus\"")) + .body(containsString("env=\"test\"")) + .body(containsString("http_server_requests")) + + .body(containsString("status=\"404\"")) + .body(containsString("uri=\"NOT_FOUND\"")) + .body(containsString("outcome=\"CLIENT_ERROR\"")) + + .body(containsString("status=\"500\"")) + .body(containsString("uri=\"/message/fail\"")) + .body(containsString("outcome=\"SERVER_ERROR\"")) + + .body(containsString("status=\"200\"")) + .body(containsString("uri=\"/message\"")) + .body(containsString("uri=\"/message/item/{id}\"")) + .body(containsString("outcome=\"SUCCESS\"")) + .body(containsString("uri=\"/message/match/{id}/{sub}\"")) + .body(containsString("uri=\"/message/match/{other}\"")) + + .body(containsString( + "http_server_requests_seconds_count{env=\"test\",method=\"GET\",outcome=\"SUCCESS\",registry=\"prometheus\",status=\"200\",uri=\"/template/path/{value}\"")) + + // Verify Hibernate Metrics + .body(containsString( + "hibernate_sessions_open_total{entityManagerFactory=\"\",env=\"test\",registry=\"prometheus\",} 2.0")) + .body(containsString( + "hibernate_sessions_closed_total{entityManagerFactory=\"\",env=\"test\",registry=\"prometheus\",} 2.0")) + .body(containsString( + "hibernate_connections_obtained_total{entityManagerFactory=\"\",env=\"test\",registry=\"prometheus\",}")) + .body(containsString( + "hibernate_entities_inserts_total{entityManagerFactory=\"\",env=\"test\",registry=\"prometheus\",} 3.0")) + .body(containsString( + "hibernate_flushes_total{entityManagerFactory=\"\",env=\"test\",registry=\"prometheus\",} 1.0")) + + // Annotated counters + .body(not(containsString("metric_none"))) + .body(containsString( + "metric_all_total{class=\"io.quarkus.it.micrometer.prometheus.AnnotatedResource\",env=\"test\",exception=\"none\",extra=\"tag\",method=\"countAllInvocations\",registry=\"prometheus\",result=\"success\",} 1.0")) + .body(containsString( + "metric_all_total{class=\"io.quarkus.it.micrometer.prometheus.AnnotatedResource\",env=\"test\",exception=\"NullPointerException\",extra=\"tag\",method=\"countAllInvocations\",registry=\"prometheus\",result=\"failure\",} 1.0")) + .body(containsString( + "method_counted_total{class=\"io.quarkus.it.micrometer.prometheus.AnnotatedResource\",env=\"test\",exception=\"NullPointerException\",method=\"emptyMetricName\",registry=\"prometheus\",result=\"failure\",} 1.0")) + .body(containsString( + "method_counted_total{class=\"io.quarkus.it.micrometer.prometheus.AnnotatedResource\",env=\"test\",exception=\"none\",method=\"emptyMetricName\",registry=\"prometheus\",result=\"success\",} 1.0")) + .body(not(containsString("async_none"))) + .body(containsString( + "async_all_total{class=\"io.quarkus.it.micrometer.prometheus.AnnotatedResource\",env=\"test\",exception=\"NullPointerException\",extra=\"tag\",method=\"countAllAsyncInvocations\",registry=\"prometheus\",result=\"failure\",} 1.0")) + .body(containsString( + "async_all_total{class=\"io.quarkus.it.micrometer.prometheus.AnnotatedResource\",env=\"test\",exception=\"none\",extra=\"tag\",method=\"countAllAsyncInvocations\",registry=\"prometheus\",result=\"success\",} 1.0")) + .body(containsString( + "method_counted_total{class=\"io.quarkus.it.micrometer.prometheus.AnnotatedResource\",env=\"test\",exception=\"NullPointerException\",method=\"emptyAsyncMetricName\",registry=\"prometheus\",result=\"failure\",} 1.0")) + .body(containsString( + "method_counted_total{class=\"io.quarkus.it.micrometer.prometheus.AnnotatedResource\",env=\"test\",exception=\"none\",method=\"emptyAsyncMetricName\",registry=\"prometheus\",result=\"success\",} 1.0")) + + // Annotated Timers + .body(containsString( + "call_seconds_count{class=\"io.quarkus.it.micrometer.prometheus.AnnotatedResource\",env=\"test\",exception=\"NullPointerException\",extra=\"tag\",method=\"call\",registry=\"prometheus\",} 1.0")) + .body(containsString( + "call_seconds_count{class=\"io.quarkus.it.micrometer.prometheus.AnnotatedResource\",env=\"test\",exception=\"none\",extra=\"tag\",method=\"call\",registry=\"prometheus\",}")) + .body(containsString( + "async_call_seconds_count{class=\"io.quarkus.it.micrometer.prometheus.AnnotatedResource\",env=\"test\",exception=\"NullPointerException\",extra=\"tag\",method=\"asyncCall\",registry=\"prometheus\",} 1.0")) + .body(containsString( + "async_call_seconds_count{class=\"io.quarkus.it.micrometer.prometheus.AnnotatedResource\",env=\"test\",exception=\"none\",extra=\"tag\",method=\"asyncCall\",registry=\"prometheus\",} 1.0")) + .body(containsString( + "longCall_seconds_active_count{class=\"io.quarkus.it.micrometer.prometheus.AnnotatedResource\",env=\"test\",extra=\"tag\",method=\"longCall\",registry=\"prometheus\",}")) + .body(containsString( + "async_longCall_seconds_duration_sum{class=\"io.quarkus.it.micrometer.prometheus.AnnotatedResource\",env=\"test\",extra=\"tag\",method=\"longAsyncCall\",registry=\"prometheus\",} 0.0")) + + // Configured median, 95th percentile and histogram buckets + .body(containsString( + "prime_number_test_seconds{env=\"test\",registry=\"prometheus\",quantile=\"0.5\",}")) + .body(containsString( + "prime_number_test_seconds{env=\"test\",registry=\"prometheus\",quantile=\"0.95\",}")) + .body(containsString( + "prime_number_test_seconds_bucket{env=\"test\",registry=\"prometheus\",le=\"0.001\",}")) + + // this was defined by a tag to a non-matching registry, and should not be found + .body(not(containsString("class-should-not-match"))) + + // should not find this ignored uri + .body(not(containsString("uri=\"/fruit/create\""))); + } + + @Test + @Order(11) + void testPrometheusScrapeEndpointOpenMetrics() { + RestAssured.given().header("Accept", TextFormat.CONTENT_TYPE_OPENMETRICS_100) + .when().get("/q/metrics") + .then().statusCode(200) // Prometheus body has ALL THE THINGS in no particular order