Instrument Servlet server apps for Observability
This commit introduces the new `HttpRequestsObservationFilter` This `Filter` can be used to instrument Servlet-based web frameworks for Micrometer Observations. While the Servlet request and responses are automatically used for extracting KeyValues for observations, web frameworks still need to provide the matching URL pattern, if supported. This can be done by fetching the observation context from the request attributes and contributing to it. This commit instruments Spring MVC (annotation and functional variants), effectively replacing Spring Boot's `WebMvcMetricsFilter`. See gh-28880
This commit is contained in:
@@ -26,6 +26,8 @@ import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.web.observation.HttpRequestsObservationContext;
|
||||
import org.springframework.web.observation.HttpRequestsObservationFilter;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.function.HandlerFunction;
|
||||
@@ -33,12 +35,14 @@ import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.RouterFunctions;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
|
||||
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
|
||||
import org.springframework.web.util.ServletRequestPathUtils;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link RouterFunctionMapping}.
|
||||
* @author Arjen Poutsma
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
@@ -170,11 +174,15 @@ class RouterFunctionMappingTests {
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)).isEqualTo("/match");
|
||||
assertThat(HttpRequestsObservationFilter.findObservationContext(request))
|
||||
.hasValueSatisfying(context -> assertThat(context.getPathPattern()).isEqualTo("/match"));
|
||||
assertThat(request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(handlerFunction);
|
||||
}
|
||||
|
||||
private MockHttpServletRequest createTestRequest(String path) {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", path);
|
||||
request.setAttribute(HttpRequestsObservationFilter.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE,
|
||||
new HttpRequestsObservationContext(request, new MockHttpServletResponse()));
|
||||
ServletRequestPathUtils.parseAndCache(request);
|
||||
return request;
|
||||
}
|
||||
|
||||
@@ -49,12 +49,15 @@ import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.method.support.InvocableHandlerMethod;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
import org.springframework.web.observation.HttpRequestsObservationContext;
|
||||
import org.springframework.web.observation.HttpRequestsObservationFilter;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.handler.MappedInterceptor;
|
||||
import org.springframework.web.servlet.handler.PathPatternsParameterizedTest;
|
||||
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
|
||||
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
|
||||
import org.springframework.web.util.ServletRequestPathUtils;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
|
||||
@@ -288,6 +291,18 @@ class RequestMappingInfoHandlerMappingTests {
|
||||
assertThat(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)).isEqualTo("/{path1}/2");
|
||||
}
|
||||
|
||||
@PathPatternsParameterizedTest
|
||||
void handleMatchBestMatchingPatternAttributeInObservationContext(TestRequestMappingInfoHandlerMapping mapping) {
|
||||
RequestMappingInfo key = RequestMappingInfo.paths("/{path1}/2", "/**").build();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2");
|
||||
HttpRequestsObservationContext observationContext = new HttpRequestsObservationContext(request, new MockHttpServletResponse());
|
||||
request.setAttribute(HttpRequestsObservationFilter.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observationContext);
|
||||
mapping.handleMatch(key, "/1/2", request);
|
||||
|
||||
assertThat(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)).isEqualTo("/{path1}/2");
|
||||
assertThat(observationContext.getPathPattern()).isEqualTo("/{path1}/2");
|
||||
}
|
||||
|
||||
@PathPatternsParameterizedTest // gh-22543
|
||||
void handleMatchBestMatchingPatternAttributeNoPatternsDefined(TestRequestMappingInfoHandlerMapping mapping) {
|
||||
String path = "";
|
||||
|
||||
Reference in New Issue
Block a user