From 0065a02aebc260128d9b2460d80c2ae6d93d8032 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Mon, 2 Dec 2024 18:12:32 +0100 Subject: [PATCH 1/2] Verify that we do not exceed the number of tags that can be recorded for HTTP requests This commits adds tests to verify that we do not exceed the number of tags that can be recorded for HTTP requests. It tests: - Jax-RS OK, 400 and 500 - Reactive routes OK, 400 and 500 - Programmatic routes OK, 400 and 500 --- .../HttpPathParamLimitWithJaxRs400Test.java | 111 ++++++++++++++++++ .../HttpPathParamLimitWithJaxRs500Test.java | 110 +++++++++++++++++ .../HttpPathParamLimitWithJaxRsTest.java | 86 ++++++++++++++ ...ramLimitWithProgrammaticRoutes400Test.java | 86 ++++++++++++++ ...ramLimitWithProgrammaticRoutes500Test.java | 86 ++++++++++++++ ...hParamLimitWithProgrammaticRoutesTest.java | 80 +++++++++++++ ...thParamLimitWithReactiveRoutes400Test.java | 68 +++++++++++ ...thParamLimitWithReactiveRoutes500Test.java | 68 +++++++++++ ...pPathParamLimitWithReactiveRoutesTest.java | 80 +++++++++++++ .../runtime/binder/HttpCommonTags.java | 2 +- 10 files changed, 776 insertions(+), 1 deletion(-) create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithJaxRs400Test.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithJaxRs500Test.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithJaxRsTest.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithProgrammaticRoutes400Test.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithProgrammaticRoutes500Test.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithProgrammaticRoutesTest.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithReactiveRoutes400Test.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithReactiveRoutes500Test.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithReactiveRoutesTest.java diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithJaxRs400Test.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithJaxRs400Test.java new file mode 100644 index 0000000000000..0dad65ee2cb95 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithJaxRs400Test.java @@ -0,0 +1,111 @@ +package io.quarkus.micrometer.deployment.pathparams; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.core.Response; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.micrometer.core.instrument.MeterRegistry; +import io.quarkus.micrometer.test.Util; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class HttpPathParamLimitWithJaxRs400Test { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") + .overrideConfigKey("quarkus.micrometer.binder.http-client.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.http-server.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") + .withApplicationRoot((jar) -> jar + .addClasses(Util.class, + Resource.class)); + + @Inject + MeterRegistry registry; + + public static final int COUNT = 101; + + @Test + void testWithResteasy400() throws InterruptedException { + registry.clear(); + // Test a JAX-RS endpoint with GET /jaxrs and GET /jaxrs/{message} + // Verify OK response + for (int i = 0; i < COUNT; i++) { + RestAssured.get("/jaxrs").then().statusCode(200); + RestAssured.get("/jaxrs/foo-" + i).then().statusCode(200); + } + + // Verify metrics + Util.waitForMeters(registry.find("http.server.requests").timers(), COUNT); + + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/jaxrs").timers().iterator().next().count()); + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/jaxrs/{message}").timers().iterator().next().count()); + + // Verify method producing a 400 + for (int i = 0; i < COUNT; i++) { + RestAssured.get("/bad").then().statusCode(400); + RestAssured.get("/bad/foo-" + i).then().statusCode(400); + } + + Util.waitForMeters(registry.find("http.server.requests").timers(), COUNT * 2); + + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/bad").tag("method", "GET").timers().iterator().next().count()); + Assertions.assertEquals(4, registry.find("http.server.requests") + .tag("method", "GET").timers().size()); // Pattern recognized + + } + + @Path("/") + @Singleton + public static class Resource { + + @GET + @Path("/jaxrs") + public String jaxrs() { + return "hello"; + } + + @GET + @Path("/jaxrs/{message}") + public String jaxrsWithPathParam(@PathParam("message") String message) { + return "hello " + message; + } + + @GET + @Path("/bad") + public Response bad() { + return Response.status(400).build(); + } + + @GET + @Path("/bad/{message}") + public Response bad(@PathParam("message") String message) { + return Response.status(400).build(); + } + + @GET + @Path("/fail") + public Response fail() { + return Response.status(500).build(); + } + + @GET + @Path("/fail/{message}") + public Response fail(@PathParam("message") String message) { + return Response.status(500).build(); + } + + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithJaxRs500Test.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithJaxRs500Test.java new file mode 100644 index 0000000000000..241052bdee1d6 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithJaxRs500Test.java @@ -0,0 +1,110 @@ +package io.quarkus.micrometer.deployment.pathparams; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.core.Response; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.micrometer.core.instrument.MeterRegistry; +import io.quarkus.micrometer.test.Util; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class HttpPathParamLimitWithJaxRs500Test { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") + .overrideConfigKey("quarkus.micrometer.binder.http-client.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.http-server.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") + .withApplicationRoot((jar) -> jar + .addClasses(Util.class, + Resource.class)); + + @Inject + MeterRegistry registry; + + public static final int COUNT = 101; + + @Test + void testWithResteasy500() throws InterruptedException { + registry.clear(); + // Test a JAX-RS endpoint with GET /jaxrs and GET /jaxrs/{message} + // Verify OK response + for (int i = 0; i < COUNT; i++) { + RestAssured.get("/jaxrs").then().statusCode(200); + RestAssured.get("/jaxrs/foo-" + i).then().statusCode(200); + } + + // Verify metrics + Util.waitForMeters(registry.find("http.server.requests").timers(), COUNT); + + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/jaxrs").timers().iterator().next().count()); + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/jaxrs/{message}").timers().iterator().next().count()); + + // Verify method producing a 400 + for (int i = 0; i < COUNT; i++) { + RestAssured.get("/fail").then().statusCode(500); + RestAssured.get("/fail/foo-" + i).then().statusCode(500); + } + + Util.waitForMeters(registry.find("http.server.requests").timers(), COUNT * 2); + + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/fail").tag("method", "GET").timers().iterator().next().count()); + Assertions.assertEquals(4, registry.find("http.server.requests") + .tag("method", "GET").timers().size()); // Pattern recognized + } + + @Path("/") + @Singleton + public static class Resource { + + @GET + @Path("/jaxrs") + public String jaxrs() { + return "hello"; + } + + @GET + @Path("/jaxrs/{message}") + public String jaxrsWithPathParam(@PathParam("message") String message) { + return "hello " + message; + } + + @GET + @Path("/bad") + public Response bad() { + return Response.status(400).build(); + } + + @GET + @Path("/bad/{message}") + public Response bad(@PathParam("message") String message) { + return Response.status(400).build(); + } + + @GET + @Path("/fail") + public Response fail() { + return Response.status(500).build(); + } + + @GET + @Path("/fail/{message}") + public Response fail(@PathParam("message") String message) { + return Response.status(500).build(); + } + + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithJaxRsTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithJaxRsTest.java new file mode 100644 index 0000000000000..7912f902a1fdf --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithJaxRsTest.java @@ -0,0 +1,86 @@ +package io.quarkus.micrometer.deployment.pathparams; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.micrometer.core.instrument.MeterRegistry; +import io.quarkus.micrometer.test.Util; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class HttpPathParamLimitWithJaxRsTest { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") + .overrideConfigKey("quarkus.micrometer.binder.http-client.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.http-server.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") + .withApplicationRoot((jar) -> jar + .addClasses(Util.class, + Resource.class)); + + @Inject + MeterRegistry registry; + + public static final int COUNT = 101; + public static final int ARITY_LIMIT = 100; + + @Test + void testWithResteasyOK() throws InterruptedException { + registry.clear(); + // Test a JAX-RS endpoint with GET /jaxrs and GET /jaxrs/{message} + // Verify OK response + for (int i = 0; i < COUNT; i++) { + RestAssured.get("/jaxrs").then().statusCode(200); + RestAssured.get("/jaxrs/foo-" + i).then().statusCode(200); + } + + // Verify metrics + Util.waitForMeters(registry.find("http.server.requests").timers(), COUNT); + + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/jaxrs").timers().iterator().next().count()); + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/jaxrs/{message}").timers().iterator().next().count()); + + // Verify 405 responses + for (int i = 0; i < COUNT; i++) { + RestAssured.delete("/jaxrs").then().statusCode(405); + RestAssured.patch("/jaxrs/foo-" + i).then().statusCode(405); + } + + Util.waitForMeters(registry.find("http.server.requests").timers(), COUNT * 2); + + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/jaxrs").tag("method", "DELETE").timers().iterator().next().count()); + Assertions.assertEquals(ARITY_LIMIT - 2, registry.find("http.server.requests") + .tag("method", "PATCH").timers().size()); // -2 because of the two other uri: /jaxrs and /jaxrs/{message}. + } + + @Path("/") + @Singleton + public static class Resource { + + @GET + @Path("/jaxrs") + public String jaxrs() { + return "hello"; + } + + @GET + @Path("/jaxrs/{message}") + public String jaxrsWithPathParam(@PathParam("message") String message) { + return "hello " + message; + } + + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithProgrammaticRoutes400Test.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithProgrammaticRoutes400Test.java new file mode 100644 index 0000000000000..7ca77a431071b --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithProgrammaticRoutes400Test.java @@ -0,0 +1,86 @@ +package io.quarkus.micrometer.deployment.pathparams; + +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.micrometer.core.instrument.MeterRegistry; +import io.quarkus.micrometer.test.Util; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.vertx.ext.web.Router; + +public class HttpPathParamLimitWithProgrammaticRoutes400Test { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") + .overrideConfigKey("quarkus.micrometer.binder.http-client.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.http-server.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") + .withApplicationRoot((jar) -> jar + .addClasses(Util.class, + Resource.class)); + + @Inject + MeterRegistry registry; + + public static final int COUNT = 101; + + @Test + void testWithProgrammaticRoutes400() throws InterruptedException { + registry.clear(); + // Verify OK response + for (int i = 0; i < COUNT; i++) { + RestAssured.get("/programmatic").then().statusCode(200); + RestAssured.get("/programmatic/foo-" + i).then().statusCode(200); + } + + // Verify metrics + Util.waitForMeters(registry.find("http.server.requests").timers(), COUNT); + + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/programmatic").timers().iterator().next().count()); + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/programmatic/{message}").timers().iterator().next().count()); + + // Verify 405 responses + for (int i = 0; i < COUNT; i++) { + RestAssured.get("/bad").then().statusCode(400); + RestAssured.get("/bad/foo-" + i).then().statusCode(400); + } + + Util.waitForMeters(registry.find("http.server.requests").timers(), COUNT * 2); + + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/bad").tag("method", "GET").timers().iterator().next().count()); + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("method", "GET").timers().iterator().next().count()); + } + + @Singleton + public static class Resource { + + void registerProgrammaticRoutes(@Observes Router router) { + router.get("/programmatic").handler(rc -> { + rc.response().end("hello"); + }); + router.get("/programmatic/:message").handler(rc -> { + rc.response().end("hello " + rc.pathParam("message")); + }); + + router.get("/bad").handler(rc -> { + rc.response().setStatusCode(400).end("hello"); + }); + router.get("/bad/:message").handler(rc -> { + rc.response().setStatusCode(400).end("hello " + rc.pathParam("message")); + }); + } + + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithProgrammaticRoutes500Test.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithProgrammaticRoutes500Test.java new file mode 100644 index 0000000000000..1cefa6c6a6b6f --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithProgrammaticRoutes500Test.java @@ -0,0 +1,86 @@ +package io.quarkus.micrometer.deployment.pathparams; + +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.micrometer.core.instrument.MeterRegistry; +import io.quarkus.micrometer.test.Util; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.vertx.ext.web.Router; + +public class HttpPathParamLimitWithProgrammaticRoutes500Test { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") + .overrideConfigKey("quarkus.micrometer.binder.http-client.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.http-server.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") + .withApplicationRoot((jar) -> jar + .addClasses(Util.class, + Resource.class)); + + @Inject + MeterRegistry registry; + + public static final int COUNT = 101; + + @Test + void testWithProgrammaticRoutes500() throws InterruptedException { + registry.clear(); + // Verify OK response + for (int i = 0; i < COUNT; i++) { + RestAssured.get("/programmatic").then().statusCode(200); + RestAssured.get("/programmatic/foo-" + i).then().statusCode(200); + } + + // Verify metrics + Util.waitForMeters(registry.find("http.server.requests").timers(), COUNT); + + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/programmatic").timers().iterator().next().count()); + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/programmatic/{message}").timers().iterator().next().count()); + + // Verify 405 responses + for (int i = 0; i < COUNT; i++) { + RestAssured.get("/bad").then().statusCode(500); + RestAssured.get("/bad/foo-" + i).then().statusCode(500); + } + + Util.waitForMeters(registry.find("http.server.requests").timers(), COUNT * 2); + + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/bad").tag("method", "GET").timers().iterator().next().count()); + Assertions.assertEquals(4, registry.find("http.server.requests") + .tag("method", "GET").timers().size()); + } + + @Singleton + public static class Resource { + + void registerProgrammaticRoutes(@Observes Router router) { + router.get("/programmatic").handler(rc -> { + rc.response().end("hello"); + }); + router.get("/programmatic/:message").handler(rc -> { + rc.response().end("hello " + rc.pathParam("message")); + }); + + router.get("/bad").handler(rc -> { + rc.response().setStatusCode(500).end("hello"); + }); + router.get("/bad/:message").handler(rc -> { + rc.response().setStatusCode(500).end("hello " + rc.pathParam("message")); + }); + } + + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithProgrammaticRoutesTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithProgrammaticRoutesTest.java new file mode 100644 index 0000000000000..eaf800c6cbf5c --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithProgrammaticRoutesTest.java @@ -0,0 +1,80 @@ +package io.quarkus.micrometer.deployment.pathparams; + +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.micrometer.core.instrument.MeterRegistry; +import io.quarkus.micrometer.test.Util; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.vertx.ext.web.Router; + +public class HttpPathParamLimitWithProgrammaticRoutesTest { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") + .overrideConfigKey("quarkus.micrometer.binder.http-client.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.http-server.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") + .withApplicationRoot((jar) -> jar + .addClasses(Util.class, + Resource.class)); + + @Inject + MeterRegistry registry; + + public static final int COUNT = 101; + public static final int ARITY_LIMIT = 100; + + @Test + void testWithProgrammaticRoutes() throws InterruptedException { + registry.clear(); + // Verify OK response + for (int i = 0; i < COUNT; i++) { + RestAssured.get("/programmatic").then().statusCode(200); + RestAssured.get("/programmatic/foo-" + i).then().statusCode(200); + } + + // Verify metrics + Util.waitForMeters(registry.find("http.server.requests").timers(), COUNT); + + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/programmatic").timers().iterator().next().count()); + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/programmatic/{message}").timers().iterator().next().count()); + + // Verify 405 responses + for (int i = 0; i < COUNT; i++) { + RestAssured.delete("/programmatic").then().statusCode(405); + RestAssured.patch("/programmatic/foo-" + i).then().statusCode(501); // Not totally sure why Vertx returns a 501, it's not necessarily wrong, just different. + } + + Util.waitForMeters(registry.find("http.server.requests").timers(), COUNT * 2); + + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/programmatic").tag("method", "DELETE").timers().iterator().next().count()); + Assertions.assertEquals(ARITY_LIMIT - 2, registry.find("http.server.requests") + .tag("method", "PATCH").timers().size()); // -2 because of the two other uri: /rr and /rr/{message}. + } + + @Singleton + public static class Resource { + + void registerProgrammaticRoutes(@Observes Router router) { + router.get("/programmatic").handler(rc -> { + rc.response().end("hello"); + }); + router.get("/programmatic/:message").handler(rc -> { + rc.response().end("hello " + rc.pathParam("message")); + }); + } + + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithReactiveRoutes400Test.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithReactiveRoutes400Test.java new file mode 100644 index 0000000000000..c74f806f71c55 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithReactiveRoutes400Test.java @@ -0,0 +1,68 @@ +package io.quarkus.micrometer.deployment.pathparams; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.micrometer.core.instrument.MeterRegistry; +import io.quarkus.micrometer.test.Util; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.vertx.web.Param; +import io.quarkus.vertx.web.Route; +import io.restassured.RestAssured; +import io.vertx.ext.web.RoutingContext; + +public class HttpPathParamLimitWithReactiveRoutes400Test { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") + .overrideConfigKey("quarkus.micrometer.binder.http-client.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.http-server.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") + .withApplicationRoot((jar) -> jar + .addClasses(Util.class, + Resource.class)); + + @Inject + MeterRegistry registry; + + public static final int COUNT = 101; + + @Test + void testWithReactiveRoute400() throws InterruptedException { + registry.clear(); + + for (int i = 0; i < COUNT; i++) { + RestAssured.get("/rr").then().statusCode(400); + RestAssured.get("/rr/foo-" + i).then().statusCode(400); + } + + Util.waitForMeters(registry.find("http.server.requests").timers(), COUNT); + + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/rr").tag("method", "GET") + .timers().iterator().next().count()); + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("method", "GET").tag("uri", "/rr/{message}") + .timers().iterator().next().count()); + } + + @Singleton + public static class Resource { + + @Route(path = "/rr", methods = Route.HttpMethod.GET) + public void rr(RoutingContext rc) { + rc.response().setStatusCode(400).end("hello"); + } + + @Route(path = "/rr/:message", methods = Route.HttpMethod.GET) + public void rrWithPathParam(@Param String message, RoutingContext rc) { + rc.response().setStatusCode(400).end("hello " + message); + } + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithReactiveRoutes500Test.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithReactiveRoutes500Test.java new file mode 100644 index 0000000000000..dc34711811bfe --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithReactiveRoutes500Test.java @@ -0,0 +1,68 @@ +package io.quarkus.micrometer.deployment.pathparams; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.micrometer.core.instrument.MeterRegistry; +import io.quarkus.micrometer.test.Util; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.vertx.web.Param; +import io.quarkus.vertx.web.Route; +import io.restassured.RestAssured; +import io.vertx.ext.web.RoutingContext; + +public class HttpPathParamLimitWithReactiveRoutes500Test { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") + .overrideConfigKey("quarkus.micrometer.binder.http-client.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.http-server.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") + .withApplicationRoot((jar) -> jar + .addClasses(Util.class, + Resource.class)); + + @Inject + MeterRegistry registry; + + public static final int COUNT = 101; + + @Test + void testWithReactiveRoute500() throws InterruptedException { + registry.clear(); + + for (int i = 0; i < COUNT; i++) { + RestAssured.get("/rr").then().statusCode(500); + RestAssured.get("/rr/foo-" + i).then().statusCode(500); + } + + Util.waitForMeters(registry.find("http.server.requests").timers(), COUNT); + + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/rr").tag("method", "GET") + .timers().iterator().next().count()); + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("method", "GET").tag("uri", "/rr/{message}") + .timers().iterator().next().count()); + } + + @Singleton + public static class Resource { + + @Route(path = "/rr", methods = Route.HttpMethod.GET) + public void rr(RoutingContext rc) { + rc.response().setStatusCode(500).end("hello"); + } + + @Route(path = "/rr/:message", methods = Route.HttpMethod.GET) + public void rrWithPathParam(@Param String message, RoutingContext rc) { + rc.response().setStatusCode(500).end("hello " + message); + } + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithReactiveRoutesTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithReactiveRoutesTest.java new file mode 100644 index 0000000000000..e6eaad29b2f56 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/pathparams/HttpPathParamLimitWithReactiveRoutesTest.java @@ -0,0 +1,80 @@ +package io.quarkus.micrometer.deployment.pathparams; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.micrometer.core.instrument.MeterRegistry; +import io.quarkus.micrometer.test.Util; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.vertx.web.Param; +import io.quarkus.vertx.web.Route; +import io.restassured.RestAssured; + +public class HttpPathParamLimitWithReactiveRoutesTest { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") + .overrideConfigKey("quarkus.micrometer.binder.http-client.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.http-server.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") + .withApplicationRoot((jar) -> jar + .addClasses(Util.class, + Resource.class)); + + @Inject + MeterRegistry registry; + + public static final int COUNT = 101; + public static final int ARITY_LIMIT = 100; + + @Test + void testWithReactiveRouteOK() throws InterruptedException { + registry.clear(); + // Verify OK response + for (int i = 0; i < COUNT; i++) { + RestAssured.get("/rr").then().statusCode(200); + RestAssured.get("/rr/foo-" + i).then().statusCode(200); + } + + // Verify metrics + Util.waitForMeters(registry.find("http.server.requests").timers(), COUNT); + + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/rr").timers().iterator().next().count()); + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/rr/{message}").timers().iterator().next().count()); + + // Verify 405 responses + for (int i = 0; i < COUNT; i++) { + RestAssured.delete("/rr").then().statusCode(405); + RestAssured.patch("/rr/foo-" + i).then().statusCode(501); // Not totally sure why reactive routes return a 501, it's not necessarily wrong, just different. + } + + Util.waitForMeters(registry.find("http.server.requests").timers(), COUNT * 2); + + Assertions.assertEquals(COUNT, registry.find("http.server.requests") + .tag("uri", "/rr").tag("method", "DELETE").timers().iterator().next().count()); + Assertions.assertEquals(ARITY_LIMIT - 2, registry.find("http.server.requests") + .tag("method", "PATCH").timers().size()); // -2 because of the two other uri: /rr and /rr/{message}. + } + + @Singleton + public static class Resource { + + @Route(path = "/rr", methods = Route.HttpMethod.GET) + public String rr() { + return "hello"; + } + + @Route(path = "/rr/:message", methods = Route.HttpMethod.GET) + public String rrWithPathParam(@Param String message) { + return "hello " + message; + } + } +} diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpCommonTags.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpCommonTags.java index 7cab35f4f0446..7a43564418781 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpCommonTags.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpCommonTags.java @@ -9,7 +9,7 @@ public class HttpCommonTags { public static final Tag URI_NOT_FOUND = Tag.of("uri", "NOT_FOUND"); public static final Tag URI_REDIRECTION = Tag.of("uri", "REDIRECTION"); public static final Tag URI_ROOT = Tag.of("uri", "root"); - static final Tag URI_UNKNOWN = Tag.of("uri", "UNKNOWN"); + public static final Tag URI_UNKNOWN = Tag.of("uri", "UNKNOWN"); static final Tag STATUS_UNKNOWN = Tag.of("status", "UNKNOWN"); public static final Tag STATUS_RESET = Tag.of("status", "RESET"); From 7c3c88df75702bd196f4ba3ea3ff2f3fd9807609 Mon Sep 17 00:00:00 2001 From: brunobat Date: Tue, 7 Jan 2025 16:13:14 +0000 Subject: [PATCH 2/2] add configs to suppress uris on 4xx error metrics --- .../HttpTagExplosionPreventionTest.java | 97 +++++++++++++++++++ .../binder/HttpBinderConfiguration.java | 17 ++++ .../runtime/binder/HttpCommonTags.java | 15 ++- .../binder/RestClientMetricsFilter.java | 3 +- .../binder/vertx/VertxHttpClientMetrics.java | 2 +- .../binder/vertx/VertxHttpServerMetrics.java | 10 +- .../config/runtime/HttpClientConfig.java | 11 +++ .../config/runtime/HttpServerConfig.java | 11 +++ .../runtime/binder/HttpCommonTagsTest.java | 42 +++++--- 9 files changed, 188 insertions(+), 20 deletions(-) create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/HttpTagExplosionPreventionTest.java diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/HttpTagExplosionPreventionTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/HttpTagExplosionPreventionTest.java new file mode 100644 index 0000000000000..3699e61ccb81c --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/HttpTagExplosionPreventionTest.java @@ -0,0 +1,97 @@ +package io.quarkus.micrometer.deployment.binder; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.core.Response; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.micrometer.core.instrument.MeterRegistry; +import io.quarkus.micrometer.test.Util; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class HttpTagExplosionPreventionTest { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") + .overrideConfigKey("quarkus.micrometer.binder.http-client.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.http-server.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") + .overrideConfigKey("pingpong/mp-rest/url", "${test.url}") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") + .overrideConfigKey("quarkus.micrometer.binder.http-server.suppress4xx-errors", "true") + .withApplicationRoot((jar) -> jar + .addClasses(Util.class, + Resource.class)); + + @Inject + MeterRegistry registry; + + @Test + public void test() throws Exception { + RestAssured.get("/api/hello").then().statusCode(200); + Util.waitForMeters(registry.find("http.server.requests").timers(), 1); + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/api/hello").timers().size()); + + RestAssured.get("/api/hello/foo").then().statusCode(200); + Util.waitForMeters(registry.find("http.server.requests").timers(), 2); + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/api/hello/{message}").timers().size()); + + RestAssured.delete("/api/hello").then().statusCode(405); + Util.waitForMeters(registry.find("http.server.requests").timers(), 3); + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "UNKNOWN").timers().size()); + + RestAssured.delete("/api/hello/foo").then().statusCode(405); + RestAssured.delete("/api/hello/bar").then().statusCode(405); + RestAssured.delete("/api/hello/baz").then().statusCode(405); + Util.waitForMeters(registry.find("http.server.requests").timers(), 6); + Assertions.assertEquals(4, + registry.find("http.server.requests").tag("uri", "UNKNOWN").timers().iterator().next().count()); + + for (int i = 0; i < 10; i++) { + RestAssured.get("/api/failure").then().statusCode(500); + RestAssured.get("/api/failure/bar" + i).then().statusCode(500); + } + Util.waitForMeters(registry.find("http.server.requests").timers(), 6 + 10); + + Assertions.assertEquals(2, registry.find("http.server.requests").tag("uri", "UNKNOWN").timers().size()); // 2 different set of tags + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/api/failure/{message}").timers().size()); + } + + @Path("/") + @Singleton + public static class Resource { + + @GET + @Path("api/hello/{message}") + public String hello(@PathParam("message") String message) { + return message; + } + + @GET + @Path("api/hello/") + public String hello() { + return "hello"; + } + + @GET + @Path("api/failure") + public Response failure() { + return Response.status(500).build(); + } + + @GET + @Path("api/failure/{message}") + public Response failure(@PathParam("message") String message) { + return Response.status(500).build(); + } + + } +} diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpBinderConfiguration.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpBinderConfiguration.java index 06d3047442ad5..e9b3138571cf4 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpBinderConfiguration.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpBinderConfiguration.java @@ -38,6 +38,9 @@ public class HttpBinderConfiguration { List clientIgnorePatterns = Collections.emptyList(); Map clientMatchPatterns = Collections.emptyMap(); + boolean serverSuppress4xxErrors = false; + boolean clientSuppress4xxErrors = false; + private HttpBinderConfiguration() { } @@ -47,6 +50,8 @@ public HttpBinderConfiguration(boolean httpServerMetrics, boolean httpClientMetr serverEnabled = httpServerMetrics; clientEnabled = httpClientMetrics; + serverSuppress4xxErrors = serverConfig.suppress4xxErrors; + clientSuppress4xxErrors = clientConfig.suppress4xxErrors; if (serverEnabled) { Pattern defaultIgnore = null; @@ -104,6 +109,14 @@ public Map getClientMatchPatterns() { return clientMatchPatterns; } + public boolean isServerSuppress4xxErrors() { + return serverSuppress4xxErrors; + } + + public boolean isClientSuppress4xxErrors() { + return clientSuppress4xxErrors; + } + List getIgnorePatterns(Optional> configInput, Pattern defaultIgnore) { if (configInput.isPresent()) { List input = configInput.get(); @@ -186,6 +199,8 @@ public HttpBinderConfiguration unwrap() { // not dev-mode changeable result.clientEnabled = this.clientEnabled; result.serverEnabled = this.serverEnabled; + result.serverSuppress4xxErrors = this.serverSuppress4xxErrors; + result.clientSuppress4xxErrors = this.clientSuppress4xxErrors; return result.update(this); } @@ -194,6 +209,8 @@ public HttpBinderConfiguration update(HttpBinderConfiguration httpConfig) { this.serverMatchPatterns = httpConfig.serverMatchPatterns; this.clientIgnorePatterns = httpConfig.clientIgnorePatterns; this.serverIgnorePatterns = httpConfig.serverIgnorePatterns; + this.serverSuppress4xxErrors = httpConfig.serverSuppress4xxErrors; + this.clientSuppress4xxErrors = httpConfig.clientSuppress4xxErrors; return this; } } diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpCommonTags.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpCommonTags.java index 7a43564418781..2719af48f6fdb 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpCommonTags.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpCommonTags.java @@ -9,7 +9,7 @@ public class HttpCommonTags { public static final Tag URI_NOT_FOUND = Tag.of("uri", "NOT_FOUND"); public static final Tag URI_REDIRECTION = Tag.of("uri", "REDIRECTION"); public static final Tag URI_ROOT = Tag.of("uri", "root"); - public static final Tag URI_UNKNOWN = Tag.of("uri", "UNKNOWN"); + static final Tag URI_UNKNOWN = Tag.of("uri", "UNKNOWN"); static final Tag STATUS_UNKNOWN = Tag.of("status", "UNKNOWN"); public static final Tag STATUS_RESET = Tag.of("status", "RESET"); @@ -59,7 +59,7 @@ public static Tag outcome(int statusCode) { * @param code status code of the response * @return the uri tag derived from the request */ - public static Tag uri(String pathInfo, String initialPath, int code) { + public static Tag uri(String pathInfo, String initialPath, int code, boolean suppress4xxErrors) { if (pathInfo == null) { return URI_UNKNOWN; } @@ -80,6 +80,17 @@ public static Tag uri(String pathInfo, String initialPath, int code) { } else { return URI_NOT_FOUND; } + } else if (code >= 400) { + if (!suppress4xxErrors) { + // legacy behaviour + return Tag.of("uri", pathInfo); + } else if (isTemplatedPath(pathInfo, initialPath)) { + return Tag.of("uri", pathInfo); + } else { + // Do not return the path info as it can lead to a metrics explosion + // for 4xx and 5xx responses + return URI_UNKNOWN; + } } } diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetricsFilter.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetricsFilter.java index a5de53f0846ef..d690e505e9136 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetricsFilter.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetricsFilter.java @@ -80,7 +80,8 @@ public void filter(final ClientRequestContext requestContext, final ClientRespon sample.stop(timer .withTags(Tags.of( HttpCommonTags.method(requestContext.getMethod()), - HttpCommonTags.uri(requestPath, requestContext.getUri().getPath(), statusCode), + HttpCommonTags.uri(requestPath, requestContext.getUri().getPath(), statusCode, + httpMetricsConfig.isClientSuppress4xxErrors()), HttpCommonTags.outcome(statusCode), HttpCommonTags.status(statusCode), clientName(requestContext)))); diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpClientMetrics.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpClientMetrics.java index 90db3fddf27f4..34cc38ef6878b 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpClientMetrics.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpClientMetrics.java @@ -232,7 +232,7 @@ public static class RequestTracker extends RequestMetricInfo { this.tags = origin.and( Tag.of("address", address), HttpCommonTags.method(request.method().name()), - HttpCommonTags.uri(request.uri(), null, -1)); + HttpCommonTags.uri(request.uri(), null, -1, false)); } void requestReset() { 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 9b9cebe55007b..e7148767cc0ee 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 @@ -117,7 +117,8 @@ public HttpRequestMetric responsePushed(LongTaskTimer.Sample socketMetric, HttpM if (path != null) { pushCounter .withTags(Tags.of( - HttpCommonTags.uri(path, requestMetric.initialPath, response.statusCode()), + HttpCommonTags.uri(path, requestMetric.initialPath, response.statusCode(), + config.isServerSuppress4xxErrors()), VertxMetricsTags.method(method), VertxMetricsTags.outcome(response), HttpCommonTags.status(response.statusCode()))) @@ -173,7 +174,7 @@ public void requestReset(HttpRequestMetric requestMetric) { sample::stop, requestsTimer.withTags(Tags.of( VertxMetricsTags.method(requestMetric.request().method()), - HttpCommonTags.uri(path, requestMetric.initialPath, 0), + HttpCommonTags.uri(path, requestMetric.initialPath, 0, false), Outcome.CLIENT_ERROR.asTag(), HttpCommonTags.STATUS_RESET)), requestMetric.request().context()); @@ -199,7 +200,8 @@ public void responseEnd(HttpRequestMetric requestMetric, HttpResponse response, Timer.Sample sample = requestMetric.getSample(); Tags allTags = Tags.of( VertxMetricsTags.method(requestMetric.request().method()), - HttpCommonTags.uri(path, requestMetric.initialPath, response.statusCode()), + HttpCommonTags.uri(path, requestMetric.initialPath, response.statusCode(), + config.isServerSuppress4xxErrors()), VertxMetricsTags.outcome(response), HttpCommonTags.status(response.statusCode())); if (!httpServerMetricsTagsContributors.isEmpty()) { @@ -237,7 +239,7 @@ public LongTaskTimer.Sample connected(LongTaskTimer.Sample sample, HttpRequestMe config.getServerIgnorePatterns()); if (path != null) { return websocketConnectionTimer - .withTags(Tags.of(HttpCommonTags.uri(path, requestMetric.initialPath, 0))) + .withTags(Tags.of(HttpCommonTags.uri(path, requestMetric.initialPath, 0, false))) .start(); } return null; diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/runtime/HttpClientConfig.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/runtime/HttpClientConfig.java index 0dd3c08be3c59..1adb307cad8c9 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/runtime/HttpClientConfig.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/runtime/HttpClientConfig.java @@ -38,6 +38,17 @@ public class HttpClientConfig { @ConfigItem public Optional> ignorePatterns = Optional.empty(); + /** + * Suppress 4xx errors from metrics collection for unmatched templates. + * This configuration exists to limit cardinality explosion from caller side errors. Does not apply to 404 errors. + * + * Suppressing 4xx errors is disabled by default. + * + * @asciidoclet + */ + @ConfigItem(defaultValue = "false") + public boolean suppress4xxErrors; + /** * Maximum number of unique URI tag values allowed. After the max number of * tag values is reached, metrics with additional tag values are denied by diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/runtime/HttpServerConfig.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/runtime/HttpServerConfig.java index def6895ed886e..f9342aa1dc5d6 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/runtime/HttpServerConfig.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/runtime/HttpServerConfig.java @@ -50,6 +50,17 @@ public class HttpServerConfig { @ConfigItem(defaultValue = "true") public boolean suppressNonApplicationUris; + /** + * Suppress 4xx errors from metrics collection for unmatched templates. + * This configuration exists to limit cardinality explosion from caller side error. Does not apply to 404 errors. + * + * Suppressing 4xx errors is disabled by default. + * + * @asciidoclet + */ + @ConfigItem(defaultValue = "false") + public boolean suppress4xxErrors; + /** * Maximum number of unique URI tag values allowed. After the max number of * tag values is reached, metrics with additional tag values are denied by diff --git a/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/HttpCommonTagsTest.java b/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/HttpCommonTagsTest.java index e6bf10ee83dbc..3f4518f44f595 100644 --- a/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/HttpCommonTagsTest.java +++ b/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/HttpCommonTagsTest.java @@ -21,21 +21,39 @@ public void testStatus() { @Test public void testUriRedirect() { - Assertions.assertEquals(HttpCommonTags.URI_REDIRECTION, HttpCommonTags.uri("/moved", null, 301)); - Assertions.assertEquals(HttpCommonTags.URI_REDIRECTION, HttpCommonTags.uri("/moved", null, 302)); - Assertions.assertEquals(HttpCommonTags.URI_REDIRECTION, HttpCommonTags.uri("/moved", null, 304)); - Assertions.assertEquals(Tag.of("uri", "/moved/{id}"), HttpCommonTags.uri("/moved/{id}", "/moved/111", 304)); - Assertions.assertEquals(HttpCommonTags.URI_ROOT, HttpCommonTags.uri("/", null, 304)); + Assertions.assertEquals(HttpCommonTags.URI_REDIRECTION, HttpCommonTags.uri("/moved", null, 301, false)); + Assertions.assertEquals(HttpCommonTags.URI_REDIRECTION, HttpCommonTags.uri("/moved", null, 302, false)); + Assertions.assertEquals(HttpCommonTags.URI_REDIRECTION, HttpCommonTags.uri("/moved", null, 304, false)); + Assertions.assertEquals(Tag.of("uri", "/moved/{id}"), HttpCommonTags.uri("/moved/{id}", "/moved/111", 304, false)); + Assertions.assertEquals(HttpCommonTags.URI_ROOT, HttpCommonTags.uri("/", null, 304, false)); } @Test public void testUriDefaults() { - Assertions.assertEquals(HttpCommonTags.URI_ROOT, HttpCommonTags.uri("/", null, 200)); - Assertions.assertEquals(HttpCommonTags.URI_ROOT, HttpCommonTags.uri("/", null, 404)); - Assertions.assertEquals(Tag.of("uri", "/known/ok"), HttpCommonTags.uri("/known/ok", null, 200)); - Assertions.assertEquals(HttpCommonTags.URI_NOT_FOUND, HttpCommonTags.uri("/invalid", null, 404)); - Assertions.assertEquals(Tag.of("uri", "/invalid/{id}"), HttpCommonTags.uri("/invalid/{id}", "/invalid/111", 404)); - Assertions.assertEquals(Tag.of("uri", "/known/bad/request"), HttpCommonTags.uri("/known/bad/request", null, 400)); - Assertions.assertEquals(Tag.of("uri", "/known/server/error"), HttpCommonTags.uri("/known/server/error", null, 500)); + Assertions.assertEquals(HttpCommonTags.URI_ROOT, HttpCommonTags.uri("/", null, 200, false)); + Assertions.assertEquals(HttpCommonTags.URI_ROOT, HttpCommonTags.uri("/", null, 404, false)); + Assertions.assertEquals(Tag.of("uri", "/known/ok"), HttpCommonTags.uri("/known/ok", null, 200, false)); + Assertions.assertEquals(HttpCommonTags.URI_NOT_FOUND, HttpCommonTags.uri("/invalid", null, 404, false)); + Assertions.assertEquals(Tag.of("uri", "/invalid/{id}"), + HttpCommonTags.uri("/invalid/{id}", "/invalid/111", 404, false)); + Assertions.assertEquals(Tag.of("uri", "/known/bad/request"), + HttpCommonTags.uri("/known/bad/request", null, 400, false)); + Assertions.assertEquals(Tag.of("uri", "/known/bad/{request}"), + HttpCommonTags.uri("/known/bad/{request}", "/known/bad/request", 400, false)); + Assertions.assertEquals(Tag.of("uri", "/known/server/error"), + HttpCommonTags.uri("/known/server/error", null, 500, false)); + } + + @Test + public void testUriDefaultsWithSuppression() { + Assertions.assertEquals(HttpCommonTags.URI_ROOT, HttpCommonTags.uri("/", null, 200, true)); + Assertions.assertEquals(HttpCommonTags.URI_ROOT, HttpCommonTags.uri("/", null, 404, true)); + Assertions.assertEquals(Tag.of("uri", "/known/ok"), HttpCommonTags.uri("/known/ok", null, 200, true)); + Assertions.assertEquals(HttpCommonTags.URI_NOT_FOUND, HttpCommonTags.uri("/invalid", null, 404, true)); + Assertions.assertEquals(Tag.of("uri", "/invalid/{id}"), HttpCommonTags.uri("/invalid/{id}", "/invalid/111", 404, true)); + Assertions.assertEquals(HttpCommonTags.URI_UNKNOWN, HttpCommonTags.uri("/known/bad/request", null, 400, true)); + Assertions.assertEquals(Tag.of("uri", "/known/bad/{request}"), + HttpCommonTags.uri("/known/bad/{request}", "/known/bad/request", 400, true)); + Assertions.assertEquals(HttpCommonTags.URI_UNKNOWN, HttpCommonTags.uri("/known/server/error", null, 500, true)); } }