Skip to content

Commit 1bd6b30

Browse files
committed
Allow ServerHttpObservationFilter to be extended
This commit allows to extend the `ServerHttpObservationFilter` when the observation scope is opened. This typically allows to add the current traceId as a response header. Closes gh-30632
1 parent 559fec0 commit 1bd6b30

File tree

2 files changed

+43
-4
lines changed

2 files changed

+43
-4
lines changed

spring-web/src/main/java/org/springframework/web/filter/ServerHttpObservationFilter.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -106,6 +106,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
106106

107107
Observation observation = createOrFetchObservation(request, response);
108108
try (Observation.Scope scope = observation.openScope()) {
109+
onScopeOpened(scope, request, response);
109110
filterChain.doFilter(request, response);
110111
}
111112
catch (Exception ex) {
@@ -125,6 +126,17 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
125126
}
126127
}
127128

129+
/**
130+
* Notifies this filter that a new {@link Observation.Scope} is opened for the observation that was just created.
131+
* @param scope the newly opened observation scope
132+
* @param request the HTTP client request
133+
* @param response the filter's response@
134+
* @since 6.2.0
135+
**/
136+
protected void onScopeOpened(Observation.Scope scope, HttpServletRequest request, HttpServletResponse response) {
137+
138+
}
139+
128140
private Observation createOrFetchObservation(HttpServletRequest request, HttpServletResponse response) {
129141
Observation observation = (Observation) request.getAttribute(CURRENT_OBSERVATION_ATTRIBUTE);
130142
if (observation == null) {

spring-web/src/test/java/org/springframework/web/filter/ServerHttpObservationFilterTests.java

+30-3
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@
1616

1717
package org.springframework.web.filter;
1818

19+
import io.micrometer.observation.Observation;
1920
import io.micrometer.observation.ObservationRegistry;
2021
import io.micrometer.observation.tck.TestObservationRegistry;
2122
import io.micrometer.observation.tck.TestObservationRegistryAssert;
2223
import jakarta.servlet.RequestDispatcher;
2324
import jakarta.servlet.ServletException;
25+
import jakarta.servlet.http.HttpServletRequest;
26+
import jakarta.servlet.http.HttpServletResponse;
2427
import org.junit.jupiter.api.Test;
2528

2629
import org.springframework.http.HttpMethod;
2730
import org.springframework.http.server.observation.ServerRequestObservationContext;
31+
import org.springframework.util.Assert;
2832
import org.springframework.web.testfixture.servlet.MockFilterChain;
2933
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
3034
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
@@ -41,14 +45,14 @@ class ServerHttpObservationFilterTests {
4145

4246
private final TestObservationRegistry observationRegistry = TestObservationRegistry.create();
4347

44-
private final ServerHttpObservationFilter filter = new ServerHttpObservationFilter(this.observationRegistry);
45-
4648
private final MockFilterChain mockFilterChain = new MockFilterChain();
4749

4850
private final MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), "/resource/test");
4951

5052
private final MockHttpServletResponse response = new MockHttpServletResponse();
5153

54+
private ServerHttpObservationFilter filter = new ServerHttpObservationFilter(this.observationRegistry);
55+
5256

5357
@Test
5458
void filterShouldFillObservationContext() throws Exception {
@@ -65,7 +69,7 @@ void filterShouldFillObservationContext() throws Exception {
6569

6670
@Test
6771
void filterShouldAcceptNoOpObservationContext() throws Exception {
68-
ServerHttpObservationFilter filter = new ServerHttpObservationFilter(ObservationRegistry.NOOP);
72+
this.filter = new ServerHttpObservationFilter(ObservationRegistry.NOOP);
6973
filter.doFilter(this.request, this.response, this.mockFilterChain);
7074

7175
ServerRequestObservationContext context = (ServerRequestObservationContext) this.request
@@ -109,9 +113,32 @@ void filterShouldSetDefaultErrorStatusForBubblingExceptions() {
109113
.hasLowCardinalityKeyValue("status", "500");
110114
}
111115

116+
@Test
117+
void customFilterShouldCallScopeOpened() throws Exception {
118+
this.filter = new CustomObservationFilter(this.observationRegistry);
119+
this.filter.doFilter(this.request, this.response, this.mockFilterChain);
120+
121+
assertThat(this.response.getHeader("X-Trace-Id")).isEqualTo("badc0ff33");
122+
}
123+
112124
private TestObservationRegistryAssert.TestObservationRegistryAssertReturningObservationContextAssert assertThatHttpObservation() {
113125
return TestObservationRegistryAssert.assertThat(this.observationRegistry)
114126
.hasObservationWithNameEqualTo("http.server.requests").that();
115127
}
116128

129+
static class CustomObservationFilter extends ServerHttpObservationFilter {
130+
131+
public CustomObservationFilter(ObservationRegistry observationRegistry) {
132+
super(observationRegistry);
133+
}
134+
135+
@Override
136+
protected void onScopeOpened(Observation.Scope scope, HttpServletRequest request, HttpServletResponse response) {
137+
Assert.notNull(scope, "scope must not be null");
138+
Assert.notNull(request, "request must not be null");
139+
Assert.notNull(response, "response must not be null");
140+
response.setHeader("X-Trace-Id", "badc0ff33");
141+
}
142+
}
143+
117144
}

0 commit comments

Comments
 (0)