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 ddc8453112c..3ab93484df4 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 @@ -26,6 +26,7 @@ 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.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.OncePerRequestFilter; @@ -38,6 +39,7 @@ import org.springframework.web.servlet.HandlerMapping; * @author Dave Syer * @author Phillip Webb * @author Andy Wilkinson + * @author Sebastian Kirsch */ @Configuration @ConditionalOnBean({ CounterService.class, GaugeService.class }) @@ -45,21 +47,25 @@ import org.springframework.web.servlet.HandlerMapping; OncePerRequestFilter.class, HandlerMapping.class }) @AutoConfigureAfter(MetricRepositoryAutoConfiguration.class) @ConditionalOnProperty(name = "endpoints.metrics.filter.enabled", matchIfMissing = true) +@EnableConfigurationProperties({ MetricFilterProperties.class }) public class MetricFilterAutoConfiguration { private final CounterService counterService; private final GaugeService gaugeService; + private final MetricFilterProperties properties; + public MetricFilterAutoConfiguration(CounterService counterService, - GaugeService gaugeService) { + GaugeService gaugeService, MetricFilterProperties properties) { this.counterService = counterService; this.gaugeService = gaugeService; + this.properties = properties; } @Bean public MetricsFilter metricFilter() { - return new MetricsFilter(this.counterService, this.gaugeService); + return new MetricsFilter(this.counterService, this.gaugeService, this.properties); } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricFilterProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricFilterProperties.java new file mode 100644 index 00000000000..cdbedf49752 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricFilterProperties.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure; + +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for the {@link MetricsFilter}. + * + * @author Sebastian Kirsch + * @author Phillip Webb + * @since 1.4.0 + */ +@ConfigurationProperties("endpoints.metrics.filter") +public class MetricFilterProperties { + + /** + * Submissions that should be made to the gauge. + */ + private Set gaugeSubmissions; + + /** + * Submissions that should be made to the counter. + */ + private Set counterSubmissions; + + public MetricFilterProperties() { + this.gaugeSubmissions = new HashSet( + EnumSet.of(MetricsFilterSubmission.MERGED)); + this.counterSubmissions = new HashSet( + EnumSet.of(MetricsFilterSubmission.MERGED)); + } + + public Set getGaugeSubmissions() { + return this.gaugeSubmissions; + } + + public void setGaugeSubmissions(Set gaugeSubmissions) { + this.gaugeSubmissions = gaugeSubmissions; + } + + public Set getCounterSubmissions() { + return this.counterSubmissions; + } + + public void setCounterSubmissions(Set counterSubmissions) { + this.counterSubmissions = counterSubmissions; + } + + boolean shouldSubmitToGauge(MetricsFilterSubmission submission) { + return shouldSubmit(this.gaugeSubmissions, submission); + } + + boolean shouldSubmitToCounter(MetricsFilterSubmission submission) { + return shouldSubmit(this.counterSubmissions, submission); + } + + private boolean shouldSubmit(Set submissions, + MetricsFilterSubmission submission) { + return submissions != null && submissions.contains(submission); + } + +} 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 8d448cd99c2..c011237aeea 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 @@ -61,6 +61,8 @@ final class MetricsFilter extends OncePerRequestFilter { private final GaugeService gaugeService; + private final MetricFilterProperties properties; + private static final Set STATUS_REPLACERS; static { @@ -82,9 +84,11 @@ final class MetricsFilter extends OncePerRequestFilter { KEY_REPLACERS = Collections.unmodifiableSet(replacements); } - MetricsFilter(CounterService counterService, GaugeService gaugeService) { + MetricsFilter(CounterService counterService, GaugeService gaugeService, + MetricFilterProperties properties) { this.counterService = counterService; this.gaugeService = gaugeService; + this.properties = properties; } @Override @@ -134,8 +138,9 @@ final class MetricsFilter extends OncePerRequestFilter { private void recordMetrics(HttpServletRequest request, String path, int status, long time) { String suffix = getFinalStatus(request, path, status); - submitToGauge(getKey("response" + suffix), time); - incrementCounter(getKey("status." + status + suffix)); + 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) { @@ -173,7 +178,20 @@ final class MetricsFilter extends OncePerRequestFilter { catch (Exception ex) { return null; } + } + private void submitMetrics(MetricsFilterSubmission submission, + HttpServletRequest request, int status, long time, String suffix) { + String prefix = ""; + if (submission == MetricsFilterSubmission.PER_HTTP_METHOD) { + prefix = request.getMethod() + "."; + } + if (this.properties.shouldSubmitToGauge(submission)) { + submitToGauge(getKey("response." + prefix + suffix), time); + } + if (this.properties.shouldSubmitToCounter(submission)) { + incrementCounter(getKey("status." + prefix + status + suffix)); + } } private String getKey(String string) { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsFilterSubmission.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsFilterSubmission.java new file mode 100644 index 00000000000..6528509d0ed --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsFilterSubmission.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure; + +/** + * Submission types that can be made by the {@link MetricsFilter}. + * + * @author Phillip Webb + * @since 1.4.0 + * @see MetricFilterProperties + */ +public enum MetricsFilterSubmission { + + /** + * Merge all HTTP methods into a single submission. + */ + MERGED, + + /** + * Group submissions by the HTTP method of the request. + */ + PER_HTTP_METHOD + +} 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 56402eccfeb..73f90a53326 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 @@ -64,6 +64,7 @@ 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.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; @@ -81,6 +82,15 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. */ public class MetricFilterAutoConfigurationTests { + @Test + public void defaultMetricFilterAutoConfigurationProperties() { + MetricFilterProperties properties = new MetricFilterProperties(); + assertThat(properties.getGaugeSubmissions()) + .containsExactly(MetricsFilterSubmission.MERGED); + assertThat(properties.getCounterSubmissions()) + .containsExactly(MetricsFilterSubmission.MERGED); + } + @Test public void recordsHttpInteractions() throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( @@ -295,6 +305,66 @@ public class MetricFilterAutoConfigurationTests { context.close(); } + @Test + public void additionallyRecordsMetricsWithHttpMethodNameIfConfigured() + throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class, MetricFilterAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(context, + "endpoints.metrics.filter.gauge-submissions=merged,per-http-method", + "endpoints.metrics.filter.counter-submissions=merged,per-http-method"); + context.refresh(); + Filter filter = context.getBean(Filter.class); + final MockHttpServletRequest request = new MockHttpServletRequest("PUT", + "/test/path"); + final MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain chain = mock(FilterChain.class); + willAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + response.setStatus(200); + return null; + } + }).given(chain).doFilter(request, response); + filter.doFilter(request, response, chain); + verify(context.getBean(GaugeService.class)).submit(eq("response.test.path"), + anyDouble()); + verify(context.getBean(GaugeService.class)).submit(eq("response.PUT.test.path"), + anyDouble()); + verify(context.getBean(CounterService.class)) + .increment(eq("status.200.test.path")); + verify(context.getBean(CounterService.class)) + .increment(eq("status.PUT.200.test.path")); + context.close(); + } + + @Test + public void doesNotRecordRolledUpMetricsIfConfigured() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class, MetricFilterAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(context, + "endpoints.metrics.filter.gauge-submissions=", + "endpoints.metrics.filter.counter-submissions="); + context.refresh(); + Filter filter = context.getBean(Filter.class); + final MockHttpServletRequest request = new MockHttpServletRequest("PUT", + "/test/path"); + final MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain chain = mock(FilterChain.class); + willAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + response.setStatus(200); + return null; + } + }).given(chain).doFilter(request, response); + filter.doFilter(request, response, chain); + verify(context.getBean(GaugeService.class), never()).submit(anyString(), + anyDouble()); + verify(context.getBean(CounterService.class), never()).increment(anyString()); + context.close(); + } + @Configuration public static class Config { diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index f35718eb89d..67732594165 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -896,6 +896,8 @@ content into your application; rather pick only the properties that you need. endpoints.mappings.sensitive= # Mark if the endpoint exposes sensitive information. endpoints.metrics.enabled= # Enable the endpoint. endpoints.metrics.filter.enabled=true # Enable the metrics servlet filter. + endpoints.metrics.filter.gauge-submissions=merged # Http filter gauge submissions (merged, per-http-method) + endpoints.metrics.filter.counter-submissions=merged # Http filter counter submissions (merged, per-http-method) endpoints.metrics.id= # Endpoint identifier. endpoints.metrics.path= # Endpoint path. endpoints.metrics.sensitive= # Mark if the endpoint exposes sensitive information.