Skip to content

Commit d0cf6b5

Browse files
author
Dave Syer
committed
Add 3xx redirects to the "unmapped" class of requests for metrics
When Spring Security sends 302 responses to a login page we don't get any information about the request matching in Spring MVC. Consequently apps can end up with a lot of counter.status.302.* metrics (where "*" can be whatever the user sent). This change treats 3xx the same as 4xx (if it is unmapped it just gets added to a metric called "unmapped" instead of using the actual request path). Fixes gh-2563
1 parent fdb83ec commit d0cf6b5

File tree

4 files changed

+71
-13
lines changed

4 files changed

+71
-13
lines changed

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricFilterAutoConfiguration.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2828
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2929
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
30+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3031
import org.springframework.context.annotation.Bean;
3132
import org.springframework.context.annotation.Configuration;
3233
import org.springframework.web.filter.OncePerRequestFilter;
@@ -45,6 +46,7 @@
4546
@ConditionalOnClass({ Servlet.class, ServletRegistration.class,
4647
OncePerRequestFilter.class, HandlerMapping.class })
4748
@AutoConfigureAfter(MetricRepositoryAutoConfiguration.class)
49+
@ConditionalOnProperty(name="endpoints.metrics.filter.enabled", matchIfMissing=true)
4850
public class MetricFilterAutoConfiguration {
4951

5052
@Autowired

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsFilter.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ private String getFinalStatus(HttpServletRequest request, String path, int statu
9999
if (is4xxClientError(status)) {
100100
return UNKNOWN_PATH_SUFFIX;
101101
}
102+
if (is3xxRedirection(status)) {
103+
return UNKNOWN_PATH_SUFFIX;
104+
}
102105
return path;
103106
}
104107

@@ -126,6 +129,15 @@ private boolean is4xxClientError(int status) {
126129
}
127130
}
128131

132+
private boolean is3xxRedirection(int status) {
133+
try {
134+
return HttpStatus.valueOf(status).is3xxRedirection();
135+
}
136+
catch (Exception ex) {
137+
return false;
138+
}
139+
}
140+
129141
private String getKey(String string) {
130142
// graphite compatible metric names
131143
String value = string.replace("/", ".");

spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricFilterAutoConfigurationTests.java

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,26 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure;
1818

19+
import static org.hamcrest.Matchers.equalTo;
20+
import static org.junit.Assert.assertThat;
21+
import static org.mockito.BDDMockito.willAnswer;
22+
import static org.mockito.BDDMockito.willThrow;
23+
import static org.mockito.Matchers.anyDouble;
24+
import static org.mockito.Matchers.anyString;
25+
import static org.mockito.Matchers.eq;
26+
import static org.mockito.Mockito.mock;
27+
import static org.mockito.Mockito.times;
28+
import static org.mockito.Mockito.verify;
29+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
30+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
31+
32+
import java.io.IOException;
33+
1934
import javax.servlet.Filter;
2035
import javax.servlet.FilterChain;
36+
import javax.servlet.ServletException;
37+
import javax.servlet.http.HttpServletRequest;
38+
import javax.servlet.http.HttpServletResponse;
2139

2240
import org.junit.Test;
2341
import org.mockito.invocation.InvocationOnMock;
@@ -27,31 +45,21 @@
2745
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
2846
import org.springframework.context.annotation.Bean;
2947
import org.springframework.context.annotation.Configuration;
48+
import org.springframework.core.annotation.Order;
3049
import org.springframework.http.HttpStatus;
3150
import org.springframework.mock.web.MockHttpServletRequest;
3251
import org.springframework.mock.web.MockHttpServletResponse;
52+
import org.springframework.stereotype.Component;
3353
import org.springframework.test.web.servlet.MockMvc;
3454
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
3555
import org.springframework.web.bind.annotation.PathVariable;
3656
import org.springframework.web.bind.annotation.RequestMapping;
3757
import org.springframework.web.bind.annotation.ResponseBody;
3858
import org.springframework.web.bind.annotation.ResponseStatus;
3959
import org.springframework.web.bind.annotation.RestController;
60+
import org.springframework.web.filter.OncePerRequestFilter;
4061
import org.springframework.web.util.NestedServletException;
4162

42-
import static org.hamcrest.Matchers.equalTo;
43-
import static org.junit.Assert.assertThat;
44-
import static org.mockito.BDDMockito.willAnswer;
45-
import static org.mockito.BDDMockito.willThrow;
46-
import static org.mockito.Matchers.anyDouble;
47-
import static org.mockito.Matchers.anyString;
48-
import static org.mockito.Matchers.eq;
49-
import static org.mockito.Mockito.mock;
50-
import static org.mockito.Mockito.times;
51-
import static org.mockito.Mockito.verify;
52-
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
53-
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
54-
5563
/**
5664
* Tests for {@link MetricFilterAutoConfiguration}.
5765
*
@@ -130,6 +138,23 @@ public void records404HttpInteractionsAsSingleMetric() throws Exception {
130138
context.close();
131139
}
132140

141+
@Test
142+
public void records302HttpInteractionsAsSingleMetric() throws Exception {
143+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
144+
Config.class, MetricFilterAutoConfiguration.class, RedirectFilter.class);
145+
MetricsFilter filter = context.getBean(MetricsFilter.class);
146+
MockMvc mvc = MockMvcBuilders.standaloneSetup(new MetricFilterTestController())
147+
.addFilter(filter).addFilter(context.getBean(RedirectFilter.class))
148+
.build();
149+
mvc.perform(get("/unknownPath/1")).andExpect(status().is3xxRedirection());
150+
mvc.perform(get("/unknownPath/2")).andExpect(status().is3xxRedirection());
151+
verify(context.getBean(CounterService.class), times(2)).increment(
152+
"status.302.unmapped");
153+
verify(context.getBean(GaugeService.class), times(2)).submit(
154+
eq("response.unmapped"), anyDouble());
155+
context.close();
156+
}
157+
133158
@Test
134159
public void skipsFilterIfMissingServices() throws Exception {
135160
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
@@ -214,4 +239,19 @@ public String testException() {
214239
}
215240
}
216241

242+
@Component
243+
@Order(0)
244+
public static class RedirectFilter extends OncePerRequestFilter {
245+
246+
@Override
247+
protected void doFilterInternal(HttpServletRequest request,
248+
HttpServletResponse response, FilterChain chain) throws ServletException,
249+
IOException {
250+
// send redirect before filter chain is executed, like Spring Security sending
251+
// us back to a login page
252+
response.sendRedirect("http://example.com");
253+
}
254+
255+
}
256+
217257
}

spring-boot-samples/spring-boot-sample-web-secure/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
<main.basedir>${basedir}/../..</main.basedir>
2020
</properties>
2121
<dependencies>
22+
<dependency>
23+
<groupId>org.springframework.boot</groupId>
24+
<artifactId>spring-boot-starter-actuator</artifactId>
25+
</dependency>
2226
<dependency>
2327
<groupId>org.springframework.boot</groupId>
2428
<artifactId>spring-boot-starter-security</artifactId>

0 commit comments

Comments
 (0)