Skip to content

Commit 0717748

Browse files
committed
Merge branch '6.1.x'
2 parents cc00287 + ab236c7 commit 0717748

File tree

2 files changed

+70
-8
lines changed

2 files changed

+70
-8
lines changed

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

+9-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.micrometer.observation.ObservationRegistry;
2424
import jakarta.servlet.AsyncEvent;
2525
import jakarta.servlet.AsyncListener;
26+
import jakarta.servlet.DispatcherType;
2627
import jakarta.servlet.FilterChain;
2728
import jakarta.servlet.RequestDispatcher;
2829
import jakarta.servlet.ServletException;
@@ -97,6 +98,11 @@ public static Optional<ServerRequestObservationContext> findObservationContext(H
9798
return Optional.ofNullable((ServerRequestObservationContext) request.getAttribute(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE));
9899
}
99100

101+
@Override
102+
protected boolean shouldNotFilterAsyncDispatch() {
103+
return false;
104+
}
105+
100106
@Override
101107
@SuppressWarnings("try")
102108
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
@@ -117,8 +123,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
117123
if (request.isAsyncStarted()) {
118124
request.getAsyncContext().addListener(new ObservationAsyncListener(observation));
119125
}
120-
// Stop Observation right now if async processing has not been started.
121-
else {
126+
// scope is opened for ASYNC dispatches, but the observation will be closed
127+
// by the async listener.
128+
else if (request.getDispatcherType() != DispatcherType.ASYNC){
122129
Throwable error = fetchException(request);
123130
if (error != null) {
124131
observation.error(error);
@@ -188,7 +195,6 @@ public void onComplete(AsyncEvent event) {
188195
@Override
189196
public void onError(AsyncEvent event) {
190197
this.currentObservation.error(unwrapServletException(event.getThrowable()));
191-
this.currentObservation.stop();
192198
}
193199

194200
}

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

+61-5
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,26 @@
1616

1717
package org.springframework.web.filter;
1818

19+
import java.io.IOException;
20+
1921
import io.micrometer.observation.Observation;
2022
import io.micrometer.observation.ObservationRegistry;
2123
import io.micrometer.observation.tck.TestObservationRegistry;
2224
import io.micrometer.observation.tck.TestObservationRegistryAssert;
25+
import jakarta.servlet.AsyncEvent;
26+
import jakarta.servlet.AsyncListener;
27+
import jakarta.servlet.DispatcherType;
2328
import jakarta.servlet.RequestDispatcher;
2429
import jakarta.servlet.ServletException;
30+
import jakarta.servlet.http.HttpServlet;
2531
import jakarta.servlet.http.HttpServletRequest;
2632
import jakarta.servlet.http.HttpServletResponse;
2733
import org.junit.jupiter.api.Test;
2834

2935
import org.springframework.http.HttpMethod;
3036
import org.springframework.http.server.observation.ServerRequestObservationContext;
3137
import org.springframework.util.Assert;
38+
import org.springframework.web.testfixture.servlet.MockAsyncContext;
3239
import org.springframework.web.testfixture.servlet.MockFilterChain;
3340
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
3441
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
@@ -45,18 +52,18 @@ class ServerHttpObservationFilterTests {
4552

4653
private final TestObservationRegistry observationRegistry = TestObservationRegistry.create();
4754

48-
private final MockFilterChain mockFilterChain = new MockFilterChain();
49-
5055
private final MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), "/resource/test");
5156

5257
private final MockHttpServletResponse response = new MockHttpServletResponse();
5358

59+
private MockFilterChain mockFilterChain = new MockFilterChain();
60+
5461
private ServerHttpObservationFilter filter = new ServerHttpObservationFilter(this.observationRegistry);
5562

5663

5764
@Test
58-
void filterShouldNotProcessAsyncDispatch() {
59-
assertThat(this.filter.shouldNotFilterAsyncDispatch()).isTrue();
65+
void filterShouldProcessAsyncDispatch() {
66+
assertThat(this.filter.shouldNotFilterAsyncDispatch()).isFalse();
6067
}
6168

6269
@Test
@@ -72,6 +79,12 @@ void filterShouldFillObservationContext() throws Exception {
7279
assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS").hasBeenStopped();
7380
}
7481

82+
@Test
83+
void filterShouldOpenScope() throws Exception {
84+
this.mockFilterChain = new MockFilterChain(new ScopeCheckingServlet(this.observationRegistry));
85+
filter.doFilter(this.request, this.response, this.mockFilterChain);
86+
}
87+
7588
@Test
7689
void filterShouldAcceptNoOpObservationContext() throws Exception {
7790
this.filter = new ServerHttpObservationFilter(ObservationRegistry.NOOP);
@@ -136,9 +149,52 @@ void shouldCloseObservationAfterAsyncCompletion() throws Exception {
136149
assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS").hasBeenStopped();
137150
}
138151

152+
@Test
153+
void shouldCloseObservationAfterAsyncError() throws Exception {
154+
this.request.setAsyncSupported(true);
155+
this.request.startAsync();
156+
this.filter.doFilter(this.request, this.response, this.mockFilterChain);
157+
MockAsyncContext asyncContext = (MockAsyncContext) this.request.getAsyncContext();
158+
for (AsyncListener listener : asyncContext.getListeners()) {
159+
listener.onError(new AsyncEvent(this.request.getAsyncContext(), new IllegalStateException("test error")));
160+
}
161+
asyncContext.complete();
162+
assertThatHttpObservation().hasLowCardinalityKeyValue("exception", "IllegalStateException").hasBeenStopped();
163+
}
164+
165+
@Test
166+
void shouldNotCloseObservationDuringAsyncDispatch() throws Exception {
167+
this.mockFilterChain = new MockFilterChain(new ScopeCheckingServlet(this.observationRegistry));
168+
this.request.setDispatcherType(DispatcherType.ASYNC);
169+
this.filter.doFilter(this.request, this.response, this.mockFilterChain);
170+
TestObservationRegistryAssert.assertThat(this.observationRegistry)
171+
.hasObservationWithNameEqualTo("http.server.requests")
172+
.that().isNotStopped();
173+
}
174+
139175
private TestObservationRegistryAssert.TestObservationRegistryAssertReturningObservationContextAssert assertThatHttpObservation() {
176+
TestObservationRegistryAssert.assertThat(this.observationRegistry)
177+
.hasNumberOfObservationsWithNameEqualTo("http.server.requests", 1);
178+
140179
return TestObservationRegistryAssert.assertThat(this.observationRegistry)
141-
.hasObservationWithNameEqualTo("http.server.requests").that();
180+
.hasObservationWithNameEqualTo("http.server.requests")
181+
.that()
182+
.hasBeenStopped();
183+
}
184+
185+
@SuppressWarnings("serial")
186+
static class ScopeCheckingServlet extends HttpServlet {
187+
188+
private final ObservationRegistry observationRegistry;
189+
190+
public ScopeCheckingServlet(ObservationRegistry observationRegistry) {
191+
this.observationRegistry = observationRegistry;
192+
}
193+
194+
@Override
195+
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
196+
assertThat(this.observationRegistry.getCurrentObservation()).isNotNull();
197+
}
142198
}
143199

144200
static class CustomObservationFilter extends ServerHttpObservationFilter {

0 commit comments

Comments
 (0)