diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricFilterAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricFilterAutoConfiguration.java index 01133efdbe..158dd240b4 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricFilterAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricFilterAutoConfiguration.java @@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.OncePerRequestFilter; @@ -45,6 +46,7 @@ import org.springframework.web.servlet.HandlerMapping; @ConditionalOnClass({ Servlet.class, ServletRegistration.class, OncePerRequestFilter.class, HandlerMapping.class }) @AutoConfigureAfter(MetricRepositoryAutoConfiguration.class) +@ConditionalOnProperty(name="endpoints.metrics.filter.enabled", matchIfMissing=true) public class MetricFilterAutoConfiguration { @Autowired 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 d006be6c58..b49f25697f 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 @@ -99,6 +99,9 @@ final class MetricsFilter extends OncePerRequestFilter { if (is4xxClientError(status)) { return UNKNOWN_PATH_SUFFIX; } + if (is3xxRedirection(status)) { + return UNKNOWN_PATH_SUFFIX; + } return path; } @@ -126,6 +129,15 @@ final class MetricsFilter extends OncePerRequestFilter { } } + private boolean is3xxRedirection(int status) { + try { + return HttpStatus.valueOf(status).is3xxRedirection(); + } + catch (Exception ex) { + return false; + } + } + private String getKey(String string) { // graphite compatible metric names String value = string.replace("/", "."); 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 09742c9214..ecd1237ed7 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 @@ -16,8 +16,26 @@ package org.springframework.boot.actuate.autoconfigure; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.willAnswer; +import static org.mockito.BDDMockito.willThrow; +import static org.mockito.Matchers.anyDouble; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.IOException; + import javax.servlet.Filter; import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; @@ -27,9 +45,11 @@ import org.springframework.boot.actuate.metrics.GaugeService; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.stereotype.Component; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.bind.annotation.PathVariable; @@ -37,21 +57,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.NestedServletException; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertThat; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.BDDMockito.willThrow; -import static org.mockito.Matchers.anyDouble; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - /** * Tests for {@link MetricFilterAutoConfiguration}. * @@ -130,6 +138,23 @@ public class MetricFilterAutoConfigurationTests { context.close(); } + @Test + public void records302HttpInteractionsAsSingleMetric() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + Config.class, MetricFilterAutoConfiguration.class, RedirectFilter.class); + MetricsFilter filter = context.getBean(MetricsFilter.class); + MockMvc mvc = MockMvcBuilders.standaloneSetup(new MetricFilterTestController()) + .addFilter(filter).addFilter(context.getBean(RedirectFilter.class)) + .build(); + mvc.perform(get("/unknownPath/1")).andExpect(status().is3xxRedirection()); + mvc.perform(get("/unknownPath/2")).andExpect(status().is3xxRedirection()); + verify(context.getBean(CounterService.class), times(2)).increment( + "status.302.unmapped"); + verify(context.getBean(GaugeService.class), times(2)).submit( + eq("response.unmapped"), anyDouble()); + context.close(); + } + @Test public void skipsFilterIfMissingServices() throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( @@ -214,4 +239,19 @@ public class MetricFilterAutoConfigurationTests { } } + @Component + @Order(0) + public static class RedirectFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, FilterChain chain) throws ServletException, + IOException { + // send redirect before filter chain is executed, like Spring Security sending + // us back to a login page + response.sendRedirect("http://example.com"); + } + + } + } diff --git a/spring-boot-samples/spring-boot-sample-web-secure/pom.xml b/spring-boot-samples/spring-boot-sample-web-secure/pom.xml index a4646d5295..692dcc566b 100644 --- a/spring-boot-samples/spring-boot-sample-web-secure/pom.xml +++ b/spring-boot-samples/spring-boot-sample-web-secure/pom.xml @@ -19,6 +19,10 @@ ${basedir}/../.. + + org.springframework.boot + spring-boot-starter-actuator + org.springframework.boot spring-boot-starter-security