Skip to content

Commit 24997b3

Browse files
committed
Merge branch '6.1.x'
2 parents c39ce10 + 6681394 commit 24997b3

File tree

2 files changed

+57
-10
lines changed

2 files changed

+57
-10
lines changed

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

+41-9
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@
2121

2222
import io.micrometer.observation.Observation;
2323
import io.micrometer.observation.ObservationRegistry;
24+
import jakarta.servlet.AsyncEvent;
25+
import jakarta.servlet.AsyncListener;
2426
import jakarta.servlet.FilterChain;
2527
import jakarta.servlet.RequestDispatcher;
2628
import jakarta.servlet.ServletException;
29+
import jakarta.servlet.ServletRequest;
2730
import jakarta.servlet.http.HttpServletRequest;
2831
import jakarta.servlet.http.HttpServletResponse;
2932

@@ -94,11 +97,6 @@ public static Optional<ServerRequestObservationContext> findObservationContext(H
9497
return Optional.ofNullable((ServerRequestObservationContext) request.getAttribute(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE));
9598
}
9699

97-
@Override
98-
protected boolean shouldNotFilterAsyncDispatch() {
99-
return false;
100-
}
101-
102100
@Override
103101
@SuppressWarnings("try")
104102
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
@@ -115,8 +113,12 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
115113
throw ex;
116114
}
117115
finally {
118-
// Only stop Observation if async processing is done or has never been started.
119-
if (!request.isAsyncStarted()) {
116+
// If async is started, register a listener for completion notification.
117+
if (request.isAsyncStarted()) {
118+
request.getAsyncContext().addListener(new ObservationAsyncListener(observation));
119+
}
120+
// Stop Observation right now if async processing has not been started.
121+
else {
120122
Throwable error = fetchException(request);
121123
if (error != null) {
122124
observation.error(error);
@@ -152,13 +154,43 @@ private Observation createOrFetchObservation(HttpServletRequest request, HttpSer
152154
}
153155

154156
@Nullable
155-
private Throwable unwrapServletException(Throwable ex) {
157+
static Throwable unwrapServletException(Throwable ex) {
156158
return (ex instanceof ServletException) ? ex.getCause() : ex;
157159
}
158160

159161
@Nullable
160-
private Throwable fetchException(HttpServletRequest request) {
162+
static Throwable fetchException(ServletRequest request) {
161163
return (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
162164
}
163165

166+
private static class ObservationAsyncListener implements AsyncListener {
167+
168+
private final Observation currentObservation;
169+
170+
public ObservationAsyncListener(Observation currentObservation) {
171+
this.currentObservation = currentObservation;
172+
}
173+
174+
@Override
175+
public void onStartAsync(AsyncEvent event) {
176+
}
177+
178+
@Override
179+
public void onTimeout(AsyncEvent event) {
180+
this.currentObservation.stop();
181+
}
182+
183+
@Override
184+
public void onComplete(AsyncEvent event) {
185+
this.currentObservation.stop();
186+
}
187+
188+
@Override
189+
public void onError(AsyncEvent event) {
190+
this.currentObservation.error(unwrapServletException(event.getThrowable()));
191+
this.currentObservation.stop();
192+
}
193+
194+
}
195+
164196
}

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

+16-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ class ServerHttpObservationFilterTests {
5454
private ServerHttpObservationFilter filter = new ServerHttpObservationFilter(this.observationRegistry);
5555

5656

57+
@Test
58+
void filterShouldNotProcessAsyncDispatch() {
59+
assertThat(this.filter.shouldNotFilterAsyncDispatch()).isTrue();
60+
}
61+
5762
@Test
5863
void filterShouldFillObservationContext() throws Exception {
5964
this.filter.doFilter(this.request, this.response, this.mockFilterChain);
@@ -64,7 +69,7 @@ void filterShouldFillObservationContext() throws Exception {
6469
assertThat(context.getCarrier()).isEqualTo(this.request);
6570
assertThat(context.getResponse()).isEqualTo(this.response);
6671
assertThat(context.getPathPattern()).isNull();
67-
assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS");
72+
assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS").hasBeenStopped();
6873
}
6974

7075
@Test
@@ -121,6 +126,16 @@ void customFilterShouldCallScopeOpened() throws Exception {
121126
assertThat(this.response.getHeader("X-Trace-Id")).isEqualTo("badc0ff33");
122127
}
123128

129+
@Test
130+
void shouldCloseObservationAfterAsyncCompletion() throws Exception {
131+
this.request.setAsyncSupported(true);
132+
this.request.startAsync();
133+
this.filter.doFilter(this.request, this.response, this.mockFilterChain);
134+
this.request.getAsyncContext().complete();
135+
136+
assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS").hasBeenStopped();
137+
}
138+
124139
private TestObservationRegistryAssert.TestObservationRegistryAssertReturningObservationContextAssert assertThatHttpObservation() {
125140
return TestObservationRegistryAssert.assertThat(this.observationRegistry)
126141
.hasObservationWithNameEqualTo("http.server.requests").that();

0 commit comments

Comments
 (0)