From cb955e034134699fae0e927c80ef9c4a842968c3 Mon Sep 17 00:00:00 2001 From: Erin Schnabel Date: Thu, 11 Mar 2021 08:49:05 -0500 Subject: [PATCH 1/3] Refactor Http/Vertx metrics artifacts --- .../deployment/MicrometerProcessor.java | 52 +------- .../binder/HttpBinderProcessor.java | 122 ++++++++++++++++++ .../binder/RestClientProcessor.java | 45 ------- .../binder/VertxBinderProcessor.java | 44 +------ .../binder/HttpBinderConfiguration.java | 10 +- ...MetricsCommon.java => HttpCommonTags.java} | 2 +- ...uestMetric.java => RequestMetricInfo.java} | 62 ++------- .../runtime/binder/RestClientMetrics.java | 16 +-- .../binder/vertx/HttpRequestMetric.java | 68 ++++++++++ .../binder/vertx/VertxHttpServerMetrics.java | 17 ++- .../VertxMeterBinderContainerFilterUtil.java | 1 - .../binder/vertx/VertxMeterFilter.java | 1 - .../binder/vertx/VertxMetricsTags.java | 4 +- .../runtime/binder/HttpCommonTagsTest.java | 37 ++++++ .../runtime/binder/HttpMetricsCommonTest.java | 37 ------ ...icTest.java => RequestMetricInfoTest.java} | 65 ++-------- .../vertx/VertxHttpServerMetricsTest.java | 74 ++++++++++- 17 files changed, 343 insertions(+), 314 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 rename extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/{HttpMetricsCommon.java => HttpCommonTags.java} (98%) rename extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/{HttpRequestMetric.java => RequestMetricInfo.java} (63%) create mode 100644 extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/HttpRequestMetric.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 rename extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/{HttpRequestMetricTest.java => RequestMetricInfoTest.java} (60%) 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..d451edbe3eb3a --- /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.binder.vertx.VertxMeterFilter; +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; +import io.quarkus.vertx.http.deployment.FilterBuildItem; + +/** + * Avoid directly referencing optional dependencies + */ +public class HttpBinderProcessor { + + // 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.RestClientMetrics"; + + 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); + + // 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 = { VertxBinderProcessor.VertxBinderEnabled.class, HttpServerBinderEnabled.class }) + FilterBuildItem addVertxMeterFilter() { + return new FilterBuildItem(new VertxMeterFilter(), Integer.MAX_VALUE); + } + + @BuildStep(onlyIf = { VertxBinderProcessor.VertxBinderEnabled.class, 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/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..7d8e06547c9de 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,7 +110,7 @@ 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(); } 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 98% 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..c46db559874fc 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 @@ -5,7 +5,7 @@ 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"); 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/RequestMetricInfo.java similarity index 63% rename from extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpRequestMetric.java rename to extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RequestMetricInfo.java index 8815a6bfbabcf..76cd359c8e634 100644 --- 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/RequestMetricInfo.java @@ -2,50 +2,42 @@ 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.ext.web.RoutingContext; -public class HttpRequestMetric { - static final Logger log = Logger.getLogger(HttpRequestMetric.class); +public class RequestMetricInfo { + static final Logger log = Logger.getLogger(RequestMetricInfo.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; + protected final boolean measure; /** URI path used as a tag value for non-error requests */ - final String path; + protected final String path; /** True IFF the path was revised by a matcher expression */ - final boolean pathMatched; + protected final boolean pathMatched; /** Store the sample used to measure the request */ - Timer.Sample sample; + protected Timer.Sample sample; /** * Store the tags associated with the request (change 1.6.0). * Default is empty, value assigned @ requestBegin */ - Tags tags = Tags.empty(); + protected Tags tags = Tags.empty(); /** * Extract the path out of the uri. Return null if the path should be * ignored. */ - public HttpRequestMetric(Map matchPattern, List ignorePatterns, + public RequestMetricInfo(Map matchPattern, List ignorePatterns, String uri) { if (uri == null) { this.measure = false; @@ -61,8 +53,8 @@ public HttpRequestMetric(Map matchPattern, List ignore 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(""); + workingPath = HttpCommonTags.MULTIPLE_SLASH_PATTERN.matcher('/' + workingPath).replaceAll("/"); + workingPath = HttpCommonTags.TRAILING_SLASH_PATTERN.matcher(workingPath).replaceAll(""); if (workingPath.isEmpty()) { finalPath = "/"; } else { @@ -147,43 +139,9 @@ private static String extractPath(String uri) { } 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 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 index 4e36d76d2da85..43695675ecd60 100644 --- 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 @@ -62,7 +62,7 @@ class MetricsClientRequestFilter implements ClientRequestFilter { @Override public void filter(ClientRequestContext requestContext) throws IOException { - HttpRequestMetric requestMetric = new HttpRequestMetric( + RequestMetricInfo requestMetric = new RequestMetricInfo( binderConfiguration.getClientMatchPatterns(), binderConfiguration.getClientIgnorePatterns(), requestContext.getUri().getPath()); @@ -77,25 +77,25 @@ public void filter(ClientRequestContext requestContext) throws IOException { class MetricsClientResponseFilter implements ClientResponseFilter { @Override public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException { - HttpRequestMetric requestMetric = getRequestMetric(requestContext); + RequestMetricInfo 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), + HttpCommonTags.method(requestContext.getMethod()), + HttpCommonTags.uri(requestPath, statusCode), + HttpCommonTags.outcome(statusCode), + HttpCommonTags.status(statusCode), clientName(requestContext))); sample.stop(builder.register(registry)); } } - private HttpRequestMetric getRequestMetric(ClientRequestContext requestContext) { - return (HttpRequestMetric) requestContext.getProperty(REQUEST_METRIC_PROPERTY); + private RequestMetricInfo getRequestMetric(ClientRequestContext requestContext) { + return (RequestMetricInfo) requestContext.getProperty(REQUEST_METRIC_PROPERTY); } private Tag clientName(ClientRequestContext 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..cd607df3abe56 --- /dev/null +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/HttpRequestMetric.java @@ -0,0 +1,68 @@ +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.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 templatePath = new ConcurrentHashMap<>(); + + volatile RoutingContext routingContext; + + /** + * Extract the path out of the uri. Return null if the path should be + * ignored. + * + * @param matchPattern + * @param ignorePatterns + * @param uri + */ + public HttpRequestMetric(Map matchPattern, List ignorePatterns, String uri) { + super(matchPattern, ignorePatterns, uri); + } + + 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; + } +} 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 9fb601868e79c..5389e15645130 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,8 +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.quarkus.micrometer.runtime.binder.HttpCommonTags; import io.vertx.core.Context; import io.vertx.core.Vertx; import io.vertx.core.http.HttpMethod; @@ -96,10 +95,10 @@ public HttpRequestMetric responsePushed(Map socketMetric, HttpMe HttpRequestMetric requestMetric = new HttpRequestMetric(matchPatterns, ignorePatterns, uri); if (requestMetric.isMeasure()) { registry.counter(nameHttpServerPush, Tags.of( - HttpMetricsCommon.uri(requestMetric.getPath(), response.getStatusCode()), + HttpCommonTags.uri(requestMetric.getPath(), response.getStatusCode()), VertxMetricsTags.method(method), VertxMetricsTags.outcome(response), - HttpMetricsCommon.status(response.getStatusCode()))) + HttpCommonTags.status(response.getStatusCode()))) .increment(); } log.debugf("responsePushed %s: %s, %s", uri, socketMetric, requestMetric); @@ -147,9 +146,9 @@ public void requestReset(HttpRequestMetric requestMetric) { Timer.Builder builder = Timer.builder(nameHttpServerRequests) .tags(requestMetric.getTags()) .tags(Tags.of( - HttpMetricsCommon.uri(requestPath, 0), + HttpCommonTags.uri(requestPath, 0), Outcome.CLIENT_ERROR.asTag(), - HttpMetricsCommon.STATUS_RESET)); + HttpCommonTags.STATUS_RESET)); sample.stop(builder.register(registry)); } } @@ -170,9 +169,9 @@ public void responseEnd(HttpRequestMetric requestMetric, HttpServerResponse resp Timer.Builder builder = Timer.builder(nameHttpServerRequests) .tags(requestMetric.getTags()) .tags(Tags.of( - HttpMetricsCommon.uri(requestPath, response.getStatusCode()), + HttpCommonTags.uri(requestPath, response.getStatusCode()), VertxMetricsTags.outcome(response), - HttpMetricsCommon.status(response.getStatusCode()))); + HttpCommonTags.status(response.getStatusCode()))); sample.stop(builder.register(registry)); } @@ -193,7 +192,7 @@ public LongTaskTimer.Sample connected(Map socketMetric, HttpRequ String path = getServerRequestPath(requestMetric); if (path != null) { return LongTaskTimer.builder(nameWebsocketConnections) - .tags(Tags.of(HttpMetricsCommon.uri(path, 0))) + .tags(Tags.of(HttpCommonTags.uri(path, 0))) .register(registry) .start(); } 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..6fd57b7e76eb8 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 @@ -8,7 +8,6 @@ import org.jboss.logging.Logger; -import io.quarkus.micrometer.runtime.binder.HttpRequestMetric; import io.vertx.ext.web.RoutingContext; final class VertxMeterBinderContainerFilterUtil { 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 index f73553ccb3236..71a273b3f4550 100644 --- 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 @@ -1,6 +1,5 @@ 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; 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 76b576c969307..ec7c0087e115d 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.http.HttpServerResponse; @@ -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/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/RequestMetricInfoTest.java similarity index 60% rename from extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/HttpRequestMetricTest.java rename to extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/RequestMetricInfoTest.java index 0fb7436f0e06e..bedb85c6f7660 100644 --- 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/RequestMetricInfoTest.java @@ -9,64 +9,17 @@ 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 { +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(); - @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, "//"); + RequestMetricInfo requestMetric = new RequestMetricInfo(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"); @@ -74,7 +27,7 @@ public void testParsePathDoubleSlash() { @Test public void testParseEmptyPath() { - HttpRequestMetric requestMetric = new HttpRequestMetric(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, ""); + RequestMetricInfo requestMetric = new RequestMetricInfo(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"); @@ -82,7 +35,7 @@ public void testParseEmptyPath() { @Test public void testParsePathNoLeadingSlash() { - HttpRequestMetric requestMetric = new HttpRequestMetric(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, + RequestMetricInfo requestMetric = new RequestMetricInfo(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"); @@ -91,7 +44,7 @@ public void testParsePathNoLeadingSlash() { @Test public void testParsePathWithQueryString() { - HttpRequestMetric requestMetric = new HttpRequestMetric(NO_MATCH_PATTERNS, NO_IGNORE_PATTERNS, + RequestMetricInfo requestMetric = new RequestMetricInfo(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"); @@ -100,7 +53,7 @@ public void testParsePathWithQueryString() { @Test public void testParsePathIgnoreNoLeadingSlash() { - HttpRequestMetric requestMetric = new HttpRequestMetric(NO_MATCH_PATTERNS, ignorePatterns, + RequestMetricInfo requestMetric = new RequestMetricInfo(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"); @@ -109,7 +62,7 @@ public void testParsePathIgnoreNoLeadingSlash() { @Test public void testParsePathIgnoreWithQueryString() { - HttpRequestMetric requestMetric = new HttpRequestMetric(NO_MATCH_PATTERNS, ignorePatterns, + RequestMetricInfo requestMetric = new RequestMetricInfo(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"); @@ -121,7 +74,7 @@ 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"); + RequestMetricInfo requestMetric = new RequestMetricInfo(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"); @@ -132,7 +85,7 @@ 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"); + RequestMetricInfo requestMetric = new RequestMetricInfo(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/vertx/VertxHttpServerMetricsTest.java b/extensions/micrometer/runtime/src/test/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpServerMetricsTest.java index cf237b51df113..e921aad69bd44 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 @@ -2,41 +2,100 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +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.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.ext.web.Route; +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(); + + @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 testHttpServerMetricsIgnorePatterns() { HttpServerConfig serverConfig = new HttpServerConfig(); - serverConfig.ignorePatterns = Optional.of(new ArrayList<>(Arrays.asList("/item/.*"))); + serverConfig.ignorePatterns = Optional.of(new ArrayList<>(Arrays.asList(" /item/.* ", " /oranges/.* "))); HttpBinderConfiguration binderConfig = new HttpBinderConfiguration( true, false, serverConfig, new HttpClientConfig(), new VertxConfig()); VertxHttpServerMetrics metrics = new VertxHttpServerMetrics(new SimpleMeterRegistry(), binderConfig); - Assertions.assertFalse(metrics.ignorePatterns.isEmpty()); + Assertions.assertEquals(2, metrics.ignorePatterns.size()); + Pattern p = metrics.ignorePatterns.get(0); Assertions.assertEquals("/item/.*", p.pattern()); Assertions.assertTrue(p.matcher("/item/123").matches()); + + p = metrics.ignorePatterns.get(1); + Assertions.assertEquals("/oranges/.*", p.pattern()); + Assertions.assertTrue(p.matcher("/oranges/123").matches()); } @Test public void testHttpServerMetricsMatchPatterns() { HttpServerConfig serverConfig = new HttpServerConfig(); - serverConfig.matchPatterns = Optional.of(new ArrayList<>(Arrays.asList("/item/\\d+=/item/{id}"))); + serverConfig.matchPatterns = Optional + .of(new ArrayList<>(Arrays.asList(" /item/\\d+=/item/{id} ", " /msg/\\d+=/msg/{other} "))); HttpBinderConfiguration binderConfig = new HttpBinderConfiguration( true, false, @@ -45,9 +104,16 @@ public void testHttpServerMetricsMatchPatterns() { VertxHttpServerMetrics metrics = new VertxHttpServerMetrics(new SimpleMeterRegistry(), binderConfig); Assertions.assertFalse(metrics.matchPatterns.isEmpty()); - Map.Entry entry = metrics.matchPatterns.entrySet().iterator().next(); + Iterator> i = metrics.matchPatterns.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()); } } From 4feafa9777b18959d8c484641fffd6a07f5f5f7d Mon Sep 17 00:00:00 2001 From: Erin Schnabel Date: Thu, 11 Mar 2021 10:03:09 -0500 Subject: [PATCH 2/3] MeterFilters to constrain uri variants --- .../binder/HttpBinderProcessor.java | 7 ++ .../binder/HttpWithLimitedUriTest.java | 70 +++++++++++++++++++ .../runtime/MicrometerRecorder.java | 38 ++++++---- .../binder/HttpBinderConfiguration.java | 16 +++++ .../binder/HttpMeterFilterProvider.java | 68 ++++++++++++++++++ .../runtime/binder/RestClientMetrics.java | 7 +- .../binder/vertx/VertxHttpServerMetrics.java | 6 +- .../config/runtime/HttpClientConfig.java | 10 ++- .../config/runtime/HttpServerConfig.java | 10 ++- 9 files changed, 209 insertions(+), 23 deletions(-) create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/HttpWithLimitedUriTest.java create mode 100644 extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpMeterFilterProvider.java 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 index d451edbe3eb3a..2f3a1d7559213 100644 --- 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 @@ -30,6 +30,8 @@ * 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"; @@ -73,6 +75,11 @@ SyntheticBeanBuildItem enableHttpBinders(MicrometerRecorder recorder, 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) diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/HttpWithLimitedUriTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/HttpWithLimitedUriTest.java new file mode 100644 index 0000000000000..98259da255232 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/HttpWithLimitedUriTest.java @@ -0,0 +1,70 @@ +package io.quarkus.micrometer.deployment.binder; + +import static io.restassured.RestAssured.when; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +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.test.QuarkusUnitTest; + +public class HttpWithLimitedUriTest { + @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") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(EchoResource.class)); + + @Inject + HttpServerConfig httpServerConfig; + + @Inject + MeterRegistry registry; + + @Test + public void testMetricFactoryCreatedMetrics() throws Exception { + Assertions.assertEquals(1, httpServerConfig.maxUriTags); + + // Now let's poke the bear + + // Rest requests are nicely parameterized (should remain @ 1) + when().get("/echo/ping").then().statusCode(200); + when().get("/echo/pong").then().statusCode(200); + when().get("/echo/other").then().statusCode(200); + Assertions.assertEquals(1, registry.find("http.server.requests").timers().size()); + + // we set a limit of 1. If we now request the other URL.. it should not appear in metrics + when().get("/other").then().statusCode(200); + Assertions.assertEquals(1, registry.find("http.server.requests").timers().size()); + } + + @Path("/") + public static class EchoResource { + @GET + @Path("/echo/{message}") + public String echo(@PathParam("message") String message) { + return message; + } + + @GET + @Path("/other") + public String other() { + return "boo"; + } + } + +} 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 7d8e06547c9de..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 @@ -114,4 +114,20 @@ Map getMatchPatterns(Optional> configInput) { } 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/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/RestClientMetrics.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetrics.java index 43695675ecd60..a03d4d90e1fe9 100644 --- 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 @@ -21,10 +21,11 @@ * 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; + HttpBinderConfiguration httpMetricsConfig; MetricsClientRequestFilter clientRequestFilter; MetricsClientResponseFilter clientResponseFilter; @@ -38,7 +39,7 @@ public void onNewClient(Class serviceInterface, RestClientBuilder builder) { MetricsClientRequestFilter getClientRequestFilter() { MetricsClientRequestFilter clientFilter = this.clientRequestFilter; if (clientFilter == null) { - HttpBinderConfiguration httpMetricsConfig = Arc.container().instance(HttpBinderConfiguration.class).get(); + this.httpMetricsConfig = Arc.container().instance(HttpBinderConfiguration.class).get(); clientFilter = this.clientRequestFilter = new MetricsClientRequestFilter(httpMetricsConfig); } return clientFilter; @@ -82,7 +83,7 @@ public void filter(ClientRequestContext requestContext, ClientResponseContext re Timer.Sample sample = requestMetric.sample; String requestPath = requestMetric.getHttpRequestPath(); int statusCode = responseContext.getStatus(); - Timer.Builder builder = Timer.builder(HTTP_CLIENT_METRIC_NAME) + Timer.Builder builder = Timer.builder(httpMetricsConfig.getHttpClientRequestsName()) .tags(Tags.of( HttpCommonTags.method(requestContext.getMethod()), HttpCommonTags.uri(requestPath, statusCode), 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 5389e15645130..1326b6e884313 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 @@ -43,9 +43,9 @@ 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(); 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; From 16dca2f93d23890ea552dbde4e913de077e664bd Mon Sep 17 00:00:00 2001 From: Erin Schnabel Date: Thu, 11 Mar 2021 20:54:24 -0500 Subject: [PATCH 3/3] Toggle HTTP client/server metrics; Fixes #15323 --- extensions/micrometer/deployment/pom.xml | 6 +++ .../binder/HttpBinderProcessor.java | 2 +- .../binder/VertxWithHttpDisabledTest.java | 30 ++++++++++++++- .../binder/VertxWithHttpEnabledTest.java | 32 +++++++++++++++- .../micrometer/test/PingPongResource.java | 38 +++++++++++++++++++ ...cs.java => RestClientMetricsListener.java} | 37 +++++++++--------- ...profile.rest.client.spi.RestClientListener | 2 +- 7 files changed, 124 insertions(+), 23 deletions(-) create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/PingPongResource.java rename extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/{RestClientMetrics.java => RestClientMetricsListener.java} (79%) diff --git a/extensions/micrometer/deployment/pom.xml b/extensions/micrometer/deployment/pom.xml index 0015afe491e43..756f23532f383 100644 --- a/extensions/micrometer/deployment/pom.xml +++ b/extensions/micrometer/deployment/pom.xml @@ -65,6 +65,12 @@ test + + io.quarkus + quarkus-rest-client-deployment + test + + io.quarkus quarkus-resteasy-jackson-deployment 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 index 2f3a1d7559213..e4e0456bb5eaa 100644 --- 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 @@ -43,7 +43,7 @@ public class HttpBinderProcessor { .getClassForName(REST_CLIENT_LISTENER_CLASS_NAME); // Rest Client listener - private static final String REST_CLIENT_METRICS_LISTENER = "io.quarkus.micrometer.runtime.binder.RestClientMetrics"; + private static final String REST_CLIENT_METRICS_LISTENER = "io.quarkus.micrometer.runtime.binder.RestClientMetricsListener"; static class HttpServerBinderEnabled implements BooleanSupplier { MicrometerConfig mConfig; 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 6c82c3b0d2827..fe09fb6c208be 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,9 @@ package io.quarkus.micrometer.deployment.binder; +import static io.restassured.RestAssured.when; + +import java.net.URL; + import javax.enterprise.inject.Instance; import javax.inject.Inject; @@ -9,9 +13,12 @@ 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.quarkus.test.common.http.TestHTTPResource; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.SocketAddress; @@ -21,7 +28,12 @@ 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)); + + @TestHTTPResource + URL url; @Inject Instance vertxMeterBinderAdapterInstance; @@ -29,8 +41,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(); @@ -53,6 +72,13 @@ public String path() { } })); - 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 2d0dc316698ed..fdcaceafa3803 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,8 @@ package io.quarkus.micrometer.deployment.binder; +import static io.restassured.RestAssured.when; + +import java.net.URL; import java.util.Map; import java.util.regex.Pattern; @@ -12,9 +15,12 @@ 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.quarkus.test.common.http.TestHTTPResource; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.SocketAddress; import io.vertx.core.spi.metrics.HttpServerMetrics; @@ -24,12 +30,18 @@ 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)); + + @TestHTTPResource + URL url; @Inject Instance vertxMeterBinderAdapterInstance; @@ -37,8 +49,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(); @@ -72,5 +91,16 @@ public String path() { 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/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetrics.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetricsListener.java similarity index 79% rename from extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetrics.java rename to extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetricsListener.java index a03d4d90e1fe9..24210b21585f2 100644 --- 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/RestClientMetricsListener.java @@ -20,38 +20,39 @@ /** * This is initialized via ServiceFactory (static/non-CDI initialization) */ -public class RestClientMetrics implements RestClientListener { +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) { - 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) { - this.httpMetricsConfig = Arc.container().instance(HttpBinderConfiguration.class).get(); - clientFilter = this.clientRequestFilter = new MetricsClientRequestFilter(httpMetricsConfig); + if (prepClientMetrics()) { + builder.register(this.clientRequestFilter); + builder.register(this.clientResponseFilter); } - 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(); + 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 clientFilter; + return clientMetricsEnabled; } class MetricsClientRequestFilter implements ClientRequestFilter { 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