From 356edc725c26980d13bf6cca49a4a5dbfbcef327 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 1 Dec 2016 13:22:55 +0000 Subject: [PATCH] Handle request mappings with regular expressions in MetricsFilter Closes gh-7503 --- .../actuate/autoconfigure/MetricsFilter.java | 10 ++- .../MetricFilterAutoConfigurationTests.java | 61 +++++++++++++++++++ 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsFilter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsFilter.java index cfcd6fc109..1d99ac715b 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsFilter.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsFilter.java @@ -20,7 +20,6 @@ import java.io.IOException; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; -import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.FilterChain; @@ -67,7 +66,7 @@ final class MetricsFilter extends OncePerRequestFilter { static { Set replacements = new LinkedHashSet(); - replacements.add(new PatternReplacer("[{}]", 0, "-")); + replacements.add(new PatternReplacer("\\{(.+?)(?::.+)?\\}", 0, "-$1-")); replacements.add(new PatternReplacer("**", Pattern.LITERAL, "-star-star-")); replacements.add(new PatternReplacer("*", Pattern.LITERAL, "-star-")); replacements.add(new PatternReplacer("/-", Pattern.LITERAL, "/")); @@ -140,13 +139,13 @@ final class MetricsFilter extends OncePerRequestFilter { private void recordMetrics(HttpServletRequest request, String path, int status, long time) { - String suffix = getFinalStatus(request, path, status); + String suffix = determineMetricNameSuffix(request, path, status); submitMetrics(MetricsFilterSubmission.MERGED, request, status, time, suffix); submitMetrics(MetricsFilterSubmission.PER_HTTP_METHOD, request, status, time, suffix); } - private String getFinalStatus(HttpServletRequest request, String path, int status) { + private String determineMetricNameSuffix(HttpServletRequest request, String path, int status) { Object bestMatchingPattern = request .getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); if (bestMatchingPattern != null) { @@ -242,8 +241,7 @@ final class MetricsFilter extends OncePerRequestFilter { } public String apply(String input) { - return this.pattern.matcher(input) - .replaceAll(Matcher.quoteReplacement(this.replacement)); + return this.pattern.matcher(input).replaceAll(this.replacement); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricFilterAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricFilterAutoConfigurationTests.java index 62b3aa1bb7..22ff1412bf 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricFilterAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricFilterAutoConfigurationTests.java @@ -129,6 +129,51 @@ public class MetricFilterAutoConfigurationTests { context.close(); } + @Test + public void recordsHttpInteractionsWithRegexTemplateVariable() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + Config.class, MetricFilterAutoConfiguration.class); + Filter filter = context.getBean(Filter.class); + MockMvc mvc = MockMvcBuilders.standaloneSetup(new MetricFilterTestController()) + .addFilter(filter).build(); + mvc.perform(get("/templateVarRegexTest/foo")).andExpect(status().isOk()); + verify(context.getBean(CounterService.class)) + .increment("status.200.templateVarRegexTest.someVariable"); + verify(context.getBean(GaugeService.class)) + .submit(eq("response.templateVarRegexTest.someVariable"), anyDouble()); + context.close(); + } + + @Test + public void recordsHttpInteractionsWithWilcardMapping() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + Config.class, MetricFilterAutoConfiguration.class); + Filter filter = context.getBean(Filter.class); + MockMvc mvc = MockMvcBuilders.standaloneSetup(new MetricFilterTestController()) + .addFilter(filter).build(); + mvc.perform(get("/wildcardMapping/foo")).andExpect(status().isOk()); + verify(context.getBean(CounterService.class)) + .increment("status.200.wildcardMapping.star"); + verify(context.getBean(GaugeService.class)) + .submit(eq("response.wildcardMapping.star"), anyDouble()); + context.close(); + } + + @Test + public void recordsHttpInteractionsWithDoubleWildcardMapping() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + Config.class, MetricFilterAutoConfiguration.class); + Filter filter = context.getBean(Filter.class); + MockMvc mvc = MockMvcBuilders.standaloneSetup(new MetricFilterTestController()) + .addFilter(filter).build(); + mvc.perform(get("/doubleWildcardMapping/foo/bar/baz")).andExpect(status().isOk()); + verify(context.getBean(CounterService.class)) + .increment("status.200.doubleWildcardMapping.star-star.baz"); + verify(context.getBean(GaugeService.class)) + .submit(eq("response.doubleWildcardMapping.star-star.baz"), anyDouble()); + context.close(); + } + @Test public void recordsKnown404HttpInteractionsAsSingleMetricWithPathAndTemplateVariable() throws Exception { @@ -429,6 +474,22 @@ public class MetricFilterAutoConfigurationTests { return someVariable; } + @RequestMapping("wildcardMapping/*") + public String testWildcardMapping() { + return "wildcard"; + } + + @RequestMapping("doubleWildcardMapping/**/baz") + public String testDoubleWildcardMapping() { + return "doubleWildcard"; + } + + @RequestMapping("templateVarRegexTest/{someVariable:[a-z]+}") + public String testTemplateVariableRegexResolution( + @PathVariable String someVariable) { + return someVariable; + } + @RequestMapping("knownPath/{someVariable}") @ResponseStatus(HttpStatus.NOT_FOUND) @ResponseBody