/** * @param surroundCallsWithSubspan Pass in true to have the returned {@link AsyncRestTemplate} surround all calls * with a subspan and propagate the subspan's tracing info, or false to have only the current span propagated at * the time of the call (no subspan). * @return A new {@link AsyncRestTemplate} instance with a {@link WingtipsAsyncClientHttpRequestInterceptor} * already added and with the subspan option on or off depending on the value of the {@code * surroundCallsWithSubspan} argument, and using the default {@link HttpTagAndSpanNamingStrategy} and * {@link HttpTagAndSpanNamingAdapter} ({@link ZipkinHttpTagStrategy} and {@link SpringHttpClientTagAdapter}). */ public static AsyncRestTemplate createTracingEnabledAsyncRestTemplate(boolean surroundCallsWithSubspan) { AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(); asyncRestTemplate.getInterceptors().add( new WingtipsAsyncClientHttpRequestInterceptor(surroundCallsWithSubspan) ); return asyncRestTemplate; }
@Override @SuppressWarnings("deprecation") public ListenableFuture<ClientHttpResponse> intercept( HttpRequest request, byte[] body, AsyncClientHttpRequestExecution execution ) throws IOException { // We need to wrap the request with HttpRequestWrapperWithModifiableHeaders so that tracing info can be // propagated on the headers. HttpRequestWrapperWithModifiableHeaders wrapperRequest = new HttpRequestWrapperWithModifiableHeaders(request); if (surroundCallsWithSubspan) { return createAsyncSubSpanAndExecute(wrapperRequest, body, execution); } return propagateTracingHeadersAndExecute(wrapperRequest, body, execution); }
@Test public void createAsyncSubSpanAndExecute_trigger_null_subspanFinisher_in_catch_block_branch_for_code_coverage() { // given Tracer.getInstance().startRequestWithRootSpan("someRootSpan"); TracingState tracingStateBeforeInterceptorCall = TracingState.getCurrentThreadTracingState(); WingtipsAsyncClientHttpRequestInterceptor interceptorSpy = spy(new WingtipsAsyncClientHttpRequestInterceptor( true, tagAndNamingStrategy, tagAndNamingAdapterMock )); RuntimeException explodingSubspanNameMethodEx = new RuntimeException("Intentional exception thrown by getSubspanSpanName()"); doThrow(explodingSubspanNameMethodEx).when(interceptorSpy).getSubspanSpanName( any(HttpRequest.class), any(HttpTagAndSpanNamingStrategy.class), any(HttpTagAndSpanNamingAdapter.class) ); HttpRequestWrapperWithModifiableHeaders wrapperRequest = new HttpRequestWrapperWithModifiableHeaders(requestMock); byte[] body = new byte[]{42}; // when Throwable ex = catchThrowable( () -> interceptorSpy.createAsyncSubSpanAndExecute(wrapperRequest, body, executionMock) ); // then assertThat(ex).isSameAs(explodingSubspanNameMethodEx); verify(interceptorSpy).getSubspanSpanName(wrapperRequest, tagAndNamingStrategy, tagAndNamingAdapterMock); // TracingState should have been reset even though an exception occurred in some unexpected place. assertThat(normalizeTracingState(TracingState.getCurrentThreadTracingState())) .isEqualTo(normalizeTracingState(tracingStateBeforeInterceptorCall)); }
@DataProvider(value = { "spanNameFromStrategy | PATCH | spanNameFromStrategy", "null | PATCH | asyncresttemplate_downstream_call-PATCH", " | PATCH | asyncresttemplate_downstream_call-PATCH", "[whitespace] | PATCH | asyncresttemplate_downstream_call-PATCH", "null | null | asyncresttemplate_downstream_call-UNKNOWN_HTTP_METHOD", }, splitBy = "\\|") @Test public void getSubspanSpanName_works_as_expected( String strategyResult, HttpMethod httpMethod, String expectedResult ) { // given if ("[whitespace]".equals(strategyResult)) { strategyResult = " \n\r\t "; } initialSpanNameFromStrategy.set(strategyResult); doReturn(httpMethod).when(requestMock).getMethod(); WingtipsAsyncClientHttpRequestInterceptor interceptor = new WingtipsAsyncClientHttpRequestInterceptor( true, tagAndNamingStrategy, tagAndNamingAdapterMock ); // when String result = interceptor.getSubspanSpanName(requestMock, tagAndNamingStrategy, tagAndNamingAdapterMock); // then assertThat(result).isEqualTo(expectedResult); }
getSubspanSpanName(wrapperRequest, tagAndNamingStrategy, tagAndNamingAdapter), Span.SpanPurpose.CLIENT ); ListenableFuture<ClientHttpResponse> result = propagateTracingHeadersAndExecute( wrapperRequest, body, execution );
) throws IOException { WingtipsAsyncClientHttpRequestInterceptor interceptor = new WingtipsAsyncClientHttpRequestInterceptor( subspanOptionOn, tagAndNamingStrategy, tagAndNamingAdapterMock ); Throwable ex = catchThrowable(() -> interceptor.intercept(requestMock, body, executionMock));
Throwable actualExFromInterceptor = null; try { result = interceptor.intercept(requestMock, body, executionMock);
getSubspanSpanName(wrapperRequest, tagAndNamingStrategy, tagAndNamingAdapter), Span.SpanPurpose.CLIENT ); ListenableFuture<ClientHttpResponse> result = propagateTracingHeadersAndExecute( wrapperRequest, body, execution );
/** * @param surroundCallsWithSubspan Pass in true to have the returned {@link AsyncRestTemplate} surround all calls * with a subspan and propagate the subspan's tracing info, or false to have only the current span propagated at * the time of the call (no subspan). * @return A new {@link AsyncRestTemplate} instance with a {@link WingtipsAsyncClientHttpRequestInterceptor} * already added and with the subspan option on or off depending on the value of the {@code * surroundCallsWithSubspan} argument, and using the default {@link HttpTagAndSpanNamingStrategy} and * {@link HttpTagAndSpanNamingAdapter} ({@link ZipkinHttpTagStrategy} and {@link SpringHttpClientTagAdapter}). */ public static AsyncRestTemplate createTracingEnabledAsyncRestTemplate(boolean surroundCallsWithSubspan) { AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(); asyncRestTemplate.getInterceptors().add( new WingtipsAsyncClientHttpRequestInterceptor(surroundCallsWithSubspan) ); return asyncRestTemplate; }
@Override @SuppressWarnings("deprecation") public ListenableFuture<ClientHttpResponse> intercept( HttpRequest request, byte[] body, AsyncClientHttpRequestExecution execution ) throws IOException { // We need to wrap the request with HttpRequestWrapperWithModifiableHeaders so that tracing info can be // propagated on the headers. HttpRequestWrapperWithModifiableHeaders wrapperRequest = new HttpRequestWrapperWithModifiableHeaders(request); if (surroundCallsWithSubspan) { return createAsyncSubSpanAndExecute(wrapperRequest, body, execution); } return propagateTracingHeadersAndExecute(wrapperRequest, body, execution); }
/** * @param tagAndNamingStrategy The span tag and naming strategy to use - cannot be null. If you really want no * tag and naming strategy, then pass in {@link NoOpHttpTagStrategy#getDefaultInstance()}. * @param tagAndNamingAdapter The tag and naming adapter to use - cannot be null. If you really want no tag and * naming adapter, then pass in {@link NoOpHttpTagAdapter#getDefaultInstance()}. * @return A new {@link AsyncRestTemplate} instance with a {@link WingtipsAsyncClientHttpRequestInterceptor} * already added, and with the subspan option and tag/naming strategy and adapter set to the given arguments. */ public static AsyncRestTemplate createTracingEnabledAsyncRestTemplate( boolean surroundCallsWithSubspan, HttpTagAndSpanNamingStrategy<HttpRequest, ClientHttpResponse> tagAndNamingStrategy, HttpTagAndSpanNamingAdapter<HttpRequest, ClientHttpResponse> tagAndNamingAdapter ) { AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(); asyncRestTemplate.getInterceptors().add( new WingtipsAsyncClientHttpRequestInterceptor( surroundCallsWithSubspan, tagAndNamingStrategy, tagAndNamingAdapter ) ); return asyncRestTemplate; }
/** * @param tagAndNamingStrategy The span tag and naming strategy to use - cannot be null. If you really want no * tag and naming strategy, then pass in {@link NoOpHttpTagStrategy#getDefaultInstance()}. * @param tagAndNamingAdapter The tag and naming adapter to use - cannot be null. If you really want no tag and * naming adapter, then pass in {@link NoOpHttpTagAdapter#getDefaultInstance()}. * @return A new {@link AsyncRestTemplate} instance with a {@link WingtipsAsyncClientHttpRequestInterceptor} * already added, and with the subspan option and tag/naming strategy and adapter set to the given arguments. */ public static AsyncRestTemplate createTracingEnabledAsyncRestTemplate( boolean surroundCallsWithSubspan, HttpTagAndSpanNamingStrategy<HttpRequest, ClientHttpResponse> tagAndNamingStrategy, HttpTagAndSpanNamingAdapter<HttpRequest, ClientHttpResponse> tagAndNamingAdapter ) { AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(); asyncRestTemplate.getInterceptors().add( new WingtipsAsyncClientHttpRequestInterceptor( surroundCallsWithSubspan, tagAndNamingStrategy, tagAndNamingAdapter ) ); return asyncRestTemplate; }
@DataProvider(value = { "NULL_STRATEGY_ARG", "NULL_ADAPTER_ARG" }) @Test public void constructor_with_tag_and_span_naming_args_throws_IllegalArgumentException_if_passed_null_args( NullConstructorArgsScenario scenario ) { // when Throwable ex = catchThrowable( () -> new WingtipsAsyncClientHttpRequestInterceptor(true, scenario.strategy, scenario.adapter) ); // then assertThat(ex) .isInstanceOf(IllegalArgumentException.class) .hasMessage(scenario.expectedExceptionMessage); }
@DataProvider(value = { "true", "false" }) @Test public void constructor_with_tag_and_span_naming_args_sets_fields_as_expected(boolean subspanOptionOn) { // when WingtipsAsyncClientHttpRequestInterceptor interceptor = new WingtipsAsyncClientHttpRequestInterceptor( subspanOptionOn, tagAndNamingStrategy, tagAndNamingAdapterMock ); // then assertThat(interceptor.surroundCallsWithSubspan).isEqualTo(subspanOptionOn); assertThat(interceptor.tagAndNamingStrategy).isSameAs(tagAndNamingStrategy); assertThat(interceptor.tagAndNamingAdapter).isSameAs(tagAndNamingAdapterMock); }
@Test public void default_constructor_creates_instance_with_subspan_option_on() { // when WingtipsAsyncClientHttpRequestInterceptor interceptor = new WingtipsAsyncClientHttpRequestInterceptor(); // then assertThat(interceptor.surroundCallsWithSubspan).isTrue(); assertThat(interceptor.tagAndNamingStrategy).isSameAs(ZipkinHttpTagStrategy.getDefaultInstance()); assertThat(interceptor.tagAndNamingAdapter).isSameAs(SpringHttpClientTagAdapter.getDefaultInstance()); }
@DataProvider(value = { "true", "false" }) @Test public void single_arg_constructor_creates_instance_with_subspan_option_set_to_desired_value( boolean subspanOptionOn ) { // when WingtipsAsyncClientHttpRequestInterceptor interceptor = new WingtipsAsyncClientHttpRequestInterceptor(subspanOptionOn); // then assertThat(interceptor.surroundCallsWithSubspan).isEqualTo(subspanOptionOn); assertThat(interceptor.tagAndNamingStrategy).isSameAs(ZipkinHttpTagStrategy.getDefaultInstance()); assertThat(interceptor.tagAndNamingAdapter).isSameAs(SpringHttpClientTagAdapter.getDefaultInstance()); }
) throws IOException { WingtipsAsyncClientHttpRequestInterceptor defaultInterceptor = new WingtipsAsyncClientHttpRequestInterceptor( subspanOptionOn, tagAndNamingStrategy, tagAndNamingAdapterMock );