WingtipsSpringWebfluxExchangeFilterFunction implements ExchangeFilterFunction { @SafeVarargs protected static void completeSubspanAttachedToCurrentThread( @NotNull ClientRequest request, @Nullable ClientResponse response, @Nullable Throwable error, @NotNull HttpTagAndSpanNamingStrategy<ClientRequest, ClientResponse> tagAndNamingStrategy, @NotNull HttpTagAndSpanNamingAdapter<ClientRequest, ClientResponse> tagAndNamingAdapter, Pair<String, String>... extraCustomTags ) { Span span = Tracer.getInstance().getCurrentSpan(); if (span == null) { return; } if (span.isCompleted()) { return; } try { if (extraCustomTags != null) { for (Pair<String, String> tag : extraCustomTags) { span.putTag(tag.getKey(), tag.getValue()); } } tagAndNamingStrategy.handleResponseTaggingAndFinalSpanName( span, request, response, error, tagAndNamingAdapter ); } finally { span.close(); } } WingtipsSpringWebfluxExchangeFilterFunction();  WingtipsSpringWebfluxExchangeFilterFunction(boolean surroundCallsWithSubspan); @SuppressWarnings("ConstantConditions")  WingtipsSpringWebfluxExchangeFilterFunction(
        boolean surroundCallsWithSubspan,
        @NotNull HttpTagAndSpanNamingStrategy<ClientRequest, ClientResponse> tagAndNamingStrategy,
        @NotNull HttpTagAndSpanNamingAdapter<ClientRequest, ClientResponse> tagAndNamingAdapter
    ); @Override @NotNull Mono<ClientResponse> filter(
        @NotNull ClientRequest request, @NotNull ExchangeFunction next
    ); static final @NotNull WingtipsSpringWebfluxExchangeFilterFunction DEFAULT_IMPL; }
@UseDataProvider("extraCustomTagsScenarioDataProvider") @Test public void completeSubspanAttachedToCurrentThread_works_as_expected_happy_path( ExtraCustomTagsScenario scenario ) { Span rootSpan = Tracer.getInstance().startRequestWithRootSpan("fooRootSpan"); Span currentSpan = Tracer.getInstance().startSubSpan("fooSubspan", SpanPurpose.LOCAL_ONLY); Throwable error = mock(Throwable.class); WingtipsSpringWebfluxExchangeFilterFunction.completeSubspanAttachedToCurrentThread( request, responseMock, error, tagAndNamingStrategy, tagAndNamingAdapterMock, scenario.extraCustomTags ); assertThat(strategyResponseTaggingAndFinalSpanNameMethodCalled.get()).isTrue(); strategyResponseTaggingArgs.get().verifyArgs( currentSpan, request, responseMock, error, tagAndNamingAdapterMock ); if (scenario.extraCustomTags != null) { for (Pair<String, String> expectedTag : scenario.extraCustomTags) { assertThat(currentSpan.getTags().get(expectedTag.getKey())).isEqualTo(expectedTag.getValue()); } } assertThat(currentSpan.isCompleted()).isTrue(); assertThat(spanRecorder.completedSpans).isEqualTo(singletonList(currentSpan)); assertThat(Tracer.getInstance().getCurrentSpan()).isEqualTo(rootSpan); }
@Test public void completeSubspanAttachedToCurrentThread_does_nothing_if_called_when_current_thread_span_is_null() { assertThat(Tracer.getInstance().getCurrentSpan()).isNull(); WingtipsSpringWebfluxExchangeFilterFunction.completeSubspanAttachedToCurrentThread( request, responseMock, mock(Throwable.class), tagAndNamingStrategy, tagAndNamingAdapterMock, Pair.of("foo", "bar") ); assertThat(strategyResponseTaggingAndFinalSpanNameMethodCalled.get()).isFalse(); assertThat(spanRecorder.completedSpans).isEmpty(); }
@Test public void completeSubspanAttachedToCurrentThread_does_nothing_if_called_when_current_thread_span_is_already_completed() { Span spanMock = mock(Span.class); Tracer.getInstance().registerWithThread(new ArrayDeque<>(singleton(spanMock))); reset(spanMock); doReturn(true).when(spanMock).isCompleted(); assertThat(Tracer.getInstance().getCurrentSpan()).isSameAs(spanMock); WingtipsSpringWebfluxExchangeFilterFunction.completeSubspanAttachedToCurrentThread( request, responseMock, mock(Throwable.class), tagAndNamingStrategy, tagAndNamingAdapterMock, Pair.of("foo", "bar") ); assertThat(strategyResponseTaggingAndFinalSpanNameMethodCalled.get()).isFalse(); assertThat(spanRecorder.completedSpans).isEmpty(); verify(spanMock).isCompleted(); verifyNoMoreInteractions(spanMock); }
@Test public void completeSubspanAttachedToCurrentThread_completes_span_even_if_highly_improbable_exception_occurs() { Span rootSpan = Tracer.getInstance().startRequestWithRootSpan("fooRootSpan"); Span currentSpan = Tracer.getInstance().startSubSpan("fooSubspan", SpanPurpose.LOCAL_ONLY); RuntimeException improbableException = new RuntimeException("Intentional test exception"); @SuppressWarnings("unchecked") Pair<String, String> explodingTag = mock(Pair.class); doThrow(improbableException).when(explodingTag).getKey(); Throwable ex = catchThrowable( () -> WingtipsSpringWebfluxExchangeFilterFunction.completeSubspanAttachedToCurrentThread( request, responseMock, mock(Throwable.class), tagAndNamingStrategy, tagAndNamingAdapterMock, explodingTag ) ); assertThat(ex).isSameAs(improbableException); assertThat(currentSpan.isCompleted()).isTrue(); assertThat(spanRecorder.completedSpans).isEqualTo(singletonList(currentSpan)); assertThat(Tracer.getInstance().getCurrentSpan()).isEqualTo(rootSpan); }