From 2c3a1ee2238e1d209af8e397333c3a0282841561 Mon Sep 17 00:00:00 2001 From: Erin Schnabel Date: Thu, 11 Mar 2021 08:49:05 -0500 Subject: [PATCH] Micrometer + Vert.x 4.0.3 + #15734 --- extensions/micrometer/deployment/pom.xml | 18 ++ .../deployment/MicrometerProcessor.java | 52 +---- .../binder/HttpBinderProcessor.java | 122 +++++++++++ .../binder/RestClientProcessor.java | 45 ---- .../binder/VertxBinderProcessor.java | 44 +--- .../deployment/binder/UriTagTest.java | 125 +++++++++++ .../UriTagWithHttpApplicationRootTest.java | 111 ++++++++++ .../binder/UriTagWithHttpRootTest.java | 99 +++++++++ .../binder/UriWithMaxTagMeterFilterTest.java | 49 +++++ .../binder/VertxWithHttpDisabledTest.java | 24 ++- .../binder/VertxWithHttpEnabledTest.java | 27 ++- .../micrometer/test/PingPongResource.java | 38 ++++ .../micrometer/test/ServletEndpoint.java | 19 ++ .../java/io/quarkus/micrometer/test/Util.java | 12 ++ .../micrometer/test/VertxWebEndpoint.java | 19 ++ .../test/resources/test-logging.properties | 2 +- .../runtime/MicrometerRecorder.java | 38 ++-- .../binder/HttpBinderConfiguration.java | 26 ++- ...MetricsCommon.java => HttpCommonTags.java} | 7 +- .../binder/HttpMeterFilterProvider.java | 68 ++++++ .../runtime/binder/HttpRequestMetric.java | 199 ------------------ .../runtime/binder/RequestMetricInfo.java | 96 +++++++++ .../runtime/binder/RestClientMetrics.java | 109 ---------- .../binder/RestClientMetricsListener.java | 120 +++++++++++ .../binder/vertx/HttpRequestMetric.java | 82 ++++++++ .../binder/vertx/VertxHttpServerMetrics.java | 125 ++++------- .../VertxMeterBinderContainerFilterUtil.java | 15 +- ...rtxMeterBinderRestEasyContainerFilter.java | 11 +- .../binder/vertx/VertxMeterFilter.java | 31 --- .../binder/vertx/VertxMetricsTags.java | 4 +- .../config/runtime/HttpClientConfig.java | 10 +- .../config/runtime/HttpServerConfig.java | 10 +- ...profile.rest.client.spi.RestClientListener | 2 +- .../runtime/binder/HttpCommonTagsTest.java | 37 ++++ .../runtime/binder/HttpMetricsCommonTest.java | 37 ---- .../runtime/binder/HttpRequestMetricTest.java | 140 ------------ .../runtime/binder/RequestMetricInfoTest.java | 122 +++++++++++ .../vertx/VertxHttpServerMetricsTest.java | 85 +++++--- 38 files changed, 1358 insertions(+), 822 deletions(-) create mode 100644 extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/HttpBinderProcessor.java delete mode 100644 extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/RestClientProcessor.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagTest.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpApplicationRootTest.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpRootTest.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriWithMaxTagMeterFilterTest.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/PingPongResource.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/ServletEndpoint.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/VertxWebEndpoint.java rename extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/{HttpMetricsCommon.java => HttpCommonTags.java} (91%) create mode 100644 extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpMeterFilterProvider.java delete mode 100644 extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpRequestMetric.java create mode 100644 extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RequestMetricInfo.java delete mode 100644 extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetrics.java create mode 100644 extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetricsListener.java create mode 100644 extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/HttpRequestMetric.java delete mode 100644 extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterFilter.java create mode 100644 extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/HttpCommonTagsTest.java delete mode 100644 extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/HttpMetricsCommonTest.java delete mode 100644 extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/HttpRequestMetricTest.java create mode 100644 extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/RequestMetricInfoTest.java diff --git a/extensions/micrometer/deployment/pom.xml b/extensions/micrometer/deployment/pom.xml index 0015afe491e43..9fc5f53dc9ed7 100644 --- a/extensions/micrometer/deployment/pom.xml +++ b/extensions/micrometer/deployment/pom.xml @@ -59,6 +59,12 @@ test + + io.quarkus + quarkus-rest-client-deployment + test + + io.quarkus quarkus-resteasy-deployment @@ -71,6 +77,18 @@ test + + io.quarkus + quarkus-undertow-deployment + test + + + + io.quarkus + quarkus-vertx-web-deployment + test + + io.rest-assured rest-assured diff --git a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/MicrometerProcessor.java b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/MicrometerProcessor.java index aeb2e8fae7c49..1eb7a1939c498 100644 --- a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/MicrometerProcessor.java +++ b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/MicrometerProcessor.java @@ -6,8 +6,6 @@ import java.util.Set; import java.util.function.BooleanSupplier; -import javax.inject.Singleton; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.ClassInfo; @@ -25,7 +23,6 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; -import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.DotNames; @@ -50,11 +47,7 @@ import io.quarkus.micrometer.runtime.MicrometerRecorder; import io.quarkus.micrometer.runtime.MicrometerTimed; import io.quarkus.micrometer.runtime.MicrometerTimedInterceptor; -import io.quarkus.micrometer.runtime.binder.HttpBinderConfiguration; import io.quarkus.micrometer.runtime.config.MicrometerConfig; -import io.quarkus.micrometer.runtime.config.runtime.HttpClientConfig; -import io.quarkus.micrometer.runtime.config.runtime.HttpServerConfig; -import io.quarkus.micrometer.runtime.config.runtime.VertxConfig; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.metrics.MetricsFactory; import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; @@ -72,7 +65,7 @@ public class MicrometerProcessor { private static final DotName TIMED_BINDING = DotName.createSimple(MicrometerTimed.class.getName()); private static final DotName TIMED_INTERCEPTOR = DotName.createSimple(MicrometerTimedInterceptor.class.getName()); - static class MicrometerEnabled implements BooleanSupplier { + public static class MicrometerEnabled implements BooleanSupplier { MicrometerConfig mConfig; public boolean getAsBoolean() { @@ -80,31 +73,6 @@ public boolean getAsBoolean() { } } - public static class HttpBinderEnabled implements BooleanSupplier { - MicrometerConfig mConfig; - - public boolean getAsBoolean() { - return mConfig.checkBinderEnabledWithDefault(mConfig.binder.httpServer) || - mConfig.checkBinderEnabledWithDefault(mConfig.binder.httpClient); - } - } - - public static class HttpServerBinderEnabled implements BooleanSupplier { - MicrometerConfig mConfig; - - public boolean getAsBoolean() { - return mConfig.checkBinderEnabledWithDefault(mConfig.binder.httpServer); - } - } - - public static class HttpClientBinderEnabled implements BooleanSupplier { - MicrometerConfig mConfig; - - public boolean getAsBoolean() { - return mConfig.checkBinderEnabledWithDefault(mConfig.binder.httpClient); - } - } - MicrometerConfig mConfig; @BuildStep(onlyIf = MicrometerEnabled.class) @@ -219,24 +187,6 @@ void processAnnotatedMetrics(BuildProducer anno annotationsTransformers.produce(createAnnotationTransformer(TIMED_ANNOTATION, TIMED_BINDING)); } - @BuildStep(onlyIf = { MicrometerEnabled.class }) - @Record(ExecutionTime.RUNTIME_INIT) - SyntheticBeanBuildItem enableHttpBinders(MicrometerRecorder recorder, - HttpServerConfig serverConfig, - HttpClientConfig clientConfig, - VertxConfig vertxConfig) { - return SyntheticBeanBuildItem - .configure(HttpBinderConfiguration.class) - .scope(Singleton.class) - .setRuntimeInit() - .unremovable() - .runtimeValue(recorder.configureHttpMetrics( - mConfig.checkBinderEnabledWithDefault(mConfig.binder.httpServer), - mConfig.checkBinderEnabledWithDefault(mConfig.binder.httpClient), - serverConfig, clientConfig, vertxConfig)) - .done(); - } - @BuildStep(onlyIf = MicrometerEnabled.class) @Record(ExecutionTime.STATIC_INIT) RootMeterRegistryBuildItem createRootRegistry(MicrometerRecorder recorder, diff --git a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/HttpBinderProcessor.java b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/HttpBinderProcessor.java new file mode 100644 index 0000000000000..fcc54e1fb6d5f --- /dev/null +++ b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/HttpBinderProcessor.java @@ -0,0 +1,122 @@ +package io.quarkus.micrometer.deployment.binder; + +import java.util.function.BooleanSupplier; + +import javax.inject.Singleton; + +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.micrometer.deployment.MicrometerProcessor; +import io.quarkus.micrometer.runtime.MicrometerRecorder; +import io.quarkus.micrometer.runtime.binder.HttpBinderConfiguration; +import io.quarkus.micrometer.runtime.config.MicrometerConfig; +import io.quarkus.micrometer.runtime.config.runtime.HttpClientConfig; +import io.quarkus.micrometer.runtime.config.runtime.HttpServerConfig; +import io.quarkus.micrometer.runtime.config.runtime.VertxConfig; +import io.quarkus.resteasy.common.spi.ResteasyJaxrsProviderBuildItem; +import io.quarkus.resteasy.reactive.spi.CustomContainerRequestFilterBuildItem; + +/** + * Avoid directly referencing optional dependencies + */ +public class HttpBinderProcessor { + // Common MeterFilter (uri variation limiter) + static final String HTTP_METER_FILTER_CONFIGURATION = "io.quarkus.micrometer.runtime.binder.HttpMeterFilterProvider"; + + // avoid imports due to related deps not being there + static final String RESTEASY_CONTAINER_FILTER_CLASS_NAME = "io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderRestEasyContainerFilter"; + static final String QUARKUS_REST_CONTAINER_FILTER_CLASS_NAME = "io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderQuarkusRestContainerFilter"; + + // Rest client listener SPI + private static final String REST_CLIENT_LISTENER_CLASS_NAME = "org.eclipse.microprofile.rest.client.spi.RestClientListener"; + private static final Class REST_CLIENT_LISTENER_CLASS = MicrometerRecorder + .getClassForName(REST_CLIENT_LISTENER_CLASS_NAME); + + // Rest Client listener + private static final String REST_CLIENT_METRICS_LISTENER = "io.quarkus.micrometer.runtime.binder.RestClientMetricsListener"; + + static class HttpServerBinderEnabled implements BooleanSupplier { + MicrometerConfig mConfig; + + public boolean getAsBoolean() { + return mConfig.checkBinderEnabledWithDefault(mConfig.binder.vertx) + && mConfig.checkBinderEnabledWithDefault(mConfig.binder.httpServer); + } + } + + static class HttpClientBinderEnabled implements BooleanSupplier { + MicrometerConfig mConfig; + + public boolean getAsBoolean() { + return REST_CLIENT_LISTENER_CLASS != null + && mConfig.checkBinderEnabledWithDefault(mConfig.binder.httpClient); + } + } + + @BuildStep(onlyIf = MicrometerProcessor.MicrometerEnabled.class) + @Record(ExecutionTime.RUNTIME_INIT) + SyntheticBeanBuildItem enableHttpBinders(MicrometerRecorder recorder, + MicrometerConfig buildTimeConfig, + HttpServerConfig serverConfig, + HttpClientConfig clientConfig, + VertxConfig vertxConfig, + BuildProducer additionalBeans) { + + boolean clientEnabled = buildTimeConfig.checkBinderEnabledWithDefault(buildTimeConfig.binder.httpClient); + boolean serverEnabled = buildTimeConfig.checkBinderEnabledWithDefault(buildTimeConfig.binder.httpServer); + + if (clientEnabled || serverEnabled) { + // Protect from uri tag flood + createAdditionalBean(additionalBeans, HTTP_METER_FILTER_CONFIGURATION); + } + + // Other things use this bean to test whether or not http server/client metrics are enabled + return SyntheticBeanBuildItem + .configure(HttpBinderConfiguration.class) + .scope(Singleton.class) + .setRuntimeInit() + .unremovable() + .runtimeValue(recorder.configureHttpMetrics(serverEnabled, clientEnabled, + serverConfig, clientConfig, vertxConfig)) + .done(); + } + + @BuildStep(onlyIf = HttpServerBinderEnabled.class) + void enableHttpServerSupport(Capabilities capabilities, + BuildProducer resteasyJaxrsProviders, + BuildProducer customContainerRequestFilter, + BuildProducer additionalBeans) { + + if (capabilities.isPresent(Capability.RESTEASY)) { + resteasyJaxrsProviders.produce(new ResteasyJaxrsProviderBuildItem(RESTEASY_CONTAINER_FILTER_CLASS_NAME)); + createAdditionalBean(additionalBeans, RESTEASY_CONTAINER_FILTER_CLASS_NAME); + } else if (capabilities.isPresent(Capability.RESTEASY_REACTIVE)) { + customContainerRequestFilter + .produce(new CustomContainerRequestFilterBuildItem(QUARKUS_REST_CONTAINER_FILTER_CLASS_NAME)); + createAdditionalBean(additionalBeans, QUARKUS_REST_CONTAINER_FILTER_CLASS_NAME); + } + } + + @BuildStep(onlyIf = HttpClientBinderEnabled.class) + void registerRestClientListener(BuildProducer resource, + BuildProducer reflectiveClass) { + resource.produce(new NativeImageResourceBuildItem( + "META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener")); + reflectiveClass + .produce(new ReflectiveClassBuildItem(true, true, REST_CLIENT_METRICS_LISTENER)); + } + + private void createAdditionalBean(BuildProducer additionalBeans, String className) { + additionalBeans.produce(AdditionalBeanBuildItem.builder() + .addBeanClass(className) + .setUnremovable().build()); + } +} diff --git a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/RestClientProcessor.java b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/RestClientProcessor.java deleted file mode 100644 index 6b7ac60fa4336..0000000000000 --- a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/RestClientProcessor.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.quarkus.micrometer.deployment.binder; - -import java.util.function.BooleanSupplier; - -import io.quarkus.arc.deployment.UnremovableBeanBuildItem; -import io.quarkus.deployment.annotations.BuildProducer; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; -import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; -import io.quarkus.micrometer.deployment.MicrometerProcessor; -import io.quarkus.micrometer.runtime.MicrometerRecorder; -import io.quarkus.micrometer.runtime.config.MicrometerConfig; - -public class RestClientProcessor { - // Avoid referencing optional dependencies - - // Rest client listener SPI - private static final String REST_CLIENT_LISTENER_CLASS_NAME = "org.eclipse.microprofile.rest.client.spi.RestClientListener"; - private static final Class REST_CLIENT_LISTENER_CLASS = MicrometerRecorder - .getClassForName(REST_CLIENT_LISTENER_CLASS_NAME); - - // Rest Client listener - private static final String REST_CLIENT_METRICS_LISTENER = "io.quarkus.micrometer.runtime.binder.RestClientMetrics"; - // Http Client runtime config (injected programmatically) - private static final String REST_CLIENT_HTTP_CONFIG = "io.quarkus.micrometer.runtime.config.runtime.HttpClientConfig"; - - static class RestClientEnabled implements BooleanSupplier { - MicrometerConfig mConfig; - - public boolean getAsBoolean() { - return REST_CLIENT_LISTENER_CLASS != null && mConfig.checkBinderEnabledWithDefault(mConfig.binder.httpClient); - } - } - - @BuildStep(onlyIf = { RestClientEnabled.class, MicrometerProcessor.HttpClientBinderEnabled.class }) - UnremovableBeanBuildItem registerRestClientListener(BuildProducer resource, - BuildProducer reflectiveClass) { - resource.produce(new NativeImageResourceBuildItem( - "META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener")); - reflectiveClass - .produce(new ReflectiveClassBuildItem(true, true, REST_CLIENT_METRICS_LISTENER)); - - return new UnremovableBeanBuildItem(new UnremovableBeanBuildItem.BeanClassNameExclusion(REST_CLIENT_HTTP_CONFIG)); - } -} 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 269b1ff72b8e1..e3c6a54c9881a 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 @@ -6,29 +6,20 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem; -import io.quarkus.deployment.Capabilities; -import io.quarkus.deployment.Capability; -import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Consume; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; -import io.quarkus.micrometer.deployment.MicrometerProcessor; import io.quarkus.micrometer.runtime.MicrometerRecorder; import io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter; import io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderRecorder; -import io.quarkus.micrometer.runtime.binder.vertx.VertxMeterFilter; import io.quarkus.micrometer.runtime.config.MicrometerConfig; import io.quarkus.micrometer.runtime.config.runtime.VertxConfig; -import io.quarkus.resteasy.common.spi.ResteasyJaxrsProviderBuildItem; -import io.quarkus.resteasy.reactive.spi.CustomContainerRequestFilterBuildItem; import io.quarkus.vertx.core.deployment.VertxOptionsConsumerBuildItem; -import io.quarkus.vertx.http.deployment.FilterBuildItem; /** - * Add support for the Vert.x and other http instrumentation. - * Note that various bits of support may not be present at deploy time, - * e.g. Vert.x can be present while resteasy is not. + * Add support for Vert.x instrumentation. + * HTTP instrumentation is dependent on Vert.x, but has been pulled out into its own processor * * Avoid referencing classes that in turn import optional dependencies. */ @@ -44,32 +35,6 @@ public boolean getAsBoolean() { } } - // avoid imports due to related deps not being there - static final String RESTEASY_CONTAINER_FILTER_CLASS_NAME = "io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderRestEasyContainerFilter"; - static final String QUARKUS_REST_CONTAINER_FILTER_CLASS_NAME = "io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderQuarkusRestContainerFilter"; - - @BuildStep(onlyIf = { VertxBinderEnabled.class, MicrometerProcessor.HttpServerBinderEnabled.class }) - void enableJaxRsSupport(Capabilities capabilities, - BuildProducer resteasyJaxrsProviders, - BuildProducer customContainerRequestFilter, - BuildProducer additionalBeans) { - - if (capabilities.isPresent(Capability.RESTEASY)) { - resteasyJaxrsProviders.produce(new ResteasyJaxrsProviderBuildItem(RESTEASY_CONTAINER_FILTER_CLASS_NAME)); - turnVertxBinderFilterIntoBean(additionalBeans, RESTEASY_CONTAINER_FILTER_CLASS_NAME); - } else if (capabilities.isPresent(Capability.RESTEASY_REACTIVE)) { - customContainerRequestFilter - .produce(new CustomContainerRequestFilterBuildItem(QUARKUS_REST_CONTAINER_FILTER_CLASS_NAME)); - turnVertxBinderFilterIntoBean(additionalBeans, QUARKUS_REST_CONTAINER_FILTER_CLASS_NAME); - } - } - - private void turnVertxBinderFilterIntoBean(BuildProducer additionalBeans, String className) { - additionalBeans.produce(AdditionalBeanBuildItem.builder() - .addBeanClass(className) - .setUnremovable().build()); - } - @BuildStep(onlyIf = VertxBinderEnabled.class) AdditionalBeanBuildItem createVertxAdapters() { // Add Vertx meter adapters @@ -78,11 +43,6 @@ AdditionalBeanBuildItem createVertxAdapters() { .setUnremovable().build(); } - @BuildStep(onlyIf = { VertxBinderEnabled.class, MicrometerProcessor.HttpServerBinderEnabled.class }) - FilterBuildItem addVertxMeterFilter() { - return new FilterBuildItem(new VertxMeterFilter(), Integer.MAX_VALUE); - } - @BuildStep(onlyIf = VertxBinderEnabled.class) @Record(value = ExecutionTime.STATIC_INIT) VertxOptionsConsumerBuildItem build(VertxMeterBinderRecorder recorder) { diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagTest.java new file mode 100644 index 0000000000000..bb48dc6c7d7af --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagTest.java @@ -0,0 +1,125 @@ +package io.quarkus.micrometer.deployment.binder; + +import static io.restassured.RestAssured.when; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +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.ServletEndpoint; +import io.quarkus.micrometer.test.Util; +import io.quarkus.micrometer.test.VertxWebEndpoint; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class UriTagTest { + @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.http-server.match-patterns", "/one=/two") + .overrideConfigKey("quarkus.micrometer.binder.http-server.ignore-patterns", "/two") + .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") + .overrideConfigKey("pingpong/mp-rest/url", "${test.url}") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Util.class, + PingPongResource.class, + PingPongResource.PingPongRestClient.class, + ServletEndpoint.class, + VertxWebEndpoint.class)); + + @Inject + MeterRegistry registry; + + @Test + public void testMetricFactoryCreatedMetrics() throws Exception { + RestAssured.basePath = "/"; + + // If you invoke requests, http server and client meters should be registered + + when().get("/one").then().statusCode(200); + when().get("/two").then().statusCode(200); + when().get("/ping/one").then().statusCode(200); + when().get("/ping/two").then().statusCode(200); + when().get("/ping/three").then().statusCode(200); + when().get("/vertx/item/123").then().statusCode(200); + when().get("/vertx/item/1/123").then().statusCode(200); + when().get("/servlet/12345").then().statusCode(200); + + // /one should map to /two, which is ignored. Neither should exist w/ timers + Assertions.assertEquals(0, registry.find("http.server.requests").tag("uri", "/one").timers().size(), + "/one is mapped to /two, which should be ignored. Found:\n" + + Util.listMeters(registry.find("http.server.requests").meters(), "uri")); + Assertions.assertEquals(0, registry.find("http.server.requests").tag("uri", "/two").timers().size(), + "/two should be ignored. Found:\n" + + Util.listMeters(registry.find("http.server.requests").meters(), "uri")); + + // URIs for server: /ping/{message}, /pong/{message}, /vertx/item/{id}, /vertx/item/{id}/{sub}, /servlet/ + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/ping/{message}").timers().size(), + "/ping/{message} should be returned by JAX-RS. Found:\n" + + Util.listMeters(registry.find("http.server.requests").meters(), "uri")); + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/pong/{message}").timers().size(), + "/pong/{message} should be returned by JAX-RS. Found:\n" + + Util.listMeters(registry.find("http.server.requests").meters(), "uri")); + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/vertx/item/{id}").timers().size(), + "Vert.x Web template path (/vertx/item/:id) should be detected/translated to /vertx/item/{id}. Found:\n" + + Util.listMeters(registry.find("http.server.requests").meters(), "uri")); + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/vertx/item/{id}/{sub}").timers().size(), + "Vert.x Web template path (/vertx/item/:id/:sub) should be detected/translated to /vertx/item/{id}/{sub}. Found:\n" + + Util.listMeters(registry.find("http.server.requests").meters(), "uri")); + } + + @Path("/") + @Singleton + public static class PingPongResource { + + @RegisterRestClient(configKey = "pingpong") + public interface PingPongRestClient { + + @Path("/pong/{message}") + @GET + String pingpong(@PathParam("message") String message); + } + + @Inject + @RestClient + PingPongRestClient pingRestClient; + + @GET + @Path("pong/{message}") + public String pong(@PathParam("message") String message) { + return message; + } + + @GET + @Path("ping/{message}") + public String ping(@PathParam("message") String message) { + return pingRestClient.pingpong(message); + } + + @GET + @Path("one") + public String one() { + return "OK"; + } + + @GET + @Path("two") + public String two() { + return "OK"; + } + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpApplicationRootTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpApplicationRootTest.java new file mode 100644 index 0000000000000..3945aa1074536 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpApplicationRootTest.java @@ -0,0 +1,111 @@ +package io.quarkus.micrometer.deployment.binder; + +import static io.restassured.RestAssured.when; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Application; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +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.ServletEndpoint; +import io.quarkus.micrometer.test.Util; +import io.quarkus.micrometer.test.VertxWebEndpoint; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class UriTagWithHttpApplicationRootTest { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.http.root-path", "/foo") + .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}") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Util.class, + PingPongResource.class, + PingPongResource.PingPongRestClient.class, + ServletEndpoint.class, + VertxWebEndpoint.class, + BarApp.class)); + + @Inject + MeterRegistry registry; + + @Test + public void testMetricFactoryCreatedMetrics() throws Exception { + RestAssured.basePath = "/"; + + // If you invoke requests, http server and client meters should be registered + // Leading context root (/foo) should be stripped from resulting _server_ tag + // Application path (/bar) only impacts REST endpoints + + when().get("/foo/bar/ping/one").then().statusCode(200); + when().get("/foo/bar/ping/two").then().statusCode(200); + when().get("/foo/bar/ping/three").then().statusCode(200); + when().get("/foo/vertx/item/123").then().statusCode(200); + when().get("/foo/servlet/12345").then().statusCode(200); + + // TODO: Fixing in master #15407 + + // URIs for server should include Application Path: /bar/ping/{message}, /bar/pong/{message} + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/ping/{message}").timers().size(), + "/bar/ping/{message} should be returned by JAX-RS. Found:\n" + + Util.listMeters(registry.find("http.server.requests").meters(), "uri")); + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/pong/{message}").timers().size(), + "/bar/pong/{message} should be returned by JAX-RS. Found:\n" + + Util.listMeters(registry.find("http.server.requests").meters(), "uri")); + + // Application Path does not apply to non-rest endpoints: /vertx/item/{id} + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/vertx/item/{id}").timers().size(), + "Vert.x Web template path (/vertx/item/:id) should be detected/translated to /vertx/item/{id}. Found:\n" + + Util.listMeters(registry.find("http.server.requests").meters(), "uri")); + } + + @ApplicationPath("/bar") + public static class BarApp extends Application { + } + + @Path("/") + @Singleton + public static class PingPongResource { + + @RegisterRestClient(configKey = "pingpong") + public interface PingPongRestClient { + + @Path("/foo/bar/pong/{message}") + @GET + String pingpong(@PathParam("message") String message); + } + + @Inject + @RestClient + PingPongRestClient pingRestClient; + + @GET + @Path("pong/{message}") + public String pong(@PathParam("message") String message) { + return message; + } + + @GET + @Path("ping/{message}") + public String ping(@PathParam("message") String message) { + return pingRestClient.pingpong(message); + } + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpRootTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpRootTest.java new file mode 100644 index 0000000000000..9153450a3c9cc --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpRootTest.java @@ -0,0 +1,99 @@ +package io.quarkus.micrometer.deployment.binder; + +import static io.restassured.RestAssured.when; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +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.ServletEndpoint; +import io.quarkus.micrometer.test.Util; +import io.quarkus.micrometer.test.VertxWebEndpoint; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class UriTagWithHttpRootTest { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.http.root-path", "/foo") + .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}") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Util.class, + PingPongResource.class, + PingPongResource.PingPongRestClient.class, + ServletEndpoint.class, + VertxWebEndpoint.class)); + + @Inject + MeterRegistry registry; + + @Test + public void testMetricFactoryCreatedMetrics() throws Exception { + RestAssured.basePath = "/"; + + // If you invoke requests, http server and client meters should be registered + // Leading context root (/foo) should be stripped from resulting _server_ tag + + when().get("/foo/ping/one").then().statusCode(200); + when().get("/foo/ping/two").then().statusCode(200); + when().get("/foo/ping/three").then().statusCode(200); + when().get("/foo/vertx/item/123").then().statusCode(200); + when().get("/foo/servlet/12345").then().statusCode(200); + + // URIs for server: /ping/{message}, /pong/{message}, /vertx/item/{id} + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/ping/{message}").timers().size(), + "/ping/{message} should be returned by JAX-RS. Found:\n" + + Util.listMeters(registry.find("http.server.requests").meters(), "uri")); + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/pong/{message}").timers().size(), + "/pong/{message} should be returned by JAX-RS. Found:\n" + + Util.listMeters(registry.find("http.server.requests").meters(), "uri")); + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/vertx/item/{id}").timers().size(), + "Vert.x Web template path (/vertx/item/:id) should be detected/translated to /vertx/item/{id}. Found:\n" + + Util.listMeters(registry.find("http.server.requests").meters(), "uri")); + } + + @Path("/") + @Singleton + public static class PingPongResource { + + @RegisterRestClient(configKey = "pingpong") + public interface PingPongRestClient { + + @Path("/foo/pong/{message}") + @GET + String pingpong(@PathParam("message") String message); + } + + @Inject + @RestClient + PingPongRestClient pingRestClient; + + @GET + @Path("pong/{message}") + public String pong(@PathParam("message") String message) { + return message; + } + + @GET + @Path("ping/{message}") + public String ping(@PathParam("message") String message) { + return pingRestClient.pingpong(message); + } + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriWithMaxTagMeterFilterTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriWithMaxTagMeterFilterTest.java new file mode 100644 index 0000000000000..99664a373d913 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriWithMaxTagMeterFilterTest.java @@ -0,0 +1,49 @@ +package io.quarkus.micrometer.deployment.binder; + +import static io.restassured.RestAssured.when; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +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.runtime.config.runtime.HttpServerConfig; +import io.quarkus.micrometer.test.PingPongResource; +import io.quarkus.test.QuarkusUnitTest; + +public class UriWithMaxTagMeterFilterTest { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") + .overrideConfigKey("quarkus.micrometer.binder.http-server.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.http-server.max-uri-tags", "1") + .overrideConfigKey("quarkus.micrometer.export.prometheus.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") + .overrideConfigKey("pingpong/mp-rest/url", "${test.url}") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(PingPongResource.class, PingPongResource.PingPongRestClient.class)); + + @Inject + HttpServerConfig httpServerConfig; + + @Inject + MeterRegistry registry; + + @Test + public void testMetricFactoryCreatedMetrics() throws Exception { + Assertions.assertEquals(1, httpServerConfig.maxUriTags); + + // Server will have both /ping/{message} and /pong/{message} + // Due to limit, there should only be one. + when().get("/ping/one").then().statusCode(200); + when().get("/ping/two").then().statusCode(200); + when().get("/ping/three").then().statusCode(200); + Assertions.assertEquals(1, registry.find("http.server.requests").timers().size()); + } + +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/VertxWithHttpDisabledTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/VertxWithHttpDisabledTest.java index 5e7b33175fba8..3c64418f11dd7 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/VertxWithHttpDisabledTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/VertxWithHttpDisabledTest.java @@ -1,5 +1,7 @@ package io.quarkus.micrometer.deployment.binder; +import static io.restassured.RestAssured.when; + import javax.enterprise.inject.Instance; import javax.inject.Inject; @@ -9,8 +11,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.micrometer.core.instrument.MeterRegistry; import io.quarkus.micrometer.runtime.binder.HttpBinderConfiguration; import io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter; +import io.quarkus.micrometer.test.PingPongResource; import io.quarkus.test.QuarkusUnitTest; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.SocketAddress; @@ -21,7 +25,9 @@ public class VertxWithHttpDisabledTest { .withConfigurationResource("test-logging.properties") .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") - .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + .overrideConfigKey("pingpong/mp-rest/url", "${test.url}") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(PingPongResource.class, PingPongResource.PingPongRestClient.class)); @Inject Instance vertxMeterBinderAdapterInstance; @@ -29,8 +35,15 @@ public class VertxWithHttpDisabledTest { @Inject HttpBinderConfiguration httpBinderConfiguration; + @Inject + MeterRegistry registry; + @Test public void testVertxMetricsWithoutHttp() throws Exception { + Assertions.assertFalse(httpBinderConfiguration.isClientEnabled()); + Assertions.assertFalse(httpBinderConfiguration.isServerEnabled()); + + // Vertx Binder should exist Assertions.assertTrue(vertxMeterBinderAdapterInstance.isResolvable()); VertxMeterBinderAdapter adapter = vertxMeterBinderAdapterInstance.get(); @@ -73,6 +86,13 @@ public boolean isDomainSocket() { } })); - Assertions.assertFalse(httpBinderConfiguration.isServerEnabled()); + // If you invoke requests, no http server or client meters should be registered + + when().get("/ping/one").then().statusCode(200); + when().get("/ping/two").then().statusCode(200); + when().get("/ping/three").then().statusCode(200); + + Assertions.assertEquals(0, registry.find("http.server.requests").timers().size()); + Assertions.assertEquals(0, registry.find("http.client.requests").timers().size()); } } diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/VertxWithHttpEnabledTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/VertxWithHttpEnabledTest.java index fb54f9274f1a4..50e8bb997a5b7 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/VertxWithHttpEnabledTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/VertxWithHttpEnabledTest.java @@ -1,5 +1,7 @@ package io.quarkus.micrometer.deployment.binder; +import static io.restassured.RestAssured.when; + import java.util.Map; import java.util.regex.Pattern; @@ -12,8 +14,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.micrometer.core.instrument.MeterRegistry; import io.quarkus.micrometer.runtime.binder.HttpBinderConfiguration; import io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter; +import io.quarkus.micrometer.test.PingPongResource; import io.quarkus.test.QuarkusUnitTest; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.SocketAddress; @@ -24,12 +28,15 @@ public class VertxWithHttpEnabledTest { 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.http-server.ignore-patterns", "/http") .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") .overrideConfigKey("quarkus.micrometer.binder.vertx.match-patterns", "/one=/two") .overrideConfigKey("quarkus.micrometer.binder.vertx.ignore-patterns", "/two") - .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + .overrideConfigKey("pingpong/mp-rest/url", "${test.url}") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(PingPongResource.class, PingPongResource.PingPongRestClient.class)); @Inject Instance vertxMeterBinderAdapterInstance; @@ -37,8 +44,15 @@ public class VertxWithHttpEnabledTest { @Inject HttpBinderConfiguration httpBinderConfiguration; + @Inject + MeterRegistry registry; + @Test public void testMetricFactoryCreatedMetrics() throws Exception { + Assertions.assertTrue(httpBinderConfiguration.isClientEnabled()); + Assertions.assertTrue(httpBinderConfiguration.isServerEnabled()); + + // Vertx Binder should exist Assertions.assertTrue(vertxMeterBinderAdapterInstance.isResolvable()); VertxMeterBinderAdapter adapter = vertxMeterBinderAdapterInstance.get(); @@ -92,5 +106,16 @@ public boolean isDomainSocket() { Map.Entry entry = httpBinderConfiguration.getServerMatchPatterns().entrySet().iterator().next(); Assertions.assertTrue(entry.getKey().matcher("/one").matches()); Assertions.assertEquals("/two", entry.getValue()); + + // If you invoke requests, http server and client meters should be registered + + when().get("/ping/one").then().statusCode(200); + when().get("/ping/two").then().statusCode(200); + when().get("/ping/three").then().statusCode(200); + + // For server: /ping/{message} and /pong/{message}. + Assertions.assertNotEquals(0, registry.find("http.server.requests").timers().size()); + // For client: /pong/{message} + Assertions.assertNotEquals(0, registry.find("http.client.requests").timers().size()); } } diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/PingPongResource.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/PingPongResource.java new file mode 100644 index 0000000000000..e4277bdba1c5c --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/PingPongResource.java @@ -0,0 +1,38 @@ +package io.quarkus.micrometer.test; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipse.microprofile.rest.client.inject.RestClient; + +@Path("/") +@Singleton +public class PingPongResource { + @RegisterRestClient(configKey = "pingpong") + public interface PingPongRestClient { + + @Path("pong/{message}") + @GET + String pingpong(@PathParam("message") String message); + } + + @Inject + @RestClient + PingPongRestClient pingRestClient; + + @GET + @Path("pong/{message}") + public String pong(@PathParam("message") String message) { + return message; + } + + @GET + @Path("ping/{message}") + public String ping(@PathParam("message") String message) { + return pingRestClient.pingpong(message); + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/ServletEndpoint.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/ServletEndpoint.java new file mode 100644 index 0000000000000..8f9b4e0818546 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/ServletEndpoint.java @@ -0,0 +1,19 @@ +package io.quarkus.micrometer.test; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet(name = "ServletEndpoint", urlPatterns = "/servlet/*") +public class ServletEndpoint extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setContentType("text/plain"); + resp.getWriter().println("OK"); + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/Util.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/Util.java index 5d646d37c8b87..ff98bff0a2627 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/Util.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/Util.java @@ -1,11 +1,15 @@ package io.quarkus.micrometer.test; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.logging.LogRecord; +import java.util.stream.Collectors; import org.junit.jupiter.api.Assertions; +import io.micrometer.core.instrument.Meter; + public class Util { private Util() { } @@ -26,4 +30,12 @@ static String stackToString(Throwable t) { Arrays.asList(t.getStackTrace()).forEach(x -> sb.append("\t").append(x.toString()).append("\n")); return sb.toString(); } + + public static String listMeters(Collection collection, final String tag) { + return collection.stream() + .map(x -> { + return x.getId().getTag(tag); + }) + .collect(Collectors.joining(",")); + } } diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/VertxWebEndpoint.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/VertxWebEndpoint.java new file mode 100644 index 0000000000000..7abc7e72da032 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/VertxWebEndpoint.java @@ -0,0 +1,19 @@ +package io.quarkus.micrometer.test; + +import io.quarkus.vertx.web.Param; +import io.quarkus.vertx.web.Route; +import io.quarkus.vertx.web.Route.HttpMethod; +import io.quarkus.vertx.web.RouteBase; + +@RouteBase(path = "/vertx") +public class VertxWebEndpoint { + @Route(path = "item/:id", methods = HttpMethod.GET) + public String item(@Param("id") Integer id) { + return "message with id " + id; + } + + @Route(path = "item/:id/:sub", methods = HttpMethod.GET) + public String item(@Param("id") Integer id, @Param("sub") Integer sub) { + return "message with id " + id + " and sub " + sub; + } +} diff --git a/extensions/micrometer/deployment/src/test/resources/test-logging.properties b/extensions/micrometer/deployment/src/test/resources/test-logging.properties index 6eed6ab2596da..320692f541b52 100644 --- a/extensions/micrometer/deployment/src/test/resources/test-logging.properties +++ b/extensions/micrometer/deployment/src/test/resources/test-logging.properties @@ -1,4 +1,4 @@ -#quarkus.log.category."io.quarkus.micrometer".level=DEBUG +quarkus.log.category."io.quarkus.micrometer".level=DEBUG quarkus.log.category."io.quarkus.bootstrap".level=INFO #quarkus.log.category."io.quarkus.arc".level=DEBUG quarkus.log.category."io.netty".level=INFO diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerRecorder.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerRecorder.java index 479777b793128..8a66fc70d9e20 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerRecorder.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/MicrometerRecorder.java @@ -66,32 +66,37 @@ public void configureRegistries(MicrometerConfig config, Map, List> classMeterFilters = new HashMap<>(registryClasses.size()); // Find global/common registry configuration - Instance globalFilters = beanManager.createInstance() + List globalFilters = new ArrayList<>(); + Instance globalFilterInstance = beanManager.createInstance() .select(MeterFilter.class, Default.Literal.INSTANCE); + populateListWithMeterFilters(globalFilterInstance, globalFilters); // Find MeterFilters for specific registry classes, i.e.: // @MeterFilterConstraint(applyTo = DatadogMeterRegistry.class) Instance filters log.debugf("Configuring Micrometer registries : %s", registryClasses); for (Class typeClass : registryClasses) { - Instance classFilters = beanManager.createInstance() + Instance classFilterInstance = beanManager.createInstance() .select(MeterFilter.class, new MeterFilterConstraint.Literal(typeClass)); - if (!classFilters.isUnsatisfied()) { - log.debugf("MeterFilter discovered for %s", typeClass); - classMeterFilters.computeIfAbsent(typeClass, k -> new ArrayList<>()).add(classFilters.get()); - } + List classFilters = classMeterFilters.computeIfAbsent(typeClass, k -> new ArrayList<>()); + + populateListWithMeterFilters(classFilterInstance, classFilters); } // Find and configure MeterRegistry beans (includes runtime config) Set> beans = new HashSet<>(beanManager.getBeans(MeterRegistry.class, Any.Literal.INSTANCE)); beans.removeIf(bean -> bean.getBeanClass().equals(CompositeRegistryCreator.class)); + // Apply global filters to the global registry + applyMeterFilters(Metrics.globalRegistry, globalFilters); + for (Bean i : beans) { MeterRegistry registry = (MeterRegistry) beanManager .getReference(i, MeterRegistry.class, beanManager.createCreationalContext(i)); // Add & configure non-root registries if (registry != Metrics.globalRegistry) { - applyMeterFilters(registry, globalFilters, classMeterFilters.get(registry.getClass())); + applyMeterFilters(registry, globalFilters); + applyMeterFilters(registry, classMeterFilters.get(registry.getClass())); log.debugf("Adding configured registry %s", registry.getClass(), registry); Metrics.globalRegistry.add(registry); allRegistries.add(registry); @@ -136,17 +141,20 @@ public void run() { }); } - void applyMeterFilters(MeterRegistry registry, Instance globalFilters, List classFilters) { - // Apply global filters to the registry - if (!globalFilters.isUnsatisfied()) { - for (MeterFilter generalFilter : globalFilters) { - registry.config().meterFilter(generalFilter); + void populateListWithMeterFilters(Instance filterInstance, List meterFilters) { + if (!filterInstance.isUnsatisfied()) { + for (MeterFilter filter : filterInstance) { + // @Produces methods can return null, and those will turn up here. + if (filter != null) { + meterFilters.add(filter); + } } } + } - // Apply type/class-specific MeterFilters, if any - if (classFilters != null) { - for (MeterFilter meterFilter : classFilters) { + void applyMeterFilters(MeterRegistry registry, List filters) { + if (filters != null) { + for (MeterFilter meterFilter : filters) { registry.config().meterFilter(meterFilter); } } 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 c32466d157c6b..945c7190f69c0 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 @@ -85,9 +85,9 @@ List getIgnorePatterns(Optional> configInput) { List input = configInput.get(); List ignorePatterns = new ArrayList<>(input.size()); for (String s : input) { - ignorePatterns.add(Pattern.compile(s)); + ignorePatterns.add(Pattern.compile(s.trim())); } - return ignorePatterns; + return Collections.unmodifiableList(ignorePatterns); } return Collections.emptyList(); } @@ -99,8 +99,8 @@ Map getMatchPatterns(Optional> configInput) { for (String s : input) { int pos = s.indexOf("="); if (pos > 0 && s.length() > 2) { - String pattern = s.substring(0, pos); - String replacement = s.substring(pos + 1); + String pattern = s.substring(0, pos).trim(); + String replacement = s.substring(pos + 1).trim(); try { matchPatterns.put(Pattern.compile(pattern), replacement); } catch (PatternSyntaxException pse) { @@ -110,8 +110,24 @@ Map getMatchPatterns(Optional> configInput) { log.errorf("Invalid pattern in replacement string (%s). Should be pattern=replacement", s); } } - return matchPatterns; + return Collections.unmodifiableMap(matchPatterns); } return Collections.emptyMap(); } + + public String getHttpServerRequestsName() { + return "http.server.requests"; + } + + public String getHttpServerPushName() { + return "http.server.push"; + } + + public String getHttpServerWebSocketConnectionsName() { + return "http.server.websocket.connections"; + } + + public String getHttpClientRequestsName() { + return "http.client.requests"; + } } diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpMetricsCommon.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpCommonTags.java similarity index 91% rename from extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpMetricsCommon.java rename to extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpCommonTags.java index ca6428974e870..a6aa48f167cf0 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpMetricsCommon.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpCommonTags.java @@ -1,11 +1,9 @@ package io.quarkus.micrometer.runtime.binder; -import java.util.regex.Pattern; - import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.binder.http.Outcome; -public class HttpMetricsCommon { +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"); @@ -16,9 +14,6 @@ public class HttpMetricsCommon { public static final Tag METHOD_UNKNOWN = Tag.of("method", "UNKNOWN"); - public static final Pattern TRAILING_SLASH_PATTERN = Pattern.compile("/$"); - public static final Pattern MULTIPLE_SLASH_PATTERN = Pattern.compile("//+"); - /** * Creates an {@code method} {@code Tag} derived from the given {@code HTTP method}. * diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpMeterFilterProvider.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpMeterFilterProvider.java new file mode 100644 index 0000000000000..52005c4e40320 --- /dev/null +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpMeterFilterProvider.java @@ -0,0 +1,68 @@ +package io.quarkus.micrometer.runtime.binder; + +import javax.inject.Singleton; +import javax.ws.rs.Produces; + +import io.micrometer.core.instrument.config.MeterFilter; +import io.micrometer.core.instrument.internal.OnlyOnceLoggingDenyMeterFilter; +import io.quarkus.micrometer.runtime.config.runtime.HttpClientConfig; +import io.quarkus.micrometer.runtime.config.runtime.HttpServerConfig; + +@Singleton +public class HttpMeterFilterProvider { + + HttpBinderConfiguration binderConfiguration; + + HttpMeterFilterProvider(HttpBinderConfiguration binderConfiguration) { + this.binderConfiguration = binderConfiguration; + } + + @Singleton + @Produces + public MeterFilter metricsHttpClientUriTagFilter(HttpClientConfig httpClientConfig) { + if (binderConfiguration.isClientEnabled()) { + return maximumAllowableUriTagsFilter(binderConfiguration.getHttpClientRequestsName(), + httpClientConfig.maxUriTags); + } + return null; + } + + @Singleton + @Produces + public MeterFilter metricsHttpServerUriTagFilter(HttpServerConfig httpServerConfig) { + if (binderConfiguration.isServerEnabled()) { + return maximumAllowableUriTagsFilter(binderConfiguration.getHttpServerRequestsName(), + httpServerConfig.maxUriTags); + } + return null; + } + + @Singleton + @Produces + public MeterFilter metricsHttpPushUriTagFilter(HttpServerConfig httpServerConfig) { + if (binderConfiguration.isServerEnabled()) { + return maximumAllowableUriTagsFilter(binderConfiguration.getHttpServerPushName(), + httpServerConfig.maxUriTags); + } + return null; + } + + @Singleton + @Produces + public MeterFilter metricsHttpWebSocketsUriTagFilter(HttpServerConfig httpServerConfig) { + if (binderConfiguration.isServerEnabled()) { + return maximumAllowableUriTagsFilter(binderConfiguration.getHttpServerWebSocketConnectionsName(), + httpServerConfig.maxUriTags); + } + return null; + } + + MeterFilter maximumAllowableUriTagsFilter(final String metricName, final int maximumTagValues) { + MeterFilter denyFilter = new OnlyOnceLoggingDenyMeterFilter(() -> String + .format("Reached the maximum number (%s) of URI tags for '%s'. Are you using path parameters?", + maximumTagValues, metricName)); + + return MeterFilter.maximumAllowableTags(metricName, "uri", maximumTagValues, + denyFilter); + } +} diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpRequestMetric.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpRequestMetric.java deleted file mode 100644 index a9dc1affd1aba..0000000000000 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpRequestMetric.java +++ /dev/null @@ -1,199 +0,0 @@ -package io.quarkus.micrometer.runtime.binder; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Pattern; - -import org.jboss.logging.Logger; - -import io.micrometer.core.instrument.Tags; -import io.micrometer.core.instrument.Timer; -import io.vertx.core.spi.observability.HttpResponse; -import io.vertx.ext.web.RoutingContext; - -public class HttpRequestMetric { - static final Logger log = Logger.getLogger(HttpRequestMetric.class); - - public static final String HTTP_REQUEST_PATH = "HTTP_REQUEST_PATH"; - public static final String HTTP_REQUEST_PATH_MATCHED = "HTTP_REQUEST_MATCHED_PATH"; - public static final Pattern VERTX_ROUTE_PARAM = Pattern.compile("^:(.*)$"); - - /** Cache of vert.x resolved paths: /item/:id --> /item/{id} */ - final static ConcurrentHashMap templatePath = new ConcurrentHashMap<>(); - - volatile RoutingContext routingContext; - - /** Do not measure requests until/unless a uri path is set */ - final boolean measure; - - /** URI path used as a tag value for non-error requests */ - final String path; - - /** True IFF the path was revised by a matcher expression */ - final boolean pathMatched; - - /** Store the sample used to measure the request */ - Timer.Sample sample; - - /** - * Store the tags associated with the request (change Micrometer 1.6.0). - * Default is empty, value assigned @ requestBegin - */ - Tags tags = Tags.empty(); - - /** Response associated with the request (Vert.x 4.0) */ - HttpResponse response; - - /** - * Extract the path out of the uri. Return null if the path should be - * ignored. - */ - public HttpRequestMetric(Map matchPattern, List ignorePatterns, - String uri) { - if (uri == null) { - this.measure = false; - this.pathMatched = false; - this.path = null; - return; - } - - boolean matched = false; - String workingPath = extractPath(uri); - String finalPath = workingPath; - if ("/".equals(workingPath) || workingPath.isEmpty()) { - finalPath = "/"; - } else { - // Label value consistency: result should begin with a '/' and should not end with one - workingPath = HttpMetricsCommon.MULTIPLE_SLASH_PATTERN.matcher('/' + workingPath).replaceAll("/"); - workingPath = HttpMetricsCommon.TRAILING_SLASH_PATTERN.matcher(workingPath).replaceAll(""); - if (workingPath.isEmpty()) { - finalPath = "/"; - } else { - finalPath = workingPath; - // test path against configured patterns (whole path) - for (Map.Entry mp : matchPattern.entrySet()) { - if (mp.getKey().matcher(workingPath).matches()) { - finalPath = mp.getValue(); - matched = true; - break; - } - } - } - } - this.path = finalPath; - this.pathMatched = matched; - - // Compare path against "ignore this path" patterns - for (Pattern p : ignorePatterns) { - if (p.matcher(this.path).matches()) { - log.debugf("Path %s ignored; matches pattern %s", uri, p.pattern()); - this.measure = false; - return; - } - } - this.measure = true; - } - - public Timer.Sample getSample() { - return sample; - } - - public void setSample(Timer.Sample sample) { - this.sample = sample; - } - - public Tags getTags() { - return tags; - } - - public void setTags(Tags tags) { - this.tags = tags; - } - - public String getPath() { - return path; - } - - public boolean isMeasure() { - return measure; - } - - public boolean isPathMatched() { - return pathMatched; - } - - private static String extractPath(String uri) { - if (uri.isEmpty()) { - return uri; - } - int i; - if (uri.charAt(0) == '/') { - i = 0; - } else { - i = uri.indexOf("://"); - if (i == -1) { - i = 0; - } else { - i = uri.indexOf('/', i + 3); - if (i == -1) { - // contains no / - return "/"; - } - } - } - - int queryStart = uri.indexOf('?', i); - if (queryStart == -1) { - queryStart = uri.length(); - } - return uri.substring(i, queryStart); - } - - public String getHttpRequestPath() { - // Vertx binder configuration, see VertxMetricsTags - if (pathMatched) { - return path; - } - if (routingContext != null) { - // JAX-RS or Servlet container filter - String rcPath = routingContext.get(HTTP_REQUEST_PATH); - if (rcPath != null) { - return rcPath; - } - // vertx-web or reactive route - String matchedPath = routingContext.currentRoute().getPath(); - if (matchedPath != null) { - if (matchedPath.contains(":")) { - // Convert /item/:id to /item/{id} and save it for next time - matchedPath = templatePath.computeIfAbsent(matchedPath, k -> { - String segments[] = k.split("/"); - for (int i = 0; i < segments.length; i++) { - segments[i] = VERTX_ROUTE_PARAM.matcher(segments[i]).replaceAll("{$1}"); - } - return String.join("/", segments); - }); - } - return matchedPath; - } - } - return path; - } - - public RoutingContext getRoutingContext() { - return routingContext; - } - - public void setRoutingContext(RoutingContext routingContext) { - this.routingContext = routingContext; - } - - @Override - public String toString() { - return "HttpRequestMetric{path=" + path - + ",pathMatched=" + pathMatched - + ",measure=" + measure - + ",tags=" + tags - + '}'; - } -} diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RequestMetricInfo.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RequestMetricInfo.java new file mode 100644 index 0000000000000..089c8bfbae851 --- /dev/null +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RequestMetricInfo.java @@ -0,0 +1,96 @@ +package io.quarkus.micrometer.runtime.binder; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.jboss.logging.Logger; + +import io.micrometer.core.instrument.Timer; + +public class RequestMetricInfo { + static final Logger log = Logger.getLogger(RequestMetricInfo.class); + + public static final Pattern TRAILING_SLASH_PATTERN = Pattern.compile("/$"); + public static final Pattern MULTIPLE_SLASH_PATTERN = Pattern.compile("//+"); + public static final String ROOT = "/"; + + public static final String HTTP_REQUEST_PATH = "HTTP_REQUEST_PATH"; + + /** Store the sample used to measure the request */ + protected Timer.Sample sample; + + public RequestMetricInfo setSample(Timer.Sample sample) { + this.sample = sample; + return this; + } + + public Timer.Sample getSample() { + return sample; + } + + /** + * Normalize and filter request path against match patterns + * + * @param uri Uri for request + * @param ignorePatterns + * @param matchPatterns + * @return final uri for tag, or null to skip measurement + */ + protected String getNormalizedUriPath(Map matchPatterns, List ignorePatterns, String uri) { + // Normalize path + String path = normalizePath(uri); + if (path.length() > 1) { + String origPath = path; + // Look for configured matches, then inferred templates + path = applyMatchPatterns(origPath, matchPatterns); + if (path.equals(origPath)) { + path = normalizePath(applyTemplateMatching(origPath)); + } + } + return filterIgnored(path, ignorePatterns); + } + + /** Subclassess should override with appropriate mechanisms for finding templated urls */ + protected String applyTemplateMatching(String path) { + return path; + } + + static String applyMatchPatterns(String path, Map matchPatterns) { + if (!matchPatterns.isEmpty()) { + for (Map.Entry mp : matchPatterns.entrySet()) { + if (mp.getKey().matcher(path).matches()) { + log.debugf("Path %s matched pattern %s, using %s", path, mp.getKey(), mp.getValue()); + return mp.getValue(); + } + } + } + return path; + } + + /** Return path or null if it should be ignored */ + static String filterIgnored(String path, List ignorePatterns) { + if (!ignorePatterns.isEmpty()) { + for (Pattern p : ignorePatterns) { + if (p.matcher(path).matches()) { + log.debugf("Path %s ignored; matches pattern %s", path, p.pattern()); + return null; + } + } + } + return path; + } + + protected static String normalizePath(String uri) { + if (uri == null || uri.isEmpty() || ROOT.equals(uri)) { + return ROOT; + } + // Label value consistency: result should begin with a '/' and should not end with one + String workingPath = MULTIPLE_SLASH_PATTERN.matcher('/' + uri).replaceAll("/"); + workingPath = TRAILING_SLASH_PATTERN.matcher(workingPath).replaceAll(""); + if (workingPath.isEmpty()) { + return ROOT; + } + return workingPath; + } +} diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetrics.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetrics.java deleted file mode 100644 index 4e36d76d2da85..0000000000000 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetrics.java +++ /dev/null @@ -1,109 +0,0 @@ -package io.quarkus.micrometer.runtime.binder; - -import java.io.IOException; - -import javax.ws.rs.client.ClientRequestContext; -import javax.ws.rs.client.ClientRequestFilter; -import javax.ws.rs.client.ClientResponseContext; -import javax.ws.rs.client.ClientResponseFilter; - -import org.eclipse.microprofile.rest.client.RestClientBuilder; -import org.eclipse.microprofile.rest.client.spi.RestClientListener; - -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Metrics; -import io.micrometer.core.instrument.Tag; -import io.micrometer.core.instrument.Tags; -import io.micrometer.core.instrument.Timer; -import io.quarkus.arc.Arc; - -/** - * This is initialized via ServiceFactory (static/non-CDI initialization) - */ -public class RestClientMetrics implements RestClientListener { - private static final String HTTP_CLIENT_METRIC_NAME = "http.client.requests"; - private final static String REQUEST_METRIC_PROPERTY = "restClientMetrics"; - - final MeterRegistry registry = Metrics.globalRegistry; - MetricsClientRequestFilter clientRequestFilter; - MetricsClientResponseFilter clientResponseFilter; - - @Override - public void onNewClient(Class serviceInterface, RestClientBuilder builder) { - builder.register(getClientRequestFilter()); - builder.register(getClientResponseFilter()); - } - - // Lazy init: multiple instances aren't harmful, synchronization not necessary - MetricsClientRequestFilter getClientRequestFilter() { - MetricsClientRequestFilter clientFilter = this.clientRequestFilter; - if (clientFilter == null) { - HttpBinderConfiguration httpMetricsConfig = Arc.container().instance(HttpBinderConfiguration.class).get(); - clientFilter = this.clientRequestFilter = new MetricsClientRequestFilter(httpMetricsConfig); - } - return clientFilter; - } - - // Lazy init: multiple instances aren't harmful, synchronization not necessary - MetricsClientResponseFilter getClientResponseFilter() { - MetricsClientResponseFilter clientFilter = this.clientResponseFilter; - if (clientFilter == null) { - clientFilter = this.clientResponseFilter = new MetricsClientResponseFilter(); - } - return clientFilter; - } - - class MetricsClientRequestFilter implements ClientRequestFilter { - HttpBinderConfiguration binderConfiguration; - - MetricsClientRequestFilter(HttpBinderConfiguration binderConfiguration) { - this.binderConfiguration = binderConfiguration; - } - - @Override - public void filter(ClientRequestContext requestContext) throws IOException { - HttpRequestMetric requestMetric = new HttpRequestMetric( - binderConfiguration.getClientMatchPatterns(), - binderConfiguration.getClientIgnorePatterns(), - requestContext.getUri().getPath()); - - if (requestMetric.isMeasure()) { - requestMetric.setSample(Timer.start(registry)); - requestContext.setProperty(REQUEST_METRIC_PROPERTY, requestMetric); - } - } - } - - class MetricsClientResponseFilter implements ClientResponseFilter { - @Override - public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException { - HttpRequestMetric requestMetric = getRequestMetric(requestContext); - if (requestMetric != null) { - Timer.Sample sample = requestMetric.sample; - String requestPath = requestMetric.getHttpRequestPath(); - int statusCode = responseContext.getStatus(); - Timer.Builder builder = Timer.builder(HTTP_CLIENT_METRIC_NAME) - .tags(Tags.of( - HttpMetricsCommon.method(requestContext.getMethod()), - HttpMetricsCommon.uri(requestPath, statusCode), - HttpMetricsCommon.outcome(statusCode), - HttpMetricsCommon.status(statusCode), - clientName(requestContext))); - - sample.stop(builder.register(registry)); - } - } - - private HttpRequestMetric getRequestMetric(ClientRequestContext requestContext) { - return (HttpRequestMetric) requestContext.getProperty(REQUEST_METRIC_PROPERTY); - } - - private Tag clientName(ClientRequestContext requestContext) { - String host = requestContext.getUri().getHost(); - if (host == null) { - host = "none"; - } - return Tag.of("clientName", host); - } - } -} diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetricsListener.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetricsListener.java new file mode 100644 index 0000000000000..2dbe4104fa443 --- /dev/null +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetricsListener.java @@ -0,0 +1,120 @@ +package io.quarkus.micrometer.runtime.binder; + +import java.io.IOException; + +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import javax.ws.rs.client.ClientResponseContext; +import javax.ws.rs.client.ClientResponseFilter; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.eclipse.microprofile.rest.client.spi.RestClientListener; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.Timer; +import io.quarkus.arc.Arc; + +/** + * This is initialized via ServiceFactory (static/non-CDI initialization) + */ +public class RestClientMetricsListener implements RestClientListener { + + private final static String REQUEST_METRIC_PROPERTY = "restClientMetrics"; + + final MeterRegistry registry = Metrics.globalRegistry; + boolean initialized = false; + boolean clientMetricsEnabled = false; + + HttpBinderConfiguration httpMetricsConfig; + MetricsClientRequestFilter clientRequestFilter; + MetricsClientResponseFilter clientResponseFilter; + + @Override + public void onNewClient(Class serviceInterface, RestClientBuilder builder) { + if (prepClientMetrics()) { + builder.register(this.clientRequestFilter); + builder.register(this.clientResponseFilter); + } + } + + boolean prepClientMetrics() { + boolean clientMetricsEnabled = this.clientMetricsEnabled; + if (!this.initialized) { + this.httpMetricsConfig = Arc.container().instance(HttpBinderConfiguration.class).get(); + clientMetricsEnabled = httpMetricsConfig.isClientEnabled(); + if (clientMetricsEnabled) { + this.clientRequestFilter = new MetricsClientRequestFilter(httpMetricsConfig); + this.clientResponseFilter = new MetricsClientResponseFilter(); + } + this.clientMetricsEnabled = clientMetricsEnabled; + this.initialized = true; + } + return clientMetricsEnabled; + } + + class MetricsClientRequestFilter implements ClientRequestFilter { + HttpBinderConfiguration binderConfiguration; + + MetricsClientRequestFilter(HttpBinderConfiguration binderConfiguration) { + this.binderConfiguration = binderConfiguration; + } + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + RequestMetricInfo requestMetric = new RestClientMetricInfo(requestContext); + requestMetric.setSample(Timer.start(registry)); + requestContext.setProperty(REQUEST_METRIC_PROPERTY, requestMetric); + } + } + + class MetricsClientResponseFilter implements ClientResponseFilter { + @Override + public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException { + RequestMetricInfo requestMetric = getRequestMetric(requestContext); + if (requestMetric != null) { + String requestPath = requestMetric.getNormalizedUriPath( + httpMetricsConfig.clientMatchPatterns, + httpMetricsConfig.clientIgnorePatterns, + requestContext.getUri().getPath()); + if (requestPath != null) { + Timer.Sample sample = requestMetric.getSample(); + int statusCode = responseContext.getStatus(); + + Timer.Builder builder = Timer.builder(httpMetricsConfig.getHttpClientRequestsName()) + .tags(Tags.of( + HttpCommonTags.method(requestContext.getMethod()), + HttpCommonTags.uri(requestPath, statusCode), + HttpCommonTags.outcome(statusCode), + HttpCommonTags.status(statusCode), + clientName(requestContext))); + + sample.stop(builder.register(registry)); + } + } + } + + private RequestMetricInfo getRequestMetric(ClientRequestContext requestContext) { + return (RequestMetricInfo) requestContext.getProperty(REQUEST_METRIC_PROPERTY); + } + + private Tag clientName(ClientRequestContext requestContext) { + String host = requestContext.getUri().getHost(); + if (host == null) { + host = "none"; + } + return Tag.of("clientName", host); + } + } + + class RestClientMetricInfo extends RequestMetricInfo { + ClientRequestContext requestContext; + + RestClientMetricInfo(ClientRequestContext requestContext) { + super(); + this.requestContext = requestContext; + } + } +} diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/HttpRequestMetric.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/HttpRequestMetric.java new file mode 100644 index 0000000000000..9f8319ba05898 --- /dev/null +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/HttpRequestMetric.java @@ -0,0 +1,82 @@ +package io.quarkus.micrometer.runtime.binder.vertx; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +import io.quarkus.micrometer.runtime.binder.RequestMetricInfo; +import io.vertx.core.http.impl.HttpServerRequestInternal; +import io.vertx.core.spi.observability.HttpRequest; +import io.vertx.ext.web.RoutingContext; + +public class HttpRequestMetric extends RequestMetricInfo { + public static final Pattern VERTX_ROUTE_PARAM = Pattern.compile("^:(.*)$"); + + /** Cache of vert.x resolved paths: /item/:id --> /item/{id} */ + final static ConcurrentHashMap vertxWebToUriTemplate = new ConcurrentHashMap<>(); + + protected HttpServerRequestInternal request; + protected String initialPath; + protected String templatePath; + protected String currentRoutePath; + + public HttpRequestMetric(String uri) { + this.initialPath = uri; + } + + public HttpRequestMetric(HttpRequest request) { + this.request = (HttpServerRequestInternal) request; + this.initialPath = this.request.path(); + } + + public String getNormalizedUriPath(Map matchPatterns, List ignorePatterns) { + return super.getNormalizedUriPath(matchPatterns, ignorePatterns, initialPath); + } + + public String applyTemplateMatching(String path) { + // JAX-RS or Servlet container filter + if (templatePath != null) { + return normalizePath(templatePath); + } + + // vertx-web or reactive route: is it templated? + if (currentRoutePath != null && currentRoutePath.contains(":")) { + // Convert /item/:id to /item/{id} and save it for next time + return vertxWebToUriTemplate.computeIfAbsent(currentRoutePath, k -> { + String segments[] = k.split("/"); + for (int i = 0; i < segments.length; i++) { + segments[i] = VERTX_ROUTE_PARAM.matcher(segments[i]).replaceAll("{$1}"); + } + return normalizePath(String.join("/", segments)); + }); + } + + return path; + } + + public HttpServerRequestInternal request() { + return request; + } + + public void setTemplatePath(String path) { + this.templatePath = path; + } + + public void appendCurrentRoutePath(String path) { + if (path != null && !path.isEmpty()) { + this.currentRoutePath = path; + } + } + + public static HttpRequestMetric getRequestMetric(RoutingContext context) { + HttpServerRequestInternal internalRequest = (HttpServerRequestInternal) context.request(); + return (HttpRequestMetric) internalRequest.metric(); + } + + @Override + public String toString() { + return "HttpRequestMetric [initialPath=" + initialPath + ", currentRoutePath=" + currentRoutePath + + ", templatePath=" + templatePath + ", request=" + 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 b30a723e4bd82..27b937c5c821c 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 @@ -12,10 +12,7 @@ import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.binder.http.Outcome; import io.quarkus.micrometer.runtime.binder.HttpBinderConfiguration; -import io.quarkus.micrometer.runtime.binder.HttpMetricsCommon; -import io.quarkus.micrometer.runtime.binder.HttpRequestMetric; -import io.vertx.core.Context; -import io.vertx.core.Vertx; +import io.quarkus.micrometer.runtime.binder.HttpCommonTags; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.ServerWebSocket; import io.vertx.core.spi.metrics.HttpServerMetrics; @@ -44,43 +41,14 @@ public class VertxHttpServerMetrics extends VertxTcpMetrics VertxHttpServerMetrics(MeterRegistry registry, HttpBinderConfiguration config) { super(registry, "http.server"); - nameWebsocketConnections = "http.server.websocket.connections"; - nameHttpServerPush = "http.server.push"; - nameHttpServerRequests = "http.server.requests"; + nameWebsocketConnections = config.getHttpServerWebSocketConnectionsName(); + nameHttpServerPush = config.getHttpServerPushName(); + nameHttpServerRequests = config.getHttpServerRequestsName(); ignorePatterns = config.getServerIgnorePatterns(); matchPatterns = config.getServerMatchPatterns(); } - /** - * Stash the RequestMetric in the Vertx Context - * - * @param context Vertx context to store RequestMetric in - * @param requestMetric - * @see VertxMeterFilter - */ - public static void setRequestMetric(Context context, HttpRequestMetric requestMetric) { - if (context != null) { - context.put(METRICS_CONTEXT, requestMetric); - } - } - - /** - * Retrieve and remove the RequestMetric from the Vertx Context - * - * @param context - * @return the RequestMetricContext stored in the Vertx Context, or null - * @see VertxMeterFilter - */ - public static HttpRequestMetric retrieveRequestMetric(Context context) { - if (context != null) { - HttpRequestMetric requestMetric = context.get(METRICS_CONTEXT); - context.remove(METRICS_CONTEXT); - return requestMetric; - } - return null; - } - /** * Called when an http server response is pushed. * @@ -93,19 +61,26 @@ public static HttpRequestMetric retrieveRequestMetric(Context context) { @Override public HttpRequestMetric responsePushed(Map socketMetric, HttpMethod method, String uri, HttpResponse response) { - HttpRequestMetric requestMetric = new HttpRequestMetric(matchPatterns, ignorePatterns, uri); - if (requestMetric.isMeasure()) { + HttpRequestMetric requestMetric = new HttpRequestMetric(uri); + String path = requestMetric.getNormalizedUriPath(matchPatterns, ignorePatterns); + if (path != null) { registry.counter(nameHttpServerPush, Tags.of( - HttpMetricsCommon.uri(requestMetric.getPath(), response.statusCode()), VertxMetricsTags.method(method), + HttpCommonTags.uri(path, response.statusCode()), VertxMetricsTags.outcome(response), - HttpMetricsCommon.status(response.statusCode()))) + HttpCommonTags.status(response.statusCode()))) .increment(); } - log.debugf("responsePushed %s: %s, %s", uri, socketMetric, requestMetric); + log.debugf("responsePushed %s, %s", socketMetric, requestMetric); return requestMetric; } + @Override + public void requestRouted(HttpRequestMetric requestMetric, String route) { + log.debugf("requestRouted %s %s", route, requestMetric); + requestMetric.appendCurrentRoutePath(route); + } + /** * Called when an http server request begins. Vert.x will invoke * {@link #responseEnd} when the response has ended or {@link #requestReset} if @@ -117,17 +92,10 @@ public HttpRequestMetric responsePushed(Map socketMetric, HttpMe */ @Override public HttpRequestMetric requestBegin(Map socketMetric, HttpRequest request) { - HttpRequestMetric requestMetric = new HttpRequestMetric(matchPatterns, ignorePatterns, request.uri()); - setRequestMetric(Vertx.currentContext(), requestMetric); - - if (requestMetric.isMeasure()) { - // If we're measuring this request, create/remember the sample - requestMetric.setSample(Timer.start(registry)); - requestMetric.setTags(Tags.of(VertxMetricsTags.method(request.method()))); - - log.debugf("requestBegin %s: %s, %s", requestMetric.getPath(), socketMetric, requestMetric); - } + HttpRequestMetric requestMetric = new HttpRequestMetric(request); + requestMetric.setSample(Timer.start(registry)); + log.debugf("requestBegin %s, %s", socketMetric, requestMetric); return requestMetric; } @@ -139,40 +107,42 @@ public HttpRequestMetric requestBegin(Map socketMetric, HttpRequ */ @Override public void requestReset(HttpRequestMetric requestMetric) { - log.debugf("requestReset: %s", requestMetric); - Timer.Sample sample = getRequestSample(requestMetric); - if (sample != null) { - String requestPath = getServerRequestPath(requestMetric); + log.debugf("requestReset %s", requestMetric); + + String path = requestMetric.getNormalizedUriPath(matchPatterns, ignorePatterns); + if (path != null) { + Timer.Sample sample = requestMetric.getSample(); Timer.Builder builder = Timer.builder(nameHttpServerRequests) - .tags(requestMetric.getTags()) .tags(Tags.of( - HttpMetricsCommon.uri(requestPath, 0), + VertxMetricsTags.method(requestMetric.request().method()), + HttpCommonTags.uri(path, 0), Outcome.CLIENT_ERROR.asTag(), - HttpMetricsCommon.STATUS_RESET)); + HttpCommonTags.STATUS_RESET)); + sample.stop(builder.register(registry)); } } /** * Called when an http server response has ended. - * Must save response in the httpRequest metric @ responseBegin * * @param requestMetric a RequestMetricContext or null + * @param response the http server response * @param bytesWritten bytes written */ @Override public void responseEnd(HttpRequestMetric requestMetric, HttpResponse response, long bytesWritten) { - log.debugf("responseEnd: %s, %s", requestMetric, response); + log.debugf("responseEnd %s, %s", response, requestMetric); - Timer.Sample sample = getRequestSample(requestMetric); - if (response != null && sample != null) { - String requestPath = getServerRequestPath(requestMetric); + String path = requestMetric.getNormalizedUriPath(matchPatterns, ignorePatterns); + if (path != null) { + Timer.Sample sample = requestMetric.getSample(); Timer.Builder builder = Timer.builder(nameHttpServerRequests) - .tags(requestMetric.getTags()) .tags(Tags.of( - HttpMetricsCommon.uri(requestPath, response.statusCode()), + VertxMetricsTags.method(requestMetric.request().method()), + HttpCommonTags.uri(path, response.statusCode()), VertxMetricsTags.outcome(response), - HttpMetricsCommon.status(response.statusCode()))); + HttpCommonTags.status(response.statusCode()))); sample.stop(builder.register(registry)); } @@ -189,11 +159,12 @@ public void responseEnd(HttpRequestMetric requestMetric, HttpResponse response, @Override public LongTaskTimer.Sample connected(Map socketMetric, HttpRequestMetric requestMetric, ServerWebSocket serverWebSocket) { - log.debugf("websocket connected: %s, %s, %s", socketMetric, requestMetric, serverWebSocket); - String path = getServerRequestPath(requestMetric); + log.debugf("websocket connected %s, %s, %s", socketMetric, serverWebSocket, requestMetric); + + String path = requestMetric.getNormalizedUriPath(matchPatterns, ignorePatterns); if (path != null) { return LongTaskTimer.builder(nameWebsocketConnections) - .tags(Tags.of(HttpMetricsCommon.uri(path, 0))) + .tags(Tags.of(HttpCommonTags.uri(path, 0))) .register(registry) .start(); } @@ -207,23 +178,9 @@ public LongTaskTimer.Sample connected(Map socketMetric, HttpRequ */ @Override public void disconnected(LongTaskTimer.Sample websocketMetric) { - log.debugf("websocket disconnected: %s", websocketMetric); + log.debugf("websocket disconnected %s", websocketMetric); if (websocketMetric != null) { websocketMetric.stop(); } } - - private Timer.Sample getRequestSample(HttpRequestMetric metricsContext) { - if (metricsContext == null) { - return null; - } - return metricsContext.getSample(); - } - - private String getServerRequestPath(HttpRequestMetric metricsContext) { - if (metricsContext == null) { - return null; - } - return metricsContext.getHttpRequestPath(); - } } diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterBinderContainerFilterUtil.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterBinderContainerFilterUtil.java index 9223378b946f8..67c682e988de8 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterBinderContainerFilterUtil.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterBinderContainerFilterUtil.java @@ -6,9 +6,6 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.UriInfo; -import org.jboss.logging.Logger; - -import io.quarkus.micrometer.runtime.binder.HttpRequestMetric; import io.vertx.ext.web.RoutingContext; final class VertxMeterBinderContainerFilterUtil { @@ -16,16 +13,9 @@ final class VertxMeterBinderContainerFilterUtil { private VertxMeterBinderContainerFilterUtil() { } - private static final Logger log = Logger.getLogger(VertxMeterBinderRestEasyContainerFilter.class); - static void doFilter(RoutingContext routingContext, UriInfo info) { - // bail early if we have no routing context, or if path munging isn't necessary - if (routingContext == null || routingContext.get(HttpRequestMetric.HTTP_REQUEST_PATH_MATCHED) != null) { - return; - } String path = info.getPath(); - MultivaluedMap pathParameters = info.getPathParameters(); if (!pathParameters.isEmpty()) { // Replace parameter values in the URI with {key}: /items/123 -> /items/{id} @@ -34,9 +24,8 @@ static void doFilter(RoutingContext routingContext, UriInfo info) { path = path.replace(value, "{" + entry.getKey() + "}"); } } - log.debugf("Saving parameterized path %s in %s", path, routingContext); - } - routingContext.put(HttpRequestMetric.HTTP_REQUEST_PATH, path); + HttpRequestMetric.getRequestMetric(routingContext).setTemplatePath(path); + } } } diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterBinderRestEasyContainerFilter.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterBinderRestEasyContainerFilter.java index 2137332fb919a..66273d46ddd37 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterBinderRestEasyContainerFilter.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterBinderRestEasyContainerFilter.java @@ -1,16 +1,19 @@ package io.quarkus.micrometer.runtime.binder.vertx; -import javax.enterprise.inject.spi.CDI; +import javax.inject.Inject; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; -import io.quarkus.vertx.http.runtime.CurrentVertxRequest; +import io.vertx.ext.web.RoutingContext; public class VertxMeterBinderRestEasyContainerFilter implements ContainerRequestFilter { + @Inject + RoutingContext routingContext; + @Override public void filter(final ContainerRequestContext requestContext) { - VertxMeterBinderContainerFilterUtil.doFilter(CDI.current().select(CurrentVertxRequest.class).get().getCurrent(), - requestContext.getUriInfo()); + + VertxMeterBinderContainerFilterUtil.doFilter(routingContext, requestContext.getUriInfo()); } } diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterFilter.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterFilter.java deleted file mode 100644 index f73553ccb3236..0000000000000 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterFilter.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.quarkus.micrometer.runtime.binder.vertx; - -import io.quarkus.micrometer.runtime.binder.HttpRequestMetric; -import io.vertx.core.Context; -import io.vertx.core.Handler; -import io.vertx.core.Vertx; -import io.vertx.ext.web.RoutingContext; - -/** - * High priority handler that sets the Vertx RouterContext - * attribute on the active RequestMetric. - * To quote Stuart, "YUCK". - * Reference: https://github.com/eclipse-vertx/vert.x/issues/3579 - */ -public class VertxMeterFilter implements Handler { - @Override - public void handle(RoutingContext routingContext) { - final Context context = Vertx.currentContext(); - HttpRequestMetric requestMetric = VertxHttpServerMetrics.retrieveRequestMetric(context); - - if (requestMetric != null) { - requestMetric.setRoutingContext(routingContext); - - // remember if we can skip path munging --> @see VertxMeterBinderRestEasyContainerFilter - if (requestMetric.isPathMatched()) { - routingContext.put(HttpRequestMetric.HTTP_REQUEST_PATH_MATCHED, true); - } - } - routingContext.next(); - } -} diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMetricsTags.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMetricsTags.java index be94579fb3650..0cff2413f7d2b 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMetricsTags.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMetricsTags.java @@ -4,7 +4,7 @@ import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.binder.http.Outcome; -import io.quarkus.micrometer.runtime.binder.HttpMetricsCommon; +import io.quarkus.micrometer.runtime.binder.HttpCommonTags; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.spi.observability.HttpResponse; @@ -20,7 +20,7 @@ public class VertxMetricsTags { * @return the method tag whose value is a capitalized method (e.g. GET). */ public static Tag method(HttpMethod method) { - return (method != null) ? Tag.of("method", method.toString()) : HttpMetricsCommon.METHOD_UNKNOWN; + return (method != null) ? Tag.of("method", method.toString()) : HttpCommonTags.METHOD_UNKNOWN; } /** 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 04be00c380ece..0dd3c08be3c59 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 @@ -24,7 +24,7 @@ public class HttpClientConfig { * `/item/[0-9]+=/item/custom` is specified in this list, * a request to a matching path (`/item/123`) will use the specified * replacement value (`/item/custom`) as the value for the uri label. - * Note that backslashes must be double escaped as {@code \\\\}. + * Note that backslashes must be double escaped as `\\\\`. * * @asciidoclet */ @@ -37,4 +37,12 @@ public class HttpClientConfig { */ @ConfigItem public Optional> ignorePatterns = Optional.empty(); + + /** + * 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 + * filter. + */ + @ConfigItem(defaultValue = "100") + public int maxUriTags; } 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 1c61560307d6f..51ab1e8436428 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 @@ -24,7 +24,7 @@ public class HttpServerConfig { * `/item/[0-9]+=/item/custom` is specified in this list, * a request to a matching path (`/item/123`) will use the specified * replacement value (`/item/custom`) as the value for the uri label. - * Note that backslashes must be double escaped as {@code \\\\}. + * Note that backslashes must be double escaped as `\\\\`. * * @asciidoclet */ @@ -38,6 +38,14 @@ public class HttpServerConfig { @ConfigItem public Optional> ignorePatterns = Optional.empty(); + /** + * 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 + * filter. + */ + @ConfigItem(defaultValue = "100") + public int maxUriTags; + public void mergeDeprecatedConfig(VertxConfig config) { if (!ignorePatterns.isPresent()) { ignorePatterns = config.ignorePatterns; diff --git a/extensions/micrometer/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener b/extensions/micrometer/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener index 6abda62821d09..d93a809a156ce 100644 --- a/extensions/micrometer/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener +++ b/extensions/micrometer/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener @@ -1 +1 @@ -io.quarkus.micrometer.runtime.binder.RestClientMetrics +io.quarkus.micrometer.runtime.binder.RestClientMetricsListener 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 new file mode 100644 index 0000000000000..2474a9f228c6e --- /dev/null +++ b/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/HttpCommonTagsTest.java @@ -0,0 +1,37 @@ +package io.quarkus.micrometer.runtime.binder; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.micrometer.core.instrument.Tag; + +/** + * Test tag creation + * Disabled on Java 8 because of Mocks + */ +public class HttpCommonTagsTest { + + @Test + public void testStatus() { + Assertions.assertEquals(Tag.of("status", "200"), HttpCommonTags.status(200)); + Assertions.assertEquals(Tag.of("status", "301"), HttpCommonTags.status(301)); + Assertions.assertEquals(Tag.of("status", "304"), HttpCommonTags.status(304)); + Assertions.assertEquals(Tag.of("status", "404"), HttpCommonTags.status(404)); + } + + @Test + public void testUriRedirect() { + Assertions.assertEquals(HttpCommonTags.URI_REDIRECTION, HttpCommonTags.uri("/moved", 301)); + Assertions.assertEquals(HttpCommonTags.URI_REDIRECTION, HttpCommonTags.uri("/moved", 302)); + Assertions.assertEquals(HttpCommonTags.URI_REDIRECTION, HttpCommonTags.uri("/moved", 304)); + } + + @Test + public void testUriDefaults() { + Assertions.assertEquals(HttpCommonTags.URI_ROOT, HttpCommonTags.uri("/", 200)); + Assertions.assertEquals(Tag.of("uri", "/known/ok"), HttpCommonTags.uri("/known/ok", 200)); + Assertions.assertEquals(HttpCommonTags.URI_NOT_FOUND, HttpCommonTags.uri("/invalid", 404)); + Assertions.assertEquals(Tag.of("uri", "/known/bad/request"), HttpCommonTags.uri("/known/bad/request", 400)); + Assertions.assertEquals(Tag.of("uri", "/known/server/error"), HttpCommonTags.uri("/known/server/error", 500)); + } +} diff --git a/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/HttpMetricsCommonTest.java b/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/HttpMetricsCommonTest.java deleted file mode 100644 index b9a8540dfdc41..0000000000000 --- a/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/HttpMetricsCommonTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.quarkus.micrometer.runtime.binder; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import io.micrometer.core.instrument.Tag; - -/** - * Test tag creation - * Disabled on Java 8 because of Mocks - */ -public class HttpMetricsCommonTest { - - @Test - public void testStatus() { - Assertions.assertEquals(Tag.of("status", "200"), HttpMetricsCommon.status(200)); - Assertions.assertEquals(Tag.of("status", "301"), HttpMetricsCommon.status(301)); - Assertions.assertEquals(Tag.of("status", "304"), HttpMetricsCommon.status(304)); - Assertions.assertEquals(Tag.of("status", "404"), HttpMetricsCommon.status(404)); - } - - @Test - public void testUriRedirect() { - Assertions.assertEquals(HttpMetricsCommon.URI_REDIRECTION, HttpMetricsCommon.uri("/moved", 301)); - Assertions.assertEquals(HttpMetricsCommon.URI_REDIRECTION, HttpMetricsCommon.uri("/moved", 302)); - Assertions.assertEquals(HttpMetricsCommon.URI_REDIRECTION, HttpMetricsCommon.uri("/moved", 304)); - } - - @Test - public void testUriDefaults() { - Assertions.assertEquals(HttpMetricsCommon.URI_ROOT, HttpMetricsCommon.uri("/", 200)); - Assertions.assertEquals(Tag.of("uri", "/known/ok"), HttpMetricsCommon.uri("/known/ok", 200)); - Assertions.assertEquals(HttpMetricsCommon.URI_NOT_FOUND, HttpMetricsCommon.uri("/invalid", 404)); - Assertions.assertEquals(Tag.of("uri", "/known/bad/request"), HttpMetricsCommon.uri("/known/bad/request", 400)); - Assertions.assertEquals(Tag.of("uri", "/known/server/error"), HttpMetricsCommon.uri("/known/server/error", 500)); - } -} diff --git a/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/HttpRequestMetricTest.java b/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/HttpRequestMetricTest.java deleted file mode 100644 index 0fb7436f0e06e..0000000000000 --- a/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/HttpRequestMetricTest.java +++ /dev/null @@ -1,140 +0,0 @@ -package io.quarkus.micrometer.runtime.binder; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledOnJre; -import org.junit.jupiter.api.condition.JRE; -import org.mockito.Mockito; - -import io.vertx.ext.web.Route; -import io.vertx.ext.web.RoutingContext; - -/** - * Disabled on Java 8 because of Mocks - */ -@DisabledOnJre(JRE.JAVA_8) -public class HttpRequestMetricTest { - - final List NO_IGNORE_PATTERNS = Collections.emptyList(); - final List ignorePatterns = Arrays.asList(Pattern.compile("/ignore.*")); - - final Map NO_MATCH_PATTERNS = Collections.emptyMap(); - - @Test - public void testReturnPathFromHttpRequestPath() { - HttpRequestMetric requestMetric = new HttpRequestMetric(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, "/"); - requestMetric.routingContext = Mockito.mock(RoutingContext.class); - - Mockito.when(requestMetric.routingContext.get(HttpRequestMetric.HTTP_REQUEST_PATH)) - .thenReturn("/item/{id}"); - - Assertions.assertEquals("/item/{id}", requestMetric.getHttpRequestPath()); - } - - @Test - public void testReturnPathFromRoutingContext() { - HttpRequestMetric requestMetric = new HttpRequestMetric(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, "/"); - requestMetric.routingContext = Mockito.mock(RoutingContext.class); - Route currentRoute = Mockito.mock(Route.class); - - Mockito.when(requestMetric.routingContext.currentRoute()).thenReturn(currentRoute); - Mockito.when(currentRoute.getPath()).thenReturn("/item"); - - Assertions.assertEquals("/item", requestMetric.getHttpRequestPath()); - } - - @Test - public void testReturnGenericPathFromRoutingContext() { - HttpRequestMetric requestMetric = new HttpRequestMetric(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, "/"); - requestMetric.routingContext = Mockito.mock(RoutingContext.class); - Route currentRoute = Mockito.mock(Route.class); - - Mockito.when(requestMetric.routingContext.currentRoute()).thenReturn(currentRoute); - Mockito.when(currentRoute.getPath()).thenReturn("/item/:id"); - - Assertions.assertEquals("/item/{id}", requestMetric.getHttpRequestPath()); - // Make sure conversion is cached - Assertions.assertEquals("/item/{id}", HttpRequestMetric.templatePath.get("/item/:id")); - } - - @Test - public void testParsePathDoubleSlash() { - HttpRequestMetric requestMetric = new HttpRequestMetric(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, "//"); - Assertions.assertEquals("/", requestMetric.path); - Assertions.assertTrue(requestMetric.measure, "Path should be measured"); - Assertions.assertFalse(requestMetric.pathMatched, "Path should not be marked as matched"); - } - - @Test - public void testParseEmptyPath() { - HttpRequestMetric requestMetric = new HttpRequestMetric(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, ""); - Assertions.assertEquals("/", requestMetric.path); - Assertions.assertTrue(requestMetric.measure, "Path should be measured"); - Assertions.assertFalse(requestMetric.pathMatched, "Path should not be marked as matched"); - } - - @Test - public void testParsePathNoLeadingSlash() { - HttpRequestMetric requestMetric = new HttpRequestMetric(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, - "path/with/no/leading/slash"); - Assertions.assertEquals("/path/with/no/leading/slash", requestMetric.path); - Assertions.assertTrue(requestMetric.measure, "Path should be measured"); - Assertions.assertFalse(requestMetric.pathMatched, "Path should not be marked as matched"); - } - - @Test - public void testParsePathWithQueryString() { - HttpRequestMetric requestMetric = new HttpRequestMetric(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, - "/path/with/query/string?stuff"); - Assertions.assertEquals("/path/with/query/string", requestMetric.path); - Assertions.assertTrue(requestMetric.measure, "Path should be measured"); - Assertions.assertFalse(requestMetric.pathMatched, "Path should not be marked as matched"); - } - - @Test - public void testParsePathIgnoreNoLeadingSlash() { - HttpRequestMetric requestMetric = new HttpRequestMetric(NO_MATCH_PATTERNS, ignorePatterns, - "ignore/me/with/no/leading/slash"); - Assertions.assertEquals("/ignore/me/with/no/leading/slash", requestMetric.path); - Assertions.assertFalse(requestMetric.measure, "Path should be measured"); - Assertions.assertFalse(requestMetric.pathMatched, "Path should not be marked as matched"); - } - - @Test - public void testParsePathIgnoreWithQueryString() { - HttpRequestMetric requestMetric = new HttpRequestMetric(NO_MATCH_PATTERNS, ignorePatterns, - "/ignore/me/with/query/string?stuff"); - Assertions.assertEquals("/ignore/me/with/query/string", requestMetric.path); - Assertions.assertFalse(requestMetric.measure, "Path should be measured"); - Assertions.assertFalse(requestMetric.pathMatched, "Path should not be marked as matched"); - } - - @Test - public void testParsePathMatchReplaceNoLeadingSlash() { - final Map matchPatterns = new HashMap<>(); - matchPatterns.put(Pattern.compile("/item/\\d+"), "/item/{id}"); - - HttpRequestMetric requestMetric = new HttpRequestMetric(matchPatterns, NO_IGNORE_PATTERNS, "item/123"); - Assertions.assertEquals("/item/{id}", requestMetric.path); - Assertions.assertTrue(requestMetric.measure, "Path should be measured"); - Assertions.assertTrue(requestMetric.pathMatched, "Path should be marked as matched"); - } - - @Test - public void testParsePathMatchReplaceLeadingSlash() { - final Map matchPatterns = new HashMap<>(); - matchPatterns.put(Pattern.compile("/item/\\d+"), "/item/{id}"); - - HttpRequestMetric requestMetric = new HttpRequestMetric(matchPatterns, NO_IGNORE_PATTERNS, "/item/123"); - Assertions.assertEquals("/item/{id}", requestMetric.path); - Assertions.assertTrue(requestMetric.measure, "Path should be measured"); - Assertions.assertTrue(requestMetric.pathMatched, "Path should be marked as matched"); - } -} diff --git a/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/RequestMetricInfoTest.java b/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/RequestMetricInfoTest.java new file mode 100644 index 0000000000000..9f43dbf500262 --- /dev/null +++ b/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/RequestMetricInfoTest.java @@ -0,0 +1,122 @@ +package io.quarkus.micrometer.runtime.binder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.quarkus.micrometer.runtime.config.runtime.HttpClientConfig; +import io.quarkus.micrometer.runtime.config.runtime.HttpServerConfig; +import io.quarkus.micrometer.runtime.config.runtime.VertxConfig; + +public class RequestMetricInfoTest { + + final List NO_IGNORE_PATTERNS = Collections.emptyList(); + final List ignorePatterns = Arrays.asList(Pattern.compile("/ignore.*")); + + final Map NO_MATCH_PATTERNS = Collections.emptyMap(); + + RequestMetricInfo requestMetric; + + @BeforeEach + public void init() { + requestMetric = new RequestMetricInfo(); + } + + @Test + public void testParsePathDoubleSlash() { + String path = requestMetric.getNormalizedUriPath(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, "//"); + Assertions.assertEquals("/", path); + } + + @Test + public void testParseEmptyPath() { + String path = requestMetric.getNormalizedUriPath(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, ""); + Assertions.assertEquals("/", path); + } + + @Test + public void testParsePathNoLeadingSlash() { + String path = requestMetric.getNormalizedUriPath(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, + "path/with/no/leading/slash"); + Assertions.assertEquals("/path/with/no/leading/slash", path); + } + + @Test + public void testParsePathIgnoreNoLeadingSlash() { + String path = requestMetric.getNormalizedUriPath(NO_MATCH_PATTERNS, ignorePatterns, + "ignore/me/with/no/leading/slash"); + Assertions.assertEquals(null, path); + } + + @Test + public void testHttpServerMetricsIgnorePatterns() { + HttpServerConfig serverConfig = new HttpServerConfig(); + serverConfig.ignorePatterns = Optional.of(new ArrayList<>(Arrays.asList(" /item/.* ", " /oranges/.* "))); + + HttpBinderConfiguration binderConfig = new HttpBinderConfiguration( + true, false, + serverConfig, new HttpClientConfig(), new VertxConfig()); + + Assertions.assertEquals(2, binderConfig.serverIgnorePatterns.size()); + + Pattern p = binderConfig.serverIgnorePatterns.get(0); + Assertions.assertEquals("/item/.*", p.pattern()); + Assertions.assertTrue(p.matcher("/item/123").matches()); + + p = binderConfig.serverIgnorePatterns.get(1); + Assertions.assertEquals("/oranges/.*", p.pattern()); + Assertions.assertTrue(p.matcher("/oranges/123").matches()); + } + + @Test + public void testParsePathMatchReplaceNoLeadingSlash() { + final Map matchPatterns = new HashMap<>(); + matchPatterns.put(Pattern.compile("/item/\\d+"), "/item/{id}"); + + String path = requestMetric.getNormalizedUriPath(matchPatterns, NO_IGNORE_PATTERNS, "item/123"); + Assertions.assertEquals("/item/{id}", path); + } + + @Test + public void testParsePathMatchReplaceLeadingSlash() { + final Map matchPatterns = new HashMap<>(); + matchPatterns.put(Pattern.compile("/item/\\d+"), "/item/{id}"); + + String path = requestMetric.getNormalizedUriPath(matchPatterns, NO_IGNORE_PATTERNS, "/item/123"); + Assertions.assertEquals("/item/{id}", path); + } + + @Test + public void testHttpServerMetricsMatchPatterns() { + HttpServerConfig serverConfig = new HttpServerConfig(); + serverConfig.matchPatterns = Optional + .of(new ArrayList<>(Arrays.asList(" /item/\\d+=/item/{id} ", " /msg/\\d+=/msg/{other} "))); + + HttpBinderConfiguration binderConfig = new HttpBinderConfiguration( + true, false, + serverConfig, new HttpClientConfig(), new VertxConfig()); + + Assertions.assertFalse(binderConfig.serverMatchPatterns.isEmpty()); + Iterator> i = binderConfig.serverMatchPatterns.entrySet().iterator(); + Map.Entry entry = i.next(); + + Assertions.assertEquals("/item/\\d+", entry.getKey().pattern()); + Assertions.assertEquals("/item/{id}", entry.getValue()); + Assertions.assertTrue(entry.getKey().matcher("/item/123").matches()); + + entry = i.next(); + Assertions.assertEquals("/msg/\\d+", entry.getKey().pattern()); + Assertions.assertEquals("/msg/{other}", entry.getValue()); + Assertions.assertTrue(entry.getKey().matcher("/msg/789").matches()); + } +} diff --git a/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpServerMetricsTest.java b/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpServerMetricsTest.java index cf237b51df113..73aaaf76ad522 100644 --- a/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpServerMetricsTest.java +++ b/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpServerMetricsTest.java @@ -1,53 +1,74 @@ package io.quarkus.micrometer.runtime.binder.vertx; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.regex.Pattern; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; +import org.mockito.Mockito; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import io.quarkus.micrometer.runtime.binder.HttpBinderConfiguration; -import io.quarkus.micrometer.runtime.config.runtime.HttpClientConfig; -import io.quarkus.micrometer.runtime.config.runtime.HttpServerConfig; -import io.quarkus.micrometer.runtime.config.runtime.VertxConfig; +import io.vertx.core.http.impl.HttpServerRequestInternal; +import io.vertx.ext.web.RoutingContext; +/** + * Disabled on Java 8 because of Mocks + */ +@DisabledOnJre(JRE.JAVA_8) public class VertxHttpServerMetricsTest { + final List NO_IGNORE_PATTERNS = Collections.emptyList(); + final List ignorePatterns = Arrays.asList(Pattern.compile("/ignore.*")); + + final Map NO_MATCH_PATTERNS = Collections.emptyMap(); + + RoutingContext routingContext; + HttpRequestMetric requestMetric; + HttpServerRequestInternal request; + + @BeforeEach + public void init() { + requestMetric = new HttpRequestMetric("/irrelevant"); + + routingContext = Mockito.mock(RoutingContext.class); + request = Mockito.mock(HttpServerRequestInternal.class); + + Mockito.when(routingContext.request()).thenReturn(request); + Mockito.when(request.metric()).thenReturn(requestMetric); + } @Test - public void testHttpServerMetricsIgnorePatterns() { - HttpServerConfig serverConfig = new HttpServerConfig(); - serverConfig.ignorePatterns = Optional.of(new ArrayList<>(Arrays.asList("/item/.*"))); - - HttpBinderConfiguration binderConfig = new HttpBinderConfiguration( - true, false, - serverConfig, new HttpClientConfig(), new VertxConfig()); - - VertxHttpServerMetrics metrics = new VertxHttpServerMetrics(new SimpleMeterRegistry(), binderConfig); - Assertions.assertFalse(metrics.ignorePatterns.isEmpty()); - Pattern p = metrics.ignorePatterns.get(0); - Assertions.assertEquals("/item/.*", p.pattern()); - Assertions.assertTrue(p.matcher("/item/123").matches()); + public void testReturnPathFromHttpRequestPath() { + HttpRequestMetric fetchedMetric = HttpRequestMetric.getRequestMetric(routingContext); + Assertions.assertSame(requestMetric, fetchedMetric); + + // Emulate a JAX-RS or Servlet filter pre-determining the template path + requestMetric.setTemplatePath("/item/{id}"); + Assertions.assertEquals("/item/{id}", requestMetric.applyTemplateMatching("/")); } @Test - public void testHttpServerMetricsMatchPatterns() { - HttpServerConfig serverConfig = new HttpServerConfig(); - serverConfig.matchPatterns = Optional.of(new ArrayList<>(Arrays.asList("/item/\\d+=/item/{id}"))); + public void testReturnRoutedPath() { + // Vertx route information collection, no web template, will use normalized initial value + requestMetric.appendCurrentRoutePath("/notused"); - HttpBinderConfiguration binderConfig = new HttpBinderConfiguration( - true, false, - serverConfig, new HttpClientConfig(), new VertxConfig()); + // Return the value passed in as parameter (no templates) + Assertions.assertEquals("/item/abc", requestMetric.applyTemplateMatching("/item/abc")); + } - VertxHttpServerMetrics metrics = new VertxHttpServerMetrics(new SimpleMeterRegistry(), binderConfig); + @Test + public void testReturnTemplatedPathFromRoutingContext() { + // Emulate a Vert.x Route containing templated values + requestMetric.appendCurrentRoutePath("/item/:id"); - Assertions.assertFalse(metrics.matchPatterns.isEmpty()); - Map.Entry entry = metrics.matchPatterns.entrySet().iterator().next(); - Assertions.assertEquals("/item/\\d+", entry.getKey().pattern()); - Assertions.assertEquals("/item/{id}", entry.getValue()); - Assertions.assertTrue(entry.getKey().matcher("/item/123").matches()); + // Should return the templated version of the path (based on the route definition) + Assertions.assertEquals("/item/{id}", requestMetric.applyTemplateMatching("/")); + // Make sure conversion is cached + Assertions.assertEquals("/item/{id}", HttpRequestMetric.vertxWebToUriTemplate.get("/item/:id")); } + }