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 30a863c73fa006..e8bec846d0b0c6 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 00000000000000..c80e2e6ae36671
--- /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 2d02bda355049a..ab27d85bf5bc49 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 00000000000000..872f2a8e5576f5
--- /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 00000000000000..061837559a44bb
--- /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 ce9ceef711fbce..b4de936814ab29 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(