From 36734b02223cf5847c8bc806da77e0f939440148 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 30 Jun 2023 12:39:01 +0300 Subject: [PATCH] Introduce a way to provide custom micrometer metrics tags Closes: #33313 --- .../binder/VertxBinderProcessor.java | 7 +++ ...ditionalHttpServerMetricsTagsProvider.java | 18 ++++++ .../binder/vertx/VertxHttpServerMetrics.java | 61 +++++++++++++++++-- .../src/main/java/io/quarkus/DummyTag.java | 15 +++++ .../src/main/java/io/quarkus/HeaderTag.java | 20 ++++++ .../PrometheusMetricsRegistryTest.java | 9 ++- 6 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/AdditionalHttpServerMetricsTagsProvider.java create mode 100644 integration-tests/micrometer-prometheus/src/main/java/io/quarkus/DummyTag.java create mode 100644 integration-tests/micrometer-prometheus/src/main/java/io/quarkus/HeaderTag.java diff --git a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/VertxBinderProcessor.java b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/VertxBinderProcessor.java index 30a863c73fa00..e8bec846d0b0c 100644 --- a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/VertxBinderProcessor.java +++ b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/VertxBinderProcessor.java @@ -5,11 +5,13 @@ import jakarta.interceptor.Interceptor; import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem; +import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.BuildSteps; import io.quarkus.deployment.annotations.Consume; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; +import io.quarkus.micrometer.runtime.AdditionalHttpServerMetricsTagsProvider; import io.quarkus.micrometer.runtime.MicrometerRecorder; import io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderRecorder; import io.quarkus.micrometer.runtime.config.MicrometerConfig; @@ -34,6 +36,11 @@ public boolean getAsBoolean() { } } + @BuildStep + UnremovableBeanBuildItem unremoveableAdditionalHttpServerMetrics() { + return UnremovableBeanBuildItem.beanTypes(AdditionalHttpServerMetricsTagsProvider.class); + } + @BuildStep @Record(value = ExecutionTime.STATIC_INIT) VertxOptionsConsumerBuildItem build(VertxMeterBinderRecorder recorder) { diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/AdditionalHttpServerMetricsTagsProvider.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/AdditionalHttpServerMetricsTagsProvider.java new file mode 100644 index 0000000000000..c80e2e6ae3667 --- /dev/null +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/AdditionalHttpServerMetricsTagsProvider.java @@ -0,0 +1,18 @@ +package io.quarkus.micrometer.runtime; + +import io.micrometer.core.instrument.Tags; +import io.vertx.core.http.HttpServerRequest; + +/** + * Allows code to add additional Micrometer {@link Tags} to the metrics collected for completed HTTP server requests. + *

+ * The implementations of this interface are meant to be registered via CDI beans. + */ +public interface AdditionalHttpServerMetricsTagsProvider { + + Tags configure(Context context); + + interface Context { + HttpServerRequest request(); + } +} diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpServerMetrics.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpServerMetrics.java index 2d02bda355049..ab27d85bf5bc4 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpServerMetrics.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpServerMetrics.java @@ -1,5 +1,8 @@ package io.quarkus.micrometer.runtime.binder.vertx; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.concurrent.atomic.LongAdder; import org.jboss.logging.Logger; @@ -10,9 +13,13 @@ import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.binder.http.Outcome; +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.micrometer.runtime.AdditionalHttpServerMetricsTagsProvider; import io.quarkus.micrometer.runtime.binder.HttpBinderConfiguration; import io.quarkus.micrometer.runtime.binder.HttpCommonTags; import io.vertx.core.http.HttpMethod; +import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.ServerWebSocket; import io.vertx.core.spi.metrics.HttpServerMetrics; import io.vertx.core.spi.observability.HttpRequest; @@ -37,6 +44,8 @@ public class VertxHttpServerMetrics extends VertxTcpServerMetrics final String nameHttpServerRequests; final LongAdder activeRequests; + private final List additionalHttpServerMetricsTagsProviders; + VertxHttpServerMetrics(MeterRegistry registry, HttpBinderConfiguration config) { super(registry, "http.server", null); this.config = config; @@ -49,6 +58,27 @@ public class VertxHttpServerMetrics extends VertxTcpServerMetrics activeRequests = new LongAdder(); Gauge.builder(config.getHttpServerActiveRequestsName(), activeRequests, LongAdder::doubleValue) .register(registry); + + additionalHttpServerMetricsTagsProviders = resolveAdditionalHttpServerMetricsTagsProviders(); + } + + private List resolveAdditionalHttpServerMetricsTagsProviders() { + final List additionalHttpServerMetricsTagsProviders; + ArcContainer arcContainer = Arc.container(); + if (arcContainer == null) { + additionalHttpServerMetricsTagsProviders = Collections.emptyList(); + } else { + var handles = arcContainer.listAll(AdditionalHttpServerMetricsTagsProvider.class); + if (handles.isEmpty()) { + additionalHttpServerMetricsTagsProviders = Collections.emptyList(); + } else { + additionalHttpServerMetricsTagsProviders = new ArrayList<>(handles.size()); + for (var handle : handles) { + additionalHttpServerMetricsTagsProviders.add(handle.get()); + } + } + } + return additionalHttpServerMetricsTagsProviders; } /** @@ -148,12 +178,18 @@ public void responseEnd(HttpRequestMetric requestMetric, HttpResponse response, config.getServerIgnorePatterns()); if (path != null) { Timer.Sample sample = requestMetric.getSample(); - Timer.Builder builder = Timer.builder(nameHttpServerRequests) - .tags(Tags.of( - VertxMetricsTags.method(requestMetric.request().method()), - HttpCommonTags.uri(path, response.statusCode()), - VertxMetricsTags.outcome(response), - HttpCommonTags.status(response.statusCode()))); + Tags allTags = Tags.of( + VertxMetricsTags.method(requestMetric.request().method()), + HttpCommonTags.uri(path, response.statusCode()), + VertxMetricsTags.outcome(response), + HttpCommonTags.status(response.statusCode())); + if (!additionalHttpServerMetricsTagsProviders.isEmpty()) { + AdditionalHttpServerMetricsTagsProvider.Context context = new DefaultContext(requestMetric.request()); + for (int i = 0; i < additionalHttpServerMetricsTagsProviders.size(); i++) { + allTags = allTags.and(additionalHttpServerMetricsTagsProviders.get(i).configure(context)); + } + } + Timer.Builder builder = Timer.builder(nameHttpServerRequests).tags(allTags); sample.stop(builder.register(registry)); } @@ -195,4 +231,17 @@ public void disconnected(LongTaskTimer.Sample websocketMetric) { websocketMetric.stop(); } } + + private static class DefaultContext implements AdditionalHttpServerMetricsTagsProvider.Context { + private final HttpServerRequest request; + + private DefaultContext(HttpServerRequest request) { + this.request = request; + } + + @Override + public HttpServerRequest request() { + return request; + } + } } diff --git a/integration-tests/micrometer-prometheus/src/main/java/io/quarkus/DummyTag.java b/integration-tests/micrometer-prometheus/src/main/java/io/quarkus/DummyTag.java new file mode 100644 index 0000000000000..872f2a8e5576f --- /dev/null +++ b/integration-tests/micrometer-prometheus/src/main/java/io/quarkus/DummyTag.java @@ -0,0 +1,15 @@ +package io.quarkus; + +import jakarta.inject.Singleton; + +import io.micrometer.core.instrument.Tags; +import io.quarkus.micrometer.runtime.AdditionalHttpServerMetricsTagsProvider; + +@Singleton +public class DummyTag implements AdditionalHttpServerMetricsTagsProvider { + + @Override + public Tags configure(Context context) { + return Tags.of("dummy", "value"); + } +} diff --git a/integration-tests/micrometer-prometheus/src/main/java/io/quarkus/HeaderTag.java b/integration-tests/micrometer-prometheus/src/main/java/io/quarkus/HeaderTag.java new file mode 100644 index 0000000000000..061837559a44b --- /dev/null +++ b/integration-tests/micrometer-prometheus/src/main/java/io/quarkus/HeaderTag.java @@ -0,0 +1,20 @@ +package io.quarkus; + +import jakarta.inject.Singleton; + +import io.micrometer.core.instrument.Tags; +import io.quarkus.micrometer.runtime.AdditionalHttpServerMetricsTagsProvider; + +@Singleton +public class HeaderTag implements AdditionalHttpServerMetricsTagsProvider { + + @Override + public Tags configure(Context context) { + String headerValue = context.request().getHeader("Foo"); + String value = "UNSET"; + if ((headerValue != null) && !headerValue.isEmpty()) { + value = headerValue; + } + return Tags.of("foo", value); + } +} 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 ce9ceef711fbc..b4de936814ab2 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 @@ -1,5 +1,6 @@ package io.quarkus.it.micrometer.prometheus; +import static io.restassured.RestAssured.given; import static io.restassured.RestAssured.when; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; @@ -44,7 +45,7 @@ void testServerError() { @Test @Order(4) void testPathParameter() { - when().get("/message/item/123").then().statusCode(200); + given().header("foo", "bar").when().get("/message/item/123").then().statusCode(200); } @Test @@ -111,11 +112,13 @@ void testPrometheusScrapeEndpointTextPlain() { .body(containsString("uri=\"/message\"")) .body(containsString("uri=\"/message/item/{id}\"")) .body(containsString("outcome=\"SUCCESS\"")) + .body(containsString("dummy=\"value\"")) + .body(containsString("foo=\"bar\"")) .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}\"")) + "http_server_requests_seconds_count{dummy=\"value\",env=\"test\",foo=\"UNSET\",method=\"GET\",outcome=\"SUCCESS\",registry=\"prometheus\",status=\"200\",uri=\"/template/path/{value}\"")) // Verify Hibernate Metrics .body(containsString( @@ -207,7 +210,7 @@ void testPrometheusScrapeEndpointOpenMetrics() { .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}\"")) + "http_server_requests_seconds_count{dummy=\"value\",env=\"test\",foo=\"UNSET\",method=\"GET\",outcome=\"SUCCESS\",registry=\"prometheus\",status=\"200\",uri=\"/template/path/{value}\"")) // Verify Hibernate Metrics .body(containsString(