Commit 37fa3a3b authored by Phillip Webb's avatar Phillip Webb

Merge pull request #5102 from sebastiankirsch

* gh-4913:
  Allow per-http-method MetricsFilter submissions
parents 40e51021 a15684e6
......@@ -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);
}
}
/*
* 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<MetricsFilterSubmission> gaugeSubmissions;
/**
* Submissions that should be made to the counter.
*/
private Set<MetricsFilterSubmission> counterSubmissions;
public MetricFilterProperties() {
this.gaugeSubmissions = new HashSet<MetricsFilterSubmission>(
EnumSet.of(MetricsFilterSubmission.MERGED));
this.counterSubmissions = new HashSet<MetricsFilterSubmission>(
EnumSet.of(MetricsFilterSubmission.MERGED));
}
public Set<MetricsFilterSubmission> getGaugeSubmissions() {
return this.gaugeSubmissions;
}
public void setGaugeSubmissions(Set<MetricsFilterSubmission> gaugeSubmissions) {
this.gaugeSubmissions = gaugeSubmissions;
}
public Set<MetricsFilterSubmission> getCounterSubmissions() {
return this.counterSubmissions;
}
public void setCounterSubmissions(Set<MetricsFilterSubmission> 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<MetricsFilterSubmission> submissions,
MetricsFilterSubmission submission) {
return submissions != null && submissions.contains(submission);
}
}
......@@ -61,6 +61,8 @@ final class MetricsFilter extends OncePerRequestFilter {
private final GaugeService gaugeService;
private final MetricFilterProperties properties;
private static final Set<PatternReplacer> 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) {
......
/*
* 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
}
......@@ -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<Object>() {
@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<Object>() {
@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 {
......
......@@ -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.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment