Fix traceId discrepancy in case error in servlet web

Signed-off-by: Nikita Konev <nikit.cpp@yandex.ru>
This commit is contained in:
Nikita Konev 2025-05-18 15:57:28 +03:00
parent 16c979460e
commit b910a54378
2 changed files with 71 additions and 1 deletions

View File

@ -46,13 +46,14 @@ import org.springframework.util.StringUtils;
* wraps the chain in before and after observations
*
* @author Josh Cummings
* @author Nikita Konev
* @since 6.0
*/
public final class ObservationFilterChainDecorator implements FilterChainProxy.FilterChainDecorator {
private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
private static final String ATTRIBUTE = ObservationFilterChainDecorator.class + ".observation";
static final String ATTRIBUTE = ObservationFilterChainDecorator.class + ".observation";
static final String UNSECURED_OBSERVATION_NAME = "spring.security.http.unsecured.requests";
@ -250,6 +251,16 @@ public final class ObservationFilterChainDecorator implements FilterChainProxy.F
private AroundFilterObservation parent(HttpServletRequest request) {
FilterChainObservationContext beforeContext = FilterChainObservationContext.before();
FilterChainObservationContext afterContext = FilterChainObservationContext.after();
AroundFilterObservation existingParentObservation = (AroundFilterObservation) request
.getAttribute(ATTRIBUTE);
if (existingParentObservation != null) {
beforeContext
.setParentObservation(existingParentObservation.before().getContext().getParentObservation());
afterContext
.setParentObservation(existingParentObservation.after().getContext().getParentObservation());
}
Observation before = Observation.createNotStarted(this.convention, () -> beforeContext, this.registry);
Observation after = Observation.createNotStarted(this.convention, () -> afterContext, this.registry);
AroundFilterObservation parent = AroundFilterObservation.create(before, after);

View File

@ -310,6 +310,65 @@ public class FilterChainProxyTests {
assertFilterChainObservation(contexts.next(), "after", 1);
}
// gh-12610
@Test
void parentObservationIsTakenIntoAccountDuringDispatchError() throws Exception {
ObservationHandler<Observation.Context> handler = mock(ObservationHandler.class);
given(handler.supportsContext(any())).willReturn(true);
ObservationRegistry registry = ObservationRegistry.create();
registry.observationConfig().observationHandler(handler);
given(this.matcher.matches(any())).willReturn(true);
SecurityFilterChain sec = new DefaultSecurityFilterChain(this.matcher, Arrays.asList(this.filter));
FilterChainProxy fcp = new FilterChainProxy(sec);
fcp.setFilterChainDecorator(new ObservationFilterChainDecorator(registry));
Filter initialFilter = ObservationFilterChainDecorator.FilterObservation
.create(Observation.createNotStarted("wrap", registry))
.wrap(fcp);
ServletRequest initialRequest = new MockHttpServletRequest("GET", "/");
initialFilter.doFilter(initialRequest, new MockHttpServletResponse(), this.chain);
// simulate request attribute copying in case dispatching to ERROR
ObservationFilterChainDecorator.AroundFilterObservation parentObservation = (ObservationFilterChainDecorator.AroundFilterObservation) initialRequest
.getAttribute(ObservationFilterChainDecorator.ATTRIBUTE);
assertThat(parentObservation).isNotNull();
// simulate dispatching error-related request
Filter errorRelatedFilter = ObservationFilterChainDecorator.FilterObservation
.create(Observation.createNotStarted("wrap", registry))
.wrap(fcp);
ServletRequest errorRelatedRequest = new MockHttpServletRequest("GET", "/error");
errorRelatedRequest.setAttribute(ObservationFilterChainDecorator.ATTRIBUTE, parentObservation);
errorRelatedFilter.doFilter(errorRelatedRequest, new MockHttpServletResponse(), this.chain);
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
verify(handler, times(8)).onStart(captor.capture());
verify(handler, times(8)).onStop(any());
List<Observation.Context> contexts = captor.getAllValues();
Observation.Context initialRequestObservationContextBefore = contexts.get(1);
Observation.Context initialRequestObservationContextAfter = contexts.get(3);
assertFilterChainObservation(initialRequestObservationContextBefore, "before", 1);
assertFilterChainObservation(initialRequestObservationContextAfter, "after", 1);
assertThat(initialRequestObservationContextBefore.getParentObservation()).isNotNull();
assertThat(initialRequestObservationContextBefore.getParentObservation())
.isSameAs(initialRequestObservationContextAfter.getParentObservation());
Observation.Context errorRelatedRequestObservationContextBefore = contexts.get(5);
Observation.Context errorRelatedRequestObservationContextAfter = contexts.get(7);
assertFilterChainObservation(errorRelatedRequestObservationContextBefore, "before", 1);
assertFilterChainObservation(errorRelatedRequestObservationContextAfter, "after", 1);
assertThat(errorRelatedRequestObservationContextBefore.getParentObservation()).isNotNull();
assertThat(errorRelatedRequestObservationContextBefore.getParentObservation())
.isSameAs(initialRequestObservationContextBefore.getParentObservation());
assertThat(errorRelatedRequestObservationContextAfter.getParentObservation()).isNotNull();
assertThat(errorRelatedRequestObservationContextAfter.getParentObservation())
.isSameAs(initialRequestObservationContextBefore.getParentObservation());
}
@Test
public void doFilterWhenMultipleFiltersThenObservationRegistryObserves() throws Exception {
ObservationHandler<Observation.Context> handler = mock(ObservationHandler.class);