diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsConfiguration.java index 0774d5036df..e2c825a19c5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsConfiguration.java @@ -16,6 +16,8 @@ package org.springframework.boot.actuate.autoconfigure.metrics.web.servlet; +import javax.servlet.DispatcherType; + import io.micrometer.core.instrument.MeterRegistry; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties; @@ -27,6 +29,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.WebApplicationContext; @@ -51,13 +54,17 @@ public class WebMvcMetricsConfiguration { } @Bean - public WebMvcMetricsFilter webMetricsFilter(MeterRegistry registry, - MetricsProperties properties, WebMvcTagsProvider tagsProvider, - WebApplicationContext context) { + public FilterRegistrationBean webMetricsFilter( + MeterRegistry registry, MetricsProperties properties, + WebMvcTagsProvider tagsProvider, WebApplicationContext context) { Server serverProperties = properties.getWeb().getServer(); - return new WebMvcMetricsFilter(context, registry, tagsProvider, - serverProperties.getRequestsMetricName(), + WebMvcMetricsFilter filter = new WebMvcMetricsFilter(context, registry, + tagsProvider, serverProperties.getRequestsMetricName(), serverProperties.isAutoTimeRequests()); + FilterRegistrationBean registration = new FilterRegistrationBean<>( + filter); + registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC); + return registration; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfigurationIntegrationTests.java index cb0c9658639..da93eb6164a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfigurationIntegrationTests.java @@ -19,6 +19,10 @@ package org.springframework.boot.actuate.autoconfigure.metrics; import java.util.Collections; import java.util.Map; import java.util.Set; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.TimeUnit; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MockClock; @@ -83,6 +87,9 @@ public class MetricsAutoConfigurationIntegrationTests { @Autowired private MeterRegistry registry; + @Autowired + private CyclicBarrier cyclicBarrier; + @SuppressWarnings("unchecked") @Test public void restTemplateIsInstrumented() { @@ -111,6 +118,21 @@ public class MetricsAutoConfigurationIntegrationTests { .hasAtLeastOneElementOfType(JvmMemoryMetrics.class); } + @Test + public void asyncRequestMappingIsInstrumented() throws InterruptedException, BrokenBarrierException { + Thread backgroundRequest = new Thread(() -> this.loopback.getForObject("/api/async", String.class)); + backgroundRequest.start(); + this.cyclicBarrier.await(); + MockClock.clock(this.registry).addSeconds(2); + this.cyclicBarrier.await(); + backgroundRequest.join(); + + assertThat(this.registry.find("http.server.requests") + .tags("uri", "/api/async").timer()) + .matches(t -> t.count() == 1) + .matches(t -> t.totalTime(TimeUnit.SECONDS) == 2); + } + @Configuration @ImportAutoConfiguration({ MetricsAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, @@ -130,16 +152,38 @@ public class MetricsAutoConfigurationIntegrationTests { return restTemplateBuilder.build(); } + @Bean + public CyclicBarrier cyclicBarrier() { + return new CyclicBarrier(2); + } } @RestController static class PersonController { + private final CyclicBarrier cyclicBarrier; + + PersonController(CyclicBarrier cyclicBarrier) { + this.cyclicBarrier = cyclicBarrier; + } @GetMapping("/api/people") Set personName() { return Collections.singleton("Jon"); } + @GetMapping("/api/async") + CompletableFuture asyncHello() throws BrokenBarrierException, InterruptedException { + this.cyclicBarrier.await(); + return CompletableFuture.supplyAsync(() -> { + try { + this.cyclicBarrier.await(); + } + catch (InterruptedException | BrokenBarrierException e) { + throw new RuntimeException(e); + } + return "async-hello"; + }); + } } }