Skip to content

Commit aa1685d

Browse files
committed
Register per-repository pattern for observability purposes.
The standard Spring MVC observability integration registers the plain request pattern for observations. For our repository controllers that would result in one pattern registered for all individual repository resources (e.g. /{repository}/{id} etc.). However, the insights users would like to gain rather follows the individual repositories exposed. That's why we have so far exposed repository specific path pattern (/myrepo/{id}) via a custom request attribute. To adhere to the new observability integration of Spring Framework 6, we need to expose that particular pattern on the ServerRequestObservationContext, too. Fixes #2212.
1 parent 96f02a7 commit aa1685d

File tree

3 files changed

+51
-4
lines changed

3 files changed

+51
-4
lines changed

spring-data-rest-tests/spring-data-rest-tests-core/src/test/java/org/springframework/data/rest/tests/AbstractWebIntegrationTests.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,17 @@
2020
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
2121
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
2222

23+
import io.micrometer.observation.ObservationRegistry;
24+
import jakarta.servlet.Filter;
25+
import jakarta.servlet.FilterChain;
26+
import jakarta.servlet.ServletException;
27+
import jakarta.servlet.ServletRequest;
28+
import jakarta.servlet.ServletResponse;
29+
import jakarta.servlet.http.HttpServletRequest;
30+
import jakarta.servlet.http.HttpServletResponse;
2331
import net.minidev.json.JSONArray;
2432

33+
import java.io.IOException;
2534
import java.util.Collections;
2635
import java.util.Map;
2736
import java.util.Optional;
@@ -35,6 +44,7 @@
3544
import org.springframework.hateoas.client.LinkDiscoverers;
3645
import org.springframework.http.HttpMethod;
3746
import org.springframework.http.MediaType;
47+
import org.springframework.http.server.observation.ServerRequestObservationContext;
3848
import org.springframework.mock.web.MockHttpServletResponse;
3949
import org.springframework.test.context.ContextConfiguration;
4050
import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -47,6 +57,7 @@
4757
import org.springframework.util.MultiValueMap;
4858
import org.springframework.util.StringUtils;
4959
import org.springframework.web.context.WebApplicationContext;
60+
import org.springframework.web.filter.ServerHttpObservationFilter;
5061
import org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration;
5162

5263
import com.jayway.jsonpath.InvalidPathException;
@@ -74,6 +85,7 @@ public abstract class AbstractWebIntegrationTests {
7485

7586
protected TestMvcClient client;
7687
protected MockMvc mvc;
88+
protected ServerRequestObservationContext observationContext;
7789

7890
@BeforeEach
7991
public void setUp() {
@@ -82,8 +94,10 @@ public void setUp() {
8294
}
8395

8496
protected void setupMockMvc() {
85-
this.mvc = MockMvcBuilders.webAppContextSetup(context)//
86-
.defaultRequest(get("/").accept(TestMvcClient.DEFAULT_MEDIA_TYPE)).build();
97+
this.mvc = MockMvcBuilders.webAppContextSetup(context) //
98+
.defaultRequest(get("/").accept(TestMvcClient.DEFAULT_MEDIA_TYPE)) //
99+
.addFilters(new FilterImplementation()) //
100+
.build();
87101
}
88102

89103
protected MockHttpServletResponse postAndGet(Link link, Object payload, MediaType mediaType) throws Exception {
@@ -263,4 +277,24 @@ protected Map<LinkRelation, String> getPayloadToPost() throws Exception {
263277
protected MultiValueMap<LinkRelation, String> getRootAndLinkedResources() {
264278
return new LinkedMultiValueMap<LinkRelation, String>(0);
265279
}
280+
281+
/**
282+
* Unconditionally registers a {@link ServerRequestObservationContext}. Required to be done explicitly as
283+
* {@link ServerHttpObservationFilter} avoids the context registration in case of a NoOp-{@link ObservationRegistry}.
284+
*
285+
* @author Oliver Drotbohm
286+
*/
287+
private class FilterImplementation implements Filter {
288+
289+
@Override
290+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
291+
throws IOException, ServletException {
292+
293+
observationContext = new ServerRequestObservationContext((HttpServletRequest) request,
294+
(HttpServletResponse) response);
295+
request.setAttribute(ServerHttpObservationFilter.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observationContext);
296+
297+
chain.doFilter(request, response);
298+
}
299+
}
266300
}

spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,14 @@ void answersToHalFormsRequests() throws Exception {
753753
.andExpect(status().isOk());
754754
}
755755

756+
@Test // #2212
757+
void exposesRepositoryPatternForObservation() throws Exception {
758+
759+
mvc.perform(get("/authors/42"));
760+
761+
assertThat(observationContext.getPathPattern()).isEqualTo("/authors/{id}");
762+
}
763+
756764
private List<Link> preparePersonResources(Person primary, Person... persons) throws Exception {
757765

758766
Link peopleLink = client.discoverUnique(LinkRelation.of("people"));

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.web.bind.annotation.CrossOrigin;
4343
import org.springframework.web.bind.annotation.RequestMethod;
4444
import org.springframework.web.cors.CorsConfiguration;
45+
import org.springframework.web.filter.ServerHttpObservationFilter;
4546
import org.springframework.web.method.HandlerMethod;
4647
import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition;
4748
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
@@ -242,8 +243,12 @@ private void exposeEffectiveLookupPathKey(HandlerMethod method, HttpServletReque
242243
PathPatternParser parser = getPatternParser();
243244
parser = parser != null ? parser : PARSER;
244245

245-
request.setAttribute(EFFECTIVE_LOOKUP_PATH_ATTRIBUTE,
246-
parser.parse(pattern.replace("/{repository}", repositoryBasePath)));
246+
var repositorySpecificPattern = pattern.replace("/{repository}", repositoryBasePath);
247+
248+
ServerHttpObservationFilter.findObservationContext(request)
249+
.ifPresent(context -> context.setPathPattern(repositorySpecificPattern));
250+
251+
request.setAttribute(EFFECTIVE_LOOKUP_PATH_ATTRIBUTE, parser.parse(repositorySpecificPattern));
247252
}
248253

249254
private static String getPattern(RequestMappingInfo info, HttpServletRequest request) {

0 commit comments

Comments
 (0)