Commit 25815ca7 authored by Phillip Webb's avatar Phillip Webb

Refine WebMvcMetricsFilter for async support

Rework `WebMvcMetricsFilter` so that async requests can be handled
correctly.

See gh-11348
parent 112ffd78
......@@ -19,17 +19,17 @@ package org.springframework.boot.actuate.autoconfigure.metrics.web.servlet;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Web.Server;
import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetrics;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
/**
......@@ -46,22 +46,18 @@ public class WebMvcMetricsConfiguration {
@Bean
@ConditionalOnMissingBean(WebMvcTagsProvider.class)
public DefaultWebMvcTagsProvider webmvcTagConfigurer() {
public DefaultWebMvcTagsProvider webMvcTagsProvider() {
return new DefaultWebMvcTagsProvider();
}
@Bean
public WebMvcMetrics controllerMetrics(MeterRegistry registry,
MetricsProperties properties, WebMvcTagsProvider configurer) {
return new WebMvcMetrics(registry, configurer,
properties.getWeb().getServer().getRequestsMetricName(),
properties.getWeb().getServer().isAutoTimeRequests(),
properties.getWeb().getServer().isRecordRequestPercentiles());
}
@Bean
public WebMvcMetricsFilter webMetricsFilter(ApplicationContext context) {
return new WebMvcMetricsFilter(context);
public WebMvcMetricsFilter webMetricsFilter(MeterRegistry registry,
MetricsProperties properties, WebMvcTagsProvider tagsProvider,
WebApplicationContext context) {
Server serverProperties = properties.getWeb().getServer();
return new WebMvcMetricsFilter(context, registry, tagsProvider,
serverProperties.getRequestsMetricName(),
serverProperties.isAutoTimeRequests());
}
}
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
......@@ -16,12 +16,11 @@
package org.springframework.boot.actuate.metrics.web.servlet;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
/**
* Default implementation of {@link WebMvcTagsProvider}.
......@@ -31,31 +30,16 @@ import io.micrometer.core.instrument.Tag;
*/
public class DefaultWebMvcTagsProvider implements WebMvcTagsProvider {
/**
* Supplies default tags to long task timers.
* @param request The HTTP request.
* @param handler The request method that is responsible for handling the request.
* @return A set of tags added to every Spring MVC HTTP request
*/
@Override
public Iterable<Tag> httpLongRequestTags(HttpServletRequest request, Object handler) {
return Arrays.asList(WebMvcTags.method(request), WebMvcTags.uri(request, null));
public Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response,
Object handler, Throwable exception) {
return Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, response),
WebMvcTags.exception(exception), WebMvcTags.status(response));
}
/**
* Supplies default tags to the Web MVC server programming model.
* @param request The HTTP request.
* @param handler the Spring MVC handler for the request
* @param response The HTTP response.
* @param ex The current exception, if any
* @return A set of tags added to every Spring MVC HTTP request.
*/
@Override
public Iterable<Tag> httpRequestTags(HttpServletRequest request, Object handler,
HttpServletResponse response, Throwable ex) {
return Arrays.asList(WebMvcTags.method(request),
WebMvcTags.uri(request, response), WebMvcTags.exception(ex),
WebMvcTags.status(response));
public Iterable<Tag> getLongRequestTags(HttpServletRequest request, Object handler) {
return Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, null));
}
}
......@@ -45,7 +45,8 @@ public final class WebMvcTags {
* @return the method tag whose value is a capitalized method (e.g. GET).
*/
public static Tag method(HttpServletRequest request) {
return Tag.of("method", request.getMethod());
return (request == null ? Tag.of("method", "UNKNOWN")
: Tag.of("method", request.getMethod()));
}
/**
......@@ -54,7 +55,8 @@ public final class WebMvcTags {
* @return the status tag derived from the status of the response
*/
public static Tag status(HttpServletResponse response) {
return Tag.of("status", ((Integer) response.getStatus()).toString());
return (response == null ? Tag.of("status", "UNKNOWN")
: Tag.of("status", ((Integer) response.getStatus()).toString()));
}
/**
......@@ -72,18 +74,14 @@ public final class WebMvcTags {
if (status != null && status.is3xxRedirection()) {
return Tag.of("uri", "REDIRECTION");
}
if (HttpStatus.NOT_FOUND.equals(status)) {
if (status != null && status.equals(HttpStatus.NOT_FOUND)) {
return Tag.of("uri", "NOT_FOUND");
}
}
String uri = (String) request
.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
if (uri == null) {
uri = request.getPathInfo();
}
if (!StringUtils.hasText(uri)) {
uri = "/";
if (request == null) {
return Tag.of("uri", "UNKNOWN");
}
String uri = getUri(request);
uri = uri.replaceAll("//+", "/").replaceAll("/$", "");
return Tag.of("uri", uri.isEmpty() ? "root" : uri);
}
......@@ -97,6 +95,13 @@ public final class WebMvcTags {
}
}
private static String getUri(HttpServletRequest request) {
String uri = (String) request
.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
uri = (uri != null ? uri : request.getPathInfo());
return (StringUtils.hasText(uri) ? uri : "/");
}
/**
* Creates a {@code exception} tag based on the {@link Class#getSimpleName() simple
* name} of the class of the given {@code exception}.
......@@ -104,10 +109,8 @@ public final class WebMvcTags {
* @return the exception tag derived from the exception
*/
public static Tag exception(Throwable exception) {
if (exception != null) {
return Tag.of("exception", exception.getClass().getSimpleName());
}
return Tag.of("exception", "None");
return Tag.of("exception",
(exception == null ? "None" : exception.getClass().getSimpleName()));
}
}
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
......@@ -31,24 +31,26 @@ import io.micrometer.core.instrument.Tag;
*/
public interface WebMvcTagsProvider {
/**
* Provides tags to be used by {@link LongTaskTimer long task timers}.
* @param request the HTTP request
* @param handler the handler for the request
* @return tags to associate with metrics recorded for the request
*/
Iterable<Tag> httpLongRequestTags(HttpServletRequest request, Object handler);
/**
* Provides tags to be associated with metrics for the given {@code request} and
* {@code response} exchange. The request was handled by the given {@code handler}.
* {@code response} exchange.
* @param request the request
* @param handler the handler
* @param response the response
* @param ex the current exception, if any
* @param handler the handler for the request or {@code null} if the handler is
* unknown
* @param exception the current exception, if any
* @return tags to associate with metrics for the request and response exchange
*/
Iterable<Tag> httpRequestTags(HttpServletRequest request, Object handler,
HttpServletResponse response, Throwable ex);
Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response,
Object handler, Throwable exception);
/**
* Provides tags to be used by {@link LongTaskTimer long task timers}.
* @param request the HTTP request
* @param handler the handler for the request or {@code null} if the handler is
* unknown
* @return tags to associate with metrics recorded for the request
*/
Iterable<Tag> getLongRequestTags(HttpServletRequest request, Object handler);
}
......@@ -26,7 +26,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
......@@ -46,6 +45,8 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Test for {@link WebMvcMetricsFilter} with auto-timed enabled.
*
* @author Jon Schneider
*/
@RunWith(SpringRunner.class)
......@@ -92,14 +93,10 @@ public class WebMvcMetricsFilterAutoTimedTests {
}
@Bean
public WebMvcMetrics controllerMetrics(MeterRegistry registry) {
return new WebMvcMetrics(registry, new DefaultWebMvcTagsProvider(),
"http.server.requests", true, false);
}
@Bean
public WebMvcMetricsFilter webMetricsFilter(ApplicationContext context) {
return new WebMvcMetricsFilter(context);
public WebMvcMetricsFilter webMetricsFilter(WebApplicationContext context,
MeterRegistry registry) {
return new WebMvcMetricsFilter(context, registry,
new DefaultWebMvcTagsProvider(), "http.server.requests", true);
}
}
......
......@@ -27,11 +27,11 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
......@@ -50,12 +50,13 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Integration tests for {@link WebMvcMetrics}.
* Tests for {@link WebMvcMetricsFilter} in the presence of a custom exception handler.
*
* @author Jon Schneider
*/
@RunWith(SpringRunner.class)
@WebAppConfiguration
@TestPropertySource(properties = "security.ignored=/**")
public class WebMvcMetricsIntegrationTests {
@Autowired
......@@ -64,11 +65,11 @@ public class WebMvcMetricsIntegrationTests {
@Autowired
private SimpleMeterRegistry registry;
private MockMvc mvc;
@Autowired
private WebMvcMetricsFilter filter;
private MockMvc mvc;
@Before
public void setupMockMvc() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
......@@ -107,14 +108,10 @@ public class WebMvcMetricsIntegrationTests {
}
@Bean
public WebMvcMetrics controllerMetrics(MeterRegistry registry) {
return new WebMvcMetrics(registry, new DefaultWebMvcTagsProvider(),
"http.server.requests", true, false);
}
@Bean
public WebMvcMetricsFilter webMetricsFilter(ApplicationContext context) {
return new WebMvcMetricsFilter(context);
public WebMvcMetricsFilter webMetricsFilter(MeterRegistry registry,
WebApplicationContext ctx) {
return new WebMvcMetricsFilter(ctx, registry, new DefaultWebMvcTagsProvider(),
"http.server.requests", true);
}
@RestController
......@@ -152,12 +149,8 @@ public class WebMvcMetricsIntegrationTests {
@ControllerAdvice
static class CustomExceptionHandler {
@Autowired
WebMvcMetrics metrics;
@ExceptionHandler
ResponseEntity<String> handleError(Exception1 ex) {
this.metrics.tagWithException(ex);
return new ResponseEntity<>("this is a custom exception body",
HttpStatus.INTERNAL_SERVER_ERROR);
}
......
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