Allow configuration of auto-timed metrics
When `management.metrics.web.server.auto-time-requests` is enabled (default=true), Spring Boot collects metrics on controller methods even when they are not annotated with `@Timed`. When this happens, created metrics are based on the default `@Timed` configuration and there is no way to customize the configuration of those auto-timed controller metrics. This commit adds default configurations to auto-timed requests on both client and server sides. See gh-15988
This commit is contained in:
committed by
Stephane Nicoll
parent
3d814a5288
commit
4df6f5dee0
@@ -18,6 +18,7 @@ package org.springframework.boot.actuate.metrics.web.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -50,11 +51,49 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto
|
||||
|
||||
private final String metricName;
|
||||
|
||||
private final boolean autoTimeRequests;
|
||||
|
||||
private final double[] percentiles;
|
||||
|
||||
private final boolean histogram;
|
||||
|
||||
/**
|
||||
* Create a new {@code MetricsClientHttpRequestInterceptor}.
|
||||
* @param meterRegistry the registry to which metrics are recorded
|
||||
* @param tagProvider provider for metrics tags
|
||||
* @param metricName name of the metric to record
|
||||
* @deprecated since 2.2.0 in favor of
|
||||
* {@link #MetricsClientHttpRequestInterceptor(MeterRegistry, RestTemplateExchangeTagsProvider, String, boolean, List, boolean)}
|
||||
*/
|
||||
MetricsClientHttpRequestInterceptor(MeterRegistry meterRegistry,
|
||||
RestTemplateExchangeTagsProvider tagProvider, String metricName) {
|
||||
this(meterRegistry, tagProvider, metricName, true, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code MetricsClientHttpRequestInterceptor}.
|
||||
* @param meterRegistry the registry to which metrics are recorded
|
||||
* @param tagProvider provider for metrics tags
|
||||
* @param metricName name of the metric to record
|
||||
* @param autoTimeRequests if requests should be automatically timed
|
||||
* @param percentileList percentiles for auto time requests
|
||||
* @param histogram histogram or not for auto time requests
|
||||
* @since 2.2.0
|
||||
*/
|
||||
MetricsClientHttpRequestInterceptor(MeterRegistry meterRegistry,
|
||||
RestTemplateExchangeTagsProvider tagProvider, String metricName,
|
||||
boolean autoTimeRequests, List<Double> percentileList, boolean histogram) {
|
||||
|
||||
double[] percentiles = (percentileList != null)
|
||||
? percentileList.stream().mapToDouble(Double::doubleValue).toArray()
|
||||
: null;
|
||||
|
||||
this.tagProvider = tagProvider;
|
||||
this.meterRegistry = meterRegistry;
|
||||
this.metricName = metricName;
|
||||
this.autoTimeRequests = autoTimeRequests;
|
||||
this.percentiles = percentiles;
|
||||
this.histogram = histogram;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,8 +106,10 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto
|
||||
return response;
|
||||
}
|
||||
finally {
|
||||
getTimeBuilder(request, response).register(this.meterRegistry)
|
||||
.record(System.nanoTime() - startTime, TimeUnit.NANOSECONDS);
|
||||
if (this.autoTimeRequests) {
|
||||
getTimeBuilder(request, response).register(this.meterRegistry)
|
||||
.record(System.nanoTime() - startTime, TimeUnit.NANOSECONDS);
|
||||
}
|
||||
urlTemplate.remove();
|
||||
}
|
||||
}
|
||||
@@ -93,7 +134,8 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto
|
||||
|
||||
private Timer.Builder getTimeBuilder(HttpRequest request,
|
||||
ClientHttpResponse response) {
|
||||
return Timer.builder(this.metricName)
|
||||
return Timer.builder(this.metricName).publishPercentiles(this.percentiles)
|
||||
.publishPercentileHistogram(this.histogram)
|
||||
.tags(this.tagProvider.getTags(urlTemplate.get(), request, response))
|
||||
.description("Timer of RestTemplate operation");
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ public class MetricsRestTemplateCustomizer implements RestTemplateCustomizer {
|
||||
* @param meterRegistry the meter registry
|
||||
* @param tagProvider the tag provider
|
||||
* @param metricName the name of the recorded metric
|
||||
* @deprecated since 2.2.0 in favor of
|
||||
* {@link #MetricsRestTemplateCustomizer(MeterRegistry, RestTemplateExchangeTagsProvider, String, boolean, List, boolean)}
|
||||
*/
|
||||
public MetricsRestTemplateCustomizer(MeterRegistry meterRegistry,
|
||||
RestTemplateExchangeTagsProvider tagProvider, String metricName) {
|
||||
@@ -52,6 +54,26 @@ public class MetricsRestTemplateCustomizer implements RestTemplateCustomizer {
|
||||
tagProvider, metricName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code MetricsRestTemplateInterceptor}. When {@code autoTimeRequests}
|
||||
* is set to {@code true}, the interceptor records metrics using the given
|
||||
* {@code meterRegistry} with tags provided by the given {@code tagProvider} and with
|
||||
* {@code percentileList} and {@code histogram} configurations.
|
||||
* @param meterRegistry the meter registry
|
||||
* @param tagProvider the tag provider
|
||||
* @param metricName the name of the recorded metric
|
||||
* @param autoTimeRequests if requests should be automatically timed
|
||||
* @param percentileList percentiles for auto time requests
|
||||
* @param histogram histogram or not for auto time requests
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public MetricsRestTemplateCustomizer(MeterRegistry meterRegistry,
|
||||
RestTemplateExchangeTagsProvider tagProvider, String metricName,
|
||||
boolean autoTimeRequests, List<Double> percentileList, boolean histogram) {
|
||||
this.interceptor = new MetricsClientHttpRequestInterceptor(meterRegistry,
|
||||
tagProvider, metricName, autoTimeRequests, percentileList, histogram);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(RestTemplate restTemplate) {
|
||||
UriTemplateHandler templateHandler = restTemplate.getUriTemplateHandler();
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.boot.actuate.metrics.web.reactive.client;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
@@ -33,6 +34,7 @@ import org.springframework.web.reactive.function.client.ExchangeFunction;
|
||||
* record metrics.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Tadaya Tsuyukubo
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public class MetricsWebClientFilterFunction implements ExchangeFilterFunction {
|
||||
@@ -46,24 +48,63 @@ public class MetricsWebClientFilterFunction implements ExchangeFilterFunction {
|
||||
|
||||
private final String metricName;
|
||||
|
||||
private final boolean autoTimeRequests;
|
||||
|
||||
private final double[] percentiles;
|
||||
|
||||
private final boolean histogram;
|
||||
|
||||
/**
|
||||
* Create a new {@code MetricsWebClientFilterFunction}.
|
||||
* @param meterRegistry the registry to which metrics are recorded
|
||||
* @param tagProvider provider for metrics tags
|
||||
* @param metricName name of the metric to record
|
||||
* @deprecated since 2.2.0 in favor of
|
||||
* {@link #MetricsWebClientFilterFunction(MeterRegistry, WebClientExchangeTagsProvider, String, boolean, List, boolean)}
|
||||
*/
|
||||
public MetricsWebClientFilterFunction(MeterRegistry meterRegistry,
|
||||
WebClientExchangeTagsProvider tagProvider, String metricName) {
|
||||
this(meterRegistry, tagProvider, metricName, true, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code MetricsWebClientFilterFunction}.
|
||||
* @param meterRegistry the registry to which metrics are recorded
|
||||
* @param tagProvider provider for metrics tags
|
||||
* @param metricName name of the metric to record
|
||||
* @param autoTimeRequests if requests should be automatically timed
|
||||
* @param percentileList percentiles for auto time requests
|
||||
* @param histogram histogram or not for auto time requests
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public MetricsWebClientFilterFunction(MeterRegistry meterRegistry,
|
||||
WebClientExchangeTagsProvider tagProvider, String metricName,
|
||||
boolean autoTimeRequests, List<Double> percentileList, boolean histogram) {
|
||||
|
||||
double[] percentiles = (percentileList != null)
|
||||
? percentileList.stream().mapToDouble(Double::doubleValue).toArray()
|
||||
: null;
|
||||
|
||||
this.meterRegistry = meterRegistry;
|
||||
this.tagProvider = tagProvider;
|
||||
this.metricName = metricName;
|
||||
this.autoTimeRequests = autoTimeRequests;
|
||||
this.percentiles = percentiles;
|
||||
this.histogram = histogram;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ClientResponse> filter(ClientRequest clientRequest,
|
||||
ExchangeFunction exchangeFunction) {
|
||||
return exchangeFunction.exchange(clientRequest).doOnEach((signal) -> {
|
||||
if (!signal.isOnComplete()) {
|
||||
if (!signal.isOnComplete() && this.autoTimeRequests) {
|
||||
Long startTime = signal.getContext().get(METRICS_WEBCLIENT_START_TIME);
|
||||
ClientResponse clientResponse = signal.get();
|
||||
Throwable throwable = signal.getThrowable();
|
||||
Iterable<Tag> tags = this.tagProvider.tags(clientRequest, clientResponse,
|
||||
throwable);
|
||||
Timer.builder(this.metricName).tags(tags)
|
||||
Timer.builder(this.metricName).publishPercentiles(this.percentiles)
|
||||
.publishPercentileHistogram(this.histogram).tags(tags)
|
||||
.description("Timer of WebClient operation")
|
||||
.register(this.meterRegistry)
|
||||
.record(System.nanoTime() - startTime, TimeUnit.NANOSECONDS);
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
|
||||
package org.springframework.boot.actuate.metrics.web.reactive.server;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.Timer;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -48,12 +50,49 @@ public class MetricsWebFilter implements WebFilter {
|
||||
|
||||
private final boolean autoTimeRequests;
|
||||
|
||||
private final double[] percentiles;
|
||||
|
||||
private final boolean histogram;
|
||||
|
||||
/**
|
||||
* Create a new {@code MetricsWebFilter}.
|
||||
* @param registry the registry to which metrics are recorded
|
||||
* @param tagsProvider provider for metrics tags
|
||||
* @param metricName name of the metric to record
|
||||
* @param autoTimeRequests if requests should be automatically timed
|
||||
* @deprecated since 2.2.0 in favor of
|
||||
* {@link #MetricsWebFilter(MeterRegistry, WebFluxTagsProvider, String, boolean, List, boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
public MetricsWebFilter(MeterRegistry registry, WebFluxTagsProvider tagsProvider,
|
||||
String metricName, boolean autoTimeRequests) {
|
||||
this(registry, tagsProvider, metricName, autoTimeRequests, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code MetricsWebFilter}.
|
||||
* @param registry the registry to which metrics are recorded
|
||||
* @param tagsProvider provider for metrics tags
|
||||
* @param metricName name of the metric to record
|
||||
* @param autoTimeRequests if requests should be automatically timed
|
||||
* @param percentileList percentiles for auto time requests
|
||||
* @param histogram histogram or not for auto time requests
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public MetricsWebFilter(MeterRegistry registry, WebFluxTagsProvider tagsProvider,
|
||||
String metricName, boolean autoTimeRequests, List<Double> percentileList,
|
||||
boolean histogram) {
|
||||
|
||||
double[] percentiles = (percentileList != null)
|
||||
? percentileList.stream().mapToDouble(Double::doubleValue).toArray()
|
||||
: null;
|
||||
|
||||
this.registry = registry;
|
||||
this.tagsProvider = tagsProvider;
|
||||
this.metricName = metricName;
|
||||
this.autoTimeRequests = autoTimeRequests;
|
||||
this.percentiles = percentiles;
|
||||
this.histogram = histogram;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,29 +106,25 @@ public class MetricsWebFilter implements WebFilter {
|
||||
private Publisher<Void> filter(ServerWebExchange exchange, Mono<Void> call) {
|
||||
long start = System.nanoTime();
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
return call.doOnSuccess((done) -> success(exchange, start)).doOnError((cause) -> {
|
||||
if (response.isCommitted()) {
|
||||
error(exchange, start, cause);
|
||||
}
|
||||
else {
|
||||
response.beforeCommit(() -> {
|
||||
error(exchange, start, cause);
|
||||
return Mono.empty();
|
||||
return call.doOnSuccess((done) -> record(exchange, start, null))
|
||||
.doOnError((cause) -> {
|
||||
if (response.isCommitted()) {
|
||||
record(exchange, start, cause);
|
||||
}
|
||||
else {
|
||||
response.beforeCommit(() -> {
|
||||
record(exchange, start, cause);
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void success(ServerWebExchange exchange, long start) {
|
||||
Iterable<Tag> tags = this.tagsProvider.httpRequestTags(exchange, null);
|
||||
this.registry.timer(this.metricName, tags).record(System.nanoTime() - start,
|
||||
TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
private void error(ServerWebExchange exchange, long start, Throwable cause) {
|
||||
private void record(ServerWebExchange exchange, long start, Throwable cause) {
|
||||
Iterable<Tag> tags = this.tagsProvider.httpRequestTags(exchange, cause);
|
||||
this.registry.timer(this.metricName, tags).record(System.nanoTime() - start,
|
||||
TimeUnit.NANOSECONDS);
|
||||
Timer.builder(this.metricName).tags(tags).publishPercentiles(this.percentiles)
|
||||
.publishPercentileHistogram(this.histogram).register(this.registry)
|
||||
.record(System.nanoTime() - start, TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.boot.actuate.metrics.web.servlet;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -61,6 +62,10 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
|
||||
|
||||
private final boolean autoTimeRequests;
|
||||
|
||||
private final double[] autoTimeRequestsPercentiles;
|
||||
|
||||
private final boolean autoTimeRequestsHistogram;
|
||||
|
||||
/**
|
||||
* Create a new {@link WebMvcMetricsFilter} instance.
|
||||
* @param registry the meter registry
|
||||
@@ -68,13 +73,40 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
|
||||
* @param metricName the metric name
|
||||
* @param autoTimeRequests if requests should be automatically timed
|
||||
* @since 2.0.7
|
||||
* @deprecated since 2.1.4 in favor of
|
||||
* {@link #WebMvcMetricsFilter(MeterRegistry, WebMvcTagsProvider, String, boolean, List, boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
public WebMvcMetricsFilter(MeterRegistry registry, WebMvcTagsProvider tagsProvider,
|
||||
String metricName, boolean autoTimeRequests) {
|
||||
this(registry, tagsProvider, metricName, autoTimeRequests, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link WebMvcMetricsFilter} instance.
|
||||
* @param registry the meter registry
|
||||
* @param tagsProvider the tags provider
|
||||
* @param metricName the metric name
|
||||
* @param autoTimeRequests if requests should be automatically timed
|
||||
* @param autoTimeRequestsPercentiles default percentiles if requests are auto timed
|
||||
* @param autoTimeRequestsHistogram default histogram flag if requests are auto timed
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public WebMvcMetricsFilter(MeterRegistry registry, WebMvcTagsProvider tagsProvider,
|
||||
String metricName, boolean autoTimeRequests,
|
||||
List<Double> autoTimeRequestsPercentiles, boolean autoTimeRequestsHistogram) {
|
||||
|
||||
double[] percentiles = (autoTimeRequestsPercentiles != null)
|
||||
? autoTimeRequestsPercentiles.stream().mapToDouble(Double::doubleValue)
|
||||
.toArray()
|
||||
: null;
|
||||
|
||||
this.registry = registry;
|
||||
this.tagsProvider = tagsProvider;
|
||||
this.metricName = metricName;
|
||||
this.autoTimeRequests = autoTimeRequests;
|
||||
this.autoTimeRequestsPercentiles = percentiles;
|
||||
this.autoTimeRequestsHistogram = autoTimeRequestsHistogram;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -156,7 +188,9 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
|
||||
handlerObject, exception);
|
||||
if (annotations.isEmpty()) {
|
||||
if (this.autoTimeRequests) {
|
||||
stop(timerSample, tags, Timer.builder(this.metricName));
|
||||
stop(timerSample, tags, Timer.builder(this.metricName)
|
||||
.publishPercentiles(this.autoTimeRequestsPercentiles)
|
||||
.publishPercentileHistogram(this.autoTimeRequestsHistogram));
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -58,7 +58,8 @@ public class MetricsRestTemplateCustomizerTests {
|
||||
this.restTemplate = new RestTemplate();
|
||||
this.mockServer = MockRestServiceServer.createServer(this.restTemplate);
|
||||
this.customizer = new MetricsRestTemplateCustomizer(this.registry,
|
||||
new DefaultRestTemplateExchangeTagsProvider(), "http.client.requests");
|
||||
new DefaultRestTemplateExchangeTagsProvider(), "http.client.requests",
|
||||
true, null, false);
|
||||
this.customizer.customize(this.restTemplate);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,8 @@ public class MetricsWebFilterTests {
|
||||
MockClock clock = new MockClock();
|
||||
this.registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, clock);
|
||||
this.webFilter = new MetricsWebFilter(this.registry,
|
||||
new DefaultWebFluxTagsProvider(), REQUEST_METRICS_NAME, true);
|
||||
new DefaultWebFluxTagsProvider(), REQUEST_METRICS_NAME, true, null,
|
||||
false);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -16,9 +16,13 @@
|
||||
|
||||
package org.springframework.boot.actuate.metrics.web.servlet;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import io.micrometer.core.instrument.Clock;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.MockClock;
|
||||
import io.micrometer.core.instrument.Timer;
|
||||
import io.micrometer.core.instrument.distribution.HistogramSnapshot;
|
||||
import io.micrometer.core.instrument.simple.SimpleConfig;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.Before;
|
||||
@@ -48,6 +52,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
* Test for {@link WebMvcMetricsFilter} with auto-timed enabled.
|
||||
*
|
||||
* @author Jon Schneider
|
||||
* @author Tadaya Tsuyukubo
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@WebAppConfiguration
|
||||
@@ -73,8 +78,13 @@ public class WebMvcMetricsFilterAutoTimedTests {
|
||||
@Test
|
||||
public void metricsCanBeAutoTimed() throws Exception {
|
||||
this.mvc.perform(get("/api/10")).andExpect(status().isOk());
|
||||
assertThat(this.registry.get("http.server.requests").tags("status", "200").timer()
|
||||
.count()).isEqualTo(1L);
|
||||
Timer timer = this.registry.get("http.server.requests").tags("status", "200")
|
||||
.timer();
|
||||
assertThat(timer.count()).isEqualTo(1L);
|
||||
HistogramSnapshot snapshot = timer.takeSnapshot();
|
||||
assertThat(snapshot.percentileValues()).hasSize(2);
|
||||
assertThat(snapshot.percentileValues()[0].percentile()).isEqualTo(0.5);
|
||||
assertThat(snapshot.percentileValues()[1].percentile()).isEqualTo(0.95);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@@ -96,7 +106,7 @@ public class WebMvcMetricsFilterAutoTimedTests {
|
||||
public WebMvcMetricsFilter webMetricsFilter(WebApplicationContext context,
|
||||
MeterRegistry registry) {
|
||||
return new WebMvcMetricsFilter(registry, new DefaultWebMvcTagsProvider(),
|
||||
"http.server.requests", true);
|
||||
"http.server.requests", true, Arrays.asList(0.5, 0.95), true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@ public class WebMvcMetricsFilterTests {
|
||||
WebMvcMetricsFilter webMetricsFilter(MeterRegistry registry,
|
||||
WebApplicationContext ctx) {
|
||||
return new WebMvcMetricsFilter(registry, new DefaultWebMvcTagsProvider(),
|
||||
"http.server.requests", true);
|
||||
"http.server.requests", true, null, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ public class WebMvcMetricsIntegrationTests {
|
||||
public WebMvcMetricsFilter webMetricsFilter(MeterRegistry registry,
|
||||
WebApplicationContext ctx) {
|
||||
return new WebMvcMetricsFilter(registry, new DefaultWebMvcTagsProvider(),
|
||||
"http.server.requests", true);
|
||||
"http.server.requests", true, null, false);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
|
||||
Reference in New Issue
Block a user