下面列出了org.apache.http.client.methods.HttpExecutionAware#com.nike.wingtips.Tracer 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。
protected void unlinkTracingAndMdcFromCurrentThread(ChannelHandlerContext ctx,
Pair<Deque<Span>, Map<String, String>> origThreadInfo) {
// Update the state (if we have any) with the current values of the MDC and tracer data
HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get();
if (state != null) {
// Get references to the *current* MDC and tracer data for storing in our ctx
// and set them on the state object
Map<String, String> currentMdcContextMapForState = MDC.getCopyOfContextMap();
Deque<Span> currentTraceStackForState = Tracer.getInstance().unregisterFromThread();
state.setLoggerMdcContextMap(currentMdcContextMapForState);
state.setDistributedTraceStack(currentTraceStackForState);
}
// Reset the thread to the way it was before linkTracingAndMdcToCurrentThread was called
AsyncNettyHelper.unlinkTracingAndMdcFromCurrentThread(origThreadInfo);
}
@Test
public void linkTracingAndMdcToCurrentThread_should_set_tracing_and_mdc_to_state_values_if_available() {
// given
Map<String, String> stateMdcInfo = new HashMap<>();
stateMdcInfo.put("foo", "bar");
Deque<Span> stateTraceStack = new LinkedList<>();
Span span = Span.generateRootSpanForNewTrace("fooSpanName", LOCAL_ONLY).withTraceId("fooTraceId").build();
stateTraceStack.add(span);
state.setLoggerMdcContextMap(stateMdcInfo);
state.setDistributedTraceStack(stateTraceStack);
assertThat(MDC.getCopyOfContextMap().isEmpty(), is(true));
assertThat(Tracer.getInstance().getCurrentSpan(), nullValue());
// when
handler.linkTracingAndMdcToCurrentThread(ctxMock);
// then
// Tracer adds some stuff to the MDC
stateMdcInfo.put(SpanFieldForLoggerMdc.TRACE_ID.mdcKey, span.getTraceId());
assertThat(MDC.getCopyOfContextMap(), is(stateMdcInfo));
assertThat(Tracer.getInstance().getCurrentSpanStackCopy(), is(stateTraceStack));
}
@Test
public void finalizeAndCompleteOverallRequestSpanAttachedToCurrentThread_does_nothing_if_called_when_current_thread_span_is_already_completed() {
// given
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);
// when
WingtipsSpringWebfluxWebFilter.finalizeAndCompleteOverallRequestSpanAttachedToCurrentThread(
exchange, mock(Throwable.class), tagAndNamingStrategy, tagAndNamingAdapterMock,
Pair.of("foo", "bar")
);
// then
assertThat(strategyResponseTaggingAndFinalSpanNameMethodCalled.get()).isFalse();
assertThat(spanRecorder.completedSpans).isEmpty();
verify(spanMock).isCompleted();
verifyNoMoreInteractions(spanMock);
}
@Before
public void beforeMethod() {
functionMock = mock(Function.class);
inObj = new Object();
outObj = new Object();
throwExceptionDuringCall = false;
currentSpanStackWhenFunctionWasCalled = new ArrayList<>();
currentMdcInfoWhenFunctionWasCalled = new ArrayList<>();
doAnswer(invocation -> {
currentSpanStackWhenFunctionWasCalled.add(Tracer.getInstance().getCurrentSpanStackCopy());
currentMdcInfoWhenFunctionWasCalled.add(MDC.getCopyOfContextMap());
if (throwExceptionDuringCall)
throw new RuntimeException("kaboom");
return outObj;
}).when(functionMock).apply(inObj);
resetTracing();
}
private Pair<ObjectHolder<Span>, ObjectHolder<Span>> setupBeforeAndAfterSpanCaptureForOnThrowable(
CompletableFuture<String> cfMock) throws Throwable {
ObjectHolder<Span> before = new ObjectHolder<>();
ObjectHolder<Span> after = new ObjectHolder<>();
doAnswer(invocation -> {
before.setObj(Tracer.getInstance().getCurrentSpan());
return invocation.callRealMethod();
}).when(circuitBreakerManualTaskMock).handleException(any(Throwable.class));
doAnswer(invocation -> {
after.setObj(Tracer.getInstance().getCurrentSpan());
return invocation.callRealMethod();
}).when(cfMock).completeExceptionally(any(Throwable.class));
return Pair.of(before, after);
}
@Before
public void beforeMethod() {
biFunctionMock = mock(BiFunction.class);
inObj1 = new Object();
inObj2 = new Object();
outObj = new Object();
throwExceptionDuringCall = false;
currentSpanStackWhenFunctionWasCalled = new ArrayList<>();
currentMdcInfoWhenFunctionWasCalled = new ArrayList<>();
doAnswer(invocation -> {
currentSpanStackWhenFunctionWasCalled.add(Tracer.getInstance().getCurrentSpanStackCopy());
currentMdcInfoWhenFunctionWasCalled.add(MDC.getCopyOfContextMap());
if (throwExceptionDuringCall)
throw new RuntimeException("kaboom");
return outObj;
}).when(biFunctionMock).apply(inObj1, inObj2);
resetTracing();
}
@Before
public void beforeMethod() {
channelMock = mock(Channel.class);
ctxMock = mock(ChannelHandlerContext.class);
stateAttributeMock = mock(Attribute.class);
state = new HttpProcessingState();
doReturn(channelMock).when(ctxMock).channel();
doReturn(stateAttributeMock).when(channelMock).attr(ChannelAttributes.HTTP_PROCESSING_STATE_ATTRIBUTE_KEY);
doReturn(state).when(stateAttributeMock).get();
runnableMock = mock(Runnable.class);
throwExceptionDuringCall = false;
currentSpanStackWhenRunnableWasCalled = new ArrayList<>();
currentMdcInfoWhenRunnableWasCalled = new ArrayList<>();
doAnswer(invocation -> {
currentSpanStackWhenRunnableWasCalled.add(Tracer.getInstance().getCurrentSpanStackCopy());
currentMdcInfoWhenRunnableWasCalled.add(MDC.getCopyOfContextMap());
if (throwExceptionDuringCall)
throw new RuntimeException("kaboom");
return null;
}).when(runnableMock).run();
resetTracingAndMdc();
}
@Before
public void beforeMethod() {
channelMock = mock(Channel.class);
ctxMock = mock(ChannelHandlerContext.class);
stateAttributeMock = mock(Attribute.class);
state = new HttpProcessingState();
doReturn(channelMock).when(ctxMock).channel();
doReturn(stateAttributeMock).when(channelMock).attr(ChannelAttributes.HTTP_PROCESSING_STATE_ATTRIBUTE_KEY);
doReturn(state).when(stateAttributeMock).get();
supplierMock = mock(Supplier.class);
outObj = new Object();
throwExceptionDuringCall = false;
currentSpanStackWhenSupplierWasCalled = new ArrayList<>();
currentMdcInfoWhenSupplierWasCalled = new ArrayList<>();
doAnswer(invocation -> {
currentSpanStackWhenSupplierWasCalled.add(Tracer.getInstance().getCurrentSpanStackCopy());
currentMdcInfoWhenSupplierWasCalled.add(MDC.getCopyOfContextMap());
if (throwExceptionDuringCall)
throw new RuntimeException("kaboom");
return outObj;
}).when(supplierMock).get();
resetTracingAndMdc();
}
/**
* Returns the name that should be used for the span surrounding the downstream call. Defaults to {@link
* ProxyRouterSpanNamingAndTaggingStrategy#getInitialSpanNameOverride(HttpRequest, RequestInfo, String, String)}
* if that returns a non-null value, then falls back to whatever {@link
* ProxyRouterSpanNamingAndTaggingStrategy#getInitialSpanName(HttpRequest)} returns, with a ultimate fallback
* of {@link HttpRequestTracingUtils#getFallbackSpanNameForHttpRequest(String, String)} if the naming strategy
* returned null or blank string for both the override and initial span name.
*
* @param downstreamRequest The Netty {@link HttpRequest} for the downstream call.
* @param namingStrategy The {@link ProxyRouterSpanNamingAndTaggingStrategy} being used.
* @return The name that should be used for the span surrounding the downstream call.
*/
protected @NotNull String getSubspanSpanName(
@NotNull HttpRequest downstreamRequest,
@NotNull RequestInfo<?> overallRequest,
@NotNull ProxyRouterSpanNamingAndTaggingStrategy<Span> namingStrategy
) {
String spanNameFromStrategy = namingStrategy.getInitialSpanName(downstreamRequest);
Span overallRequestSpan = Tracer.getInstance().getCurrentSpan();
String overallRequestSpanName = (overallRequestSpan == null) ? null : overallRequestSpan.getSpanName();
String spanNameOverride = namingStrategy.getInitialSpanNameOverride(
downstreamRequest, overallRequest, spanNameFromStrategy, overallRequestSpanName
);
if (StringUtils.isNotBlank(spanNameFromStrategy)) {
// We got a span name from the strategy. See if it should be overridden.
if (StringUtils.isNotBlank(spanNameOverride)) {
return spanNameOverride;
}
// No override, so just use the name from the strategy.
return spanNameFromStrategy;
}
// The naming strategy didn't have anything for us at all. See if there's an override.
if (StringUtils.isNotBlank(spanNameOverride)) {
return spanNameOverride;
}
// The naming strategy didn't have anything for us and there was no override. Fall back to something reasonable.
return getFallbackSpanName(downstreamRequest);
}
@DataProvider(value = {
"true",
"false"
})
@Test
public void apply_handles_tracing_and_mdc_info_as_expected(boolean throwException) {
// given
throwExceptionDuringCall = throwException;
Tracer.getInstance().startRequestWithRootSpan("foo");
Deque<Span> spanStack = Tracer.getInstance().getCurrentSpanStackCopy();
Map<String, String> mdcInfo = MDC.getCopyOfContextMap();
ConsumerWithTracing instance = new ConsumerWithTracing(
consumerMock, spanStack, mdcInfo
);
resetTracing();
assertThat(Tracer.getInstance().getCurrentSpanStackCopy()).isNull();
assertThat(MDC.getCopyOfContextMap()).isNullOrEmpty();
// when
Throwable ex = catchThrowable(() -> instance.accept(inObj));
// then
verify(consumerMock).accept(inObj);
if (throwException) {
assertThat(ex).isNotNull();
}
else {
assertThat(ex).isNull();
}
assertThat(currentSpanStackWhenConsumerWasCalled.get(0)).isEqualTo(spanStack);
assertThat(currentMdcInfoWhenConsumerWasCalled.get(0)).isEqualTo(mdcInfo);
assertThat(Tracer.getInstance().getCurrentSpanStackCopy()).isNull();
assertThat(MDC.getCopyOfContextMap()).isNullOrEmpty();
}
@DataProvider(value = {
"true",
"false"
})
@Test
public void onSuccess_handles_tracing_and_mdc_info_as_expected(boolean throwException) {
// given
throwExceptionDuringCall = throwException;
Tracer.getInstance().startRequestWithRootSpan("foo");
Deque<Span> spanStack = Tracer.getInstance().getCurrentSpanStackCopy();
Map<String, String> mdcInfo = MDC.getCopyOfContextMap();
SuccessCallbackWithTracing instance = new SuccessCallbackWithTracing(
successCallbackMock, spanStack, mdcInfo
);
resetTracing();
assertThat(Tracer.getInstance().getCurrentSpanStackCopy()).isNull();
assertThat(MDC.getCopyOfContextMap()).isNullOrEmpty();
// when
Throwable ex = catchThrowable(() -> instance.onSuccess(inObj));
// then
verify(successCallbackMock).onSuccess(inObj);
if (throwException) {
assertThat(ex).isNotNull();
}
else {
assertThat(ex).isNull();
}
assertThat(currentSpanStackWhenSuccessCallbackWasCalled.get(0)).isEqualTo(spanStack);
assertThat(currentMdcInfoWhenSuccessCallbackWasCalled.get(0)).isEqualTo(mdcInfo);
assertThat(Tracer.getInstance().getCurrentSpanStackCopy()).isNull();
assertThat(MDC.getCopyOfContextMap()).isNullOrEmpty();
}
@Test
public void filter_completes_the_overall_request_span_if_unexpected_exception_occurs() {
// given
Throwable exFromFilterChain = new RuntimeException("Intentional test exception.");
doThrow(exFromFilterChain).when(chainMock).filter(exchange);
AtomicReference<Span> expectedSpanRef = new AtomicReference<>();
doAnswer(invocation -> {
Span overallRequestSpan = Tracer.getInstance().startRequestWithRootSpan("fooOverallRequestSpan");
expectedSpanRef.set(overallRequestSpan);
return overallRequestSpan;
}).when(filterSpy).createNewSpanForRequest(exchange);
// when
Throwable ex = catchThrowable(() -> filterSpy.filter(exchange, chainMock));
// then
verify(chainMock).filter(exchange);
assertThat(ex).isSameAs(exFromFilterChain);
// Make sure createNewSpanForRequest() was called and get the expected overall request span.
verify(filterSpy).createNewSpanForRequest(exchange);
Span expectedSpan = expectedSpanRef.get();
// The overall request span should be completed at this point due to the catch block in the filter,
// and it should have been tagged appropriately.
assertThat(expectedSpan.isCompleted()).isTrue();
assertThat(spanRecorder.completedSpans).isEqualTo(singletonList(expectedSpan));
assertThat(strategyResponseTaggingAndFinalSpanNameMethodCalled.get()).isTrue();
strategyResponseTaggingArgs.get().verifyArgs(
expectedSpan, exchange, responseMock, exFromFilterChain, tagAndNamingAdapterMock
);
// The tracing state on this thread should have been cleaned up before the filter() method returned.
assertThat(Tracer.getInstance().getCurrentSpan()).isNull();
}
private void removeSpanRecorderLifecycleListener() {
List<SpanLifecycleListener> listeners = new ArrayList<>(Tracer.getInstance().getSpanLifecycleListeners());
for (SpanLifecycleListener listener : listeners) {
if (listener instanceof SpanRecorder) {
Tracer.getInstance().removeSpanLifecycleListener(listener);
}
}
}
@Before
public void beforeMethod() {
resetTracing();
spanRecorder = new SpanRecorder();
Tracer.getInstance().addSpanLifecycleListener(spanRecorder);
dtConfigAdjustments = new DtConfigAdjustments();
adjustableServerTaggingStrategy.config = dtConfigAdjustments;
adjustableProxyTaggingStrategy.config = dtConfigAdjustments;
}
private void removeSpanRecorderLifecycleListener() {
List<SpanLifecycleListener> listeners = new ArrayList<>(Tracer.getInstance().getSpanLifecycleListeners());
for (SpanLifecycleListener listener : listeners) {
if (listener instanceof SpanRecorder) {
Tracer.getInstance().removeSpanLifecycleListener(listener);
}
}
}
private void removeSpanRecorderLifecycleListener() {
List<SpanLifecycleListener> listeners = new ArrayList<>(Tracer.getInstance().getSpanLifecycleListeners());
for (SpanLifecycleListener listener : listeners) {
if (listener instanceof SpanRecorder) {
Tracer.getInstance().removeSpanLifecycleListener(listener);
}
}
}
@Test
public void doFilterInternal_should_not_complete_span_or_response_tags_until_after_filter_chain_runs(
) throws ServletException, IOException {
// given: filter and filter chain that can tell us whether or not the span is complete at the time it is called
RequestTracingFilter filter = getBasicFilter();
AtomicBoolean spanCompletedHolder = new AtomicBoolean(false);
AtomicReference<Span> spanHolder = new AtomicReference<>();
AtomicReference<Boolean> requestTagsExecutedAtTimeOfFilterChain = new AtomicReference<>();
AtomicReference<Boolean> responseTagsExecutedAtTimeOfFilterChain = new AtomicReference<>();
FilterChain smartFilterChain = (request, response) -> {
Span span = Tracer.getInstance().getCurrentSpan();
spanHolder.set(span);
if (span != null) {
spanCompletedHolder.set(span.isCompleted());
}
requestTagsExecutedAtTimeOfFilterChain.set(strategyRequestTaggingMethodCalled.get());
responseTagsExecutedAtTimeOfFilterChain.set(strategyResponseTaggingAndFinalSpanNameMethodCalled.get());
};
// when: doFilterInternal is called
filter.doFilterInternal(requestMock, responseMock, smartFilterChain);
// then: we should be able to validate that the smartFilterChain was called, and when it was called the span
// had not yet been completed, and after doFilterInternal finished it was completed. Similarly, when
// the chain is being run, request tags should be done but response tags should not.
assertThat(spanHolder.get()).isNotNull();
assertThat(spanCompletedHolder.get()).isFalse();
assertThat(spanHolder.get().isCompleted()).isTrue();
assertThat(requestTagsExecutedAtTimeOfFilterChain.get()).isTrue();
assertThat(responseTagsExecutedAtTimeOfFilterChain.get()).isFalse();
}
@DataProvider(value = {
"true",
"false"
})
@Test
public void apply_handles_tracing_and_mdc_info_as_expected(boolean throwException) {
// given
throwExceptionDuringCall = throwException;
Tracer.getInstance().startRequestWithRootSpan("foo");
Deque<Span> spanStack = Tracer.getInstance().getCurrentSpanStackCopy();
Map<String, String> mdcInfo = MDC.getCopyOfContextMap();
BiConsumerWithTracing instance = new BiConsumerWithTracing(
biConsumerMock, spanStack, mdcInfo
);
resetTracing();
assertThat(Tracer.getInstance().getCurrentSpanStackCopy()).isNull();
assertThat(MDC.getCopyOfContextMap()).isNullOrEmpty();
// when
Throwable ex = catchThrowable(() -> instance.accept(inObj1, inObj2));
// then
verify(biConsumerMock).accept(inObj1, inObj2);
if (throwException) {
assertThat(ex).isNotNull();
}
else {
assertThat(ex).isNull();
}
assertThat(currentSpanStackWhenBiConsumerWasCalled.get(0)).isEqualTo(spanStack);
assertThat(currentMdcInfoWhenBiConsumerWasCalled.get(0)).isEqualTo(mdcInfo);
assertThat(Tracer.getInstance().getCurrentSpanStackCopy()).isNull();
assertThat(MDC.getCopyOfContextMap()).isNullOrEmpty();
}
private WingtipsWebFilterTracingSubscriber setupSubscriberWrapper() {
Tracer.getInstance().startRequestWithRootSpan("TracingState for Subscriber wrapper root span");
@SuppressWarnings("unchecked")
WingtipsWebFilterTracingSubscriber result =
new WingtipsWebFilterTracingSubscriber(
mock(CoreSubscriber.class), exchange, mock(Context.class), TracingState.getCurrentThreadTracingState(),
tagAndNamingStrategy, tagAndNamingAdapterMock
);
Tracer.getInstance().unregisterFromThread();
return result;
}
@Before
public void beforeMethod() {
handlerSpy = spy(new DTraceEndHandler());
channelMock = mock(Channel.class);
ctxMock = mock(ChannelHandlerContext.class);
stateAttributeMock = mock(Attribute.class);
state = new HttpProcessingState();
doReturn(channelMock).when(ctxMock).channel();
doReturn(stateAttributeMock).when(channelMock).attr(ChannelAttributes.HTTP_PROCESSING_STATE_ATTRIBUTE_KEY);
doReturn(state).when(stateAttributeMock).get();
resetTracingAndMdc();
distributedTracingConfigMock = mock(DistributedTracingConfig.class);
state.setDistributedTracingConfig(distributedTracingConfigMock);
responseInfoMock = mock(ResponseInfo.class);
doReturn(true).when(responseInfoMock).isResponseSendingLastChunkSent();
state.setResponseInfo(responseInfoMock, null);
lastChunkChannelFutureMock = mock(ChannelFuture.class);
state.setResponseWriterFinalChunkChannelFuture(lastChunkChannelFutureMock);
doAnswer(invocation -> {
currentSpanWhenCompleteCurrentSpanWasCalled = Tracer.getInstance().getCurrentSpan();
invocation.callRealMethod();
currentSpanAfterCompleteCurrentSpanWasCalled = Tracer.getInstance().getCurrentSpan();
return null;
}).when(handlerSpy).completeCurrentSpan();
}
@Test
public void completeSubspanAttachedToCurrentThread_does_nothing_if_called_when_current_thread_span_is_null() {
// given
assertThat(Tracer.getInstance().getCurrentSpan()).isNull();
// when
WingtipsSpringWebfluxExchangeFilterFunction.completeSubspanAttachedToCurrentThread(
request, responseMock, mock(Throwable.class), tagAndNamingStrategy, tagAndNamingAdapterMock,
Pair.of("foo", "bar")
);
// then
assertThat(strategyResponseTaggingAndFinalSpanNameMethodCalled.get()).isFalse();
assertThat(spanRecorder.completedSpans).isEmpty();
}
@Test
public void completeSubspan_works_as_expected() {
// given
Tracer.getInstance().startRequestWithRootSpan("fooRootSpan");
Span currentSpanForCompletion = Tracer.getInstance().startSubSpan("fooSubspan", SpanPurpose.LOCAL_ONLY);
TracingState expectedTracingStateForCompletion = TracingState.getCurrentThreadTracingState();
// Now that the tracing-state-for-completion has been setup, setup the current thread tracing state to
// something completely different.
Tracer.getInstance().unregisterFromThread();
Tracer.getInstance().startRequestWithRootSpan("someCompletelyDifferentRootSpan");
TracingState baseTracingState = TracingState.getCurrentThreadTracingState();
Throwable error = mock(Throwable.class);
// when
filterSpy.completeSubspan(expectedTracingStateForCompletion, request, responseMock, error);
// then
assertThat(strategyResponseTaggingAndFinalSpanNameMethodCalled.get()).isTrue();
strategyResponseTaggingArgs.get().verifyArgs(
currentSpanForCompletion, request, responseMock, error, tagAndNamingAdapterMock
);
assertThat(currentSpanForCompletion.isCompleted()).isTrue();
assertThat(spanRecorder.completedSpans).isEqualTo(singletonList(currentSpanForCompletion));
// The base tracing state should have been restored right before the method call ended.
assertThat(TracingState.getCurrentThreadTracingState()).isEqualTo(baseTracingState);
}
@DataProvider(value = {
"true",
"false"
})
@Test
public void onSuccess_handles_tracing_and_mdc_info_as_expected(boolean throwException) {
// given
throwExceptionDuringCall = throwException;
Tracer.getInstance().startRequestWithRootSpan("foo");
Deque<Span> spanStack = Tracer.getInstance().getCurrentSpanStackCopy();
Map<String, String> mdcInfo = MDC.getCopyOfContextMap();
ListenableFutureCallbackWithTracing instance = new ListenableFutureCallbackWithTracing(
listenableFutureCallbackMock, spanStack, mdcInfo
);
resetTracing();
assertThat(Tracer.getInstance().getCurrentSpanStackCopy()).isNull();
assertThat(MDC.getCopyOfContextMap()).isNullOrEmpty();
// when
Throwable ex = catchThrowable(() -> instance.onSuccess(successInObj));
// then
verify(listenableFutureCallbackMock).onSuccess(successInObj);
if (throwException) {
assertThat(ex).isNotNull();
}
else {
assertThat(ex).isNull();
}
assertThat(currentSpanStackWhenListenableFutureCallbackWasCalled.get(0)).isEqualTo(spanStack);
assertThat(currentMdcInfoWhenListenableFutureCallbackWasCalled.get(0)).isEqualTo(mdcInfo);
assertThat(Tracer.getInstance().getCurrentSpanStackCopy()).isNull();
assertThat(MDC.getCopyOfContextMap()).isNullOrEmpty();
}
private WingtipsExchangeFilterFunctionTracingCompletionSubscriber setupSubscriberWrapper() {
Tracer.getInstance().startRequestWithRootSpan("TracingState for Subscriber wrapper root span");
Tracer.getInstance().startSubSpan("TracingState for Subscriber wrapper subspan", SpanPurpose.LOCAL_ONLY);
@SuppressWarnings("unchecked")
WingtipsExchangeFilterFunctionTracingCompletionSubscriber result =
new WingtipsExchangeFilterFunctionTracingCompletionSubscriber(
mock(CoreSubscriber.class), request, mock(Context.class), TracingState.getCurrentThreadTracingState(),
tagAndNamingStrategy, tagAndNamingAdapterMock
);
Tracer.getInstance().unregisterFromThread();
return result;
}
@Test
public void constructor_sets_values_exactly_as_given_when_subtracing_is_off() {
// given
CompletableFuture cfResponse = mock(CompletableFuture.class);
AsyncResponseHandler responseHandlerFunc = mock(AsyncResponseHandler.class);
RequestBuilderWrapper rbwMock = mock(RequestBuilderWrapper.class);
Optional<CircuitBreaker<Response>> circuitBreaker = Optional.of(mock(CircuitBreaker.class));
Deque<Span> spanStack = mock(Deque.class);
Map<String, String> mdcInfo = mock(Map.class);
Deque<Span> spanStackBeforeCall = Tracer.getInstance().getCurrentSpanStackCopy();
Map<String, String> mdcInfoBeforeCall = MDC.getCopyOfContextMap();
// when
AsyncCompletionHandlerWithTracingAndMdcSupport instance = new AsyncCompletionHandlerWithTracingAndMdcSupport(
cfResponse, responseHandlerFunc, false, rbwMock, circuitBreaker, spanStack, mdcInfo,
tagAndNamingStrategy
);
// then
assertThat(instance.completableFutureResponse).isSameAs(cfResponse);
assertThat(instance.responseHandlerFunction).isSameAs(responseHandlerFunc);
assertThat(instance.performSubSpanAroundDownstreamCalls).isEqualTo(false);
assertThat(instance.circuitBreakerManualTask).isSameAs(circuitBreaker);
assertThat(instance.distributedTraceStackToUse).isSameAs(spanStack);
assertThat(instance.mdcContextToUse).isSameAs(mdcInfo);
assertThat(Tracer.getInstance().getCurrentSpanStackCopy()).isEqualTo(spanStackBeforeCall);
assertThat(MDC.getCopyOfContextMap()).isEqualTo(mdcInfoBeforeCall);
}
@UseDataProvider("createNewSpanForRequestScenarioDataProvider")
@Test
public void createNewSpanForRequest_works_as_expected(CreateNewSpanForRequestScenario scenario) {
// given
doReturn(scenario.incomingTraceIdHeader).when(requestHeadersMock).getFirst(TraceHeaders.TRACE_ID);
doReturn(scenario.incomingSpanIdHeader).when(requestHeadersMock).getFirst(TraceHeaders.SPAN_ID);
doReturn(scenario.incomingSampledHeader).when(requestHeadersMock).getFirst(TraceHeaders.TRACE_SAMPLED);
doReturn(scenario.incomingUserIdHeader).when(requestHeadersMock).getFirst("user-id");
doReturn(scenario.incomingAltUserIdHeader).when(requestHeadersMock).getFirst("alt-user-id");
assertThat(Tracer.getInstance().getCurrentSpan()).isNull();
// when
Span result = filterSpy.createNewSpanForRequest(exchange);
// then
if (scenario.incomingTraceIdHeader != null) {
assertThat(result.getTraceId()).isEqualTo(scenario.incomingTraceIdHeader);
}
assertThat(result.getParentSpanId()).isEqualTo(scenario.expectedParentSpanId);
assertThat(result.isSampleable()).isEqualTo(scenario.expectedSampleableValue);
assertThat(result.getUserId()).isEqualTo(scenario.expectedUserId);
if (scenario.incomingTraceIdHeader != null && scenario.incomingSpanIdHeader == null) {
assertThat(HttpRequestTracingUtils.hasInvalidParentIdBecauseCallerDidNotSendSpanId(result)).isTrue();
assertThat(result.getTags().get(CHILD_OF_SPAN_FROM_HEADERS_WHERE_CALLER_DID_NOT_SEND_SPAN_ID_TAG_KEY))
.isEqualTo("true");
}
// The current thread tracing state should be setup appropriately.
assertThat(Tracer.getInstance().getCurrentSpan()).isSameAs(result);
}
public static ChannelHandlerContextMocks mockChannelHandlerContextWithTraceInfo(String userId) {
if (Tracer.getInstance().getCurrentSpan() == null) {
Tracer.getInstance().startRequestWithRootSpan("mockChannelHandlerContext", userId);
}
ChannelHandlerContextMocks channelHandlerMocks = mockChannelHandlerContext();
when(channelHandlerMocks.mockHttpProcessingState.getLoggerMdcContextMap()).thenReturn(MDC.getCopyOfContextMap());
when(channelHandlerMocks.mockHttpProcessingState.getDistributedTraceStack()).thenReturn(Tracer.getInstance().getCurrentSpanStackCopy());
return channelHandlerMocks;
}
@DataProvider(value = {
"true",
"false"
})
@Test
public void run_handles_tracing_and_mdc_info_as_expected(boolean throwException) {
// given
throwExceptionDuringCall = throwException;
Tracer.getInstance().startRequestWithRootSpan("foo");
Deque<Span> spanStack = Tracer.getInstance().getCurrentSpanStackCopy();
Map<String, String> mdcInfo = MDC.getCopyOfContextMap();
final RunnableWithTracing instance = new RunnableWithTracing(
runnableMock, spanStack, mdcInfo
);
resetTracing();
assertThat(Tracer.getInstance().getCurrentSpanStackCopy()).isNull();
assertThat(MDC.getCopyOfContextMap()).isNullOrEmpty();
// when
Throwable ex = catchThrowable(new ThrowingCallable() {
@Override
public void call() throws Throwable {
instance.run();
}
});
// then
verify(runnableMock).run();
if (throwException)
assertThat(ex).isNotNull();
else
assertThat(ex).isNull();
assertThat(currentSpanStackWhenRunnableWasCalled.get(0)).isEqualTo(spanStack);
assertThat(currentMdcInfoWhenRunnableWasCalled.get(0)).isEqualTo(mdcInfo);
assertThat(Tracer.getInstance().getCurrentSpanStackCopy()).isNull();
assertThat(MDC.getCopyOfContextMap()).isNullOrEmpty();
}
private Pair<Deque<Span>, Map<String, String>> generateTracingAndMdcInfo() {
resetTracingAndMdc();
Tracer.getInstance().startRequestWithRootSpan("someSpan");
Pair<Deque<Span>, Map<String, String>> result = Pair.of(
Tracer.getInstance().getCurrentSpanStackCopy(), new HashMap<>(MDC.getCopyOfContextMap())
);
resetTracingAndMdc();
return result;
}
@UseDataProvider("extraCustomTagsScenarioDataProvider")
@Test
public void completeSubspanAttachedToCurrentThread_works_as_expected_happy_path(
ExtraCustomTagsScenario scenario
) {
// given
Span rootSpan = Tracer.getInstance().startRequestWithRootSpan("fooRootSpan");
Span currentSpan = Tracer.getInstance().startSubSpan("fooSubspan", SpanPurpose.LOCAL_ONLY);
Throwable error = mock(Throwable.class);
// when
WingtipsSpringWebfluxExchangeFilterFunction.completeSubspanAttachedToCurrentThread(
request, responseMock, error, tagAndNamingStrategy, tagAndNamingAdapterMock, scenario.extraCustomTags
);
// then
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);
}