From c22e72e4678a6d5fce0b92bff84e973a56216237 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 15 Jul 2016 16:26:21 +0200 Subject: [PATCH] Added tests and fix docs for AsyncRestTemplate fixes #334 --- .../main/asciidoc/spring-cloud-sleuth.adoc | 7 +- ...eAsyncClientHttpRequestFactoryWrapper.java | 4 +- ...ceWebAsyncClientAutoConfigurationTest.java | 105 ++++++++++++++++++ .../instrument/web/client/WebClientTests.java | 22 ++-- 4 files changed, 128 insertions(+), 10 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTest.java diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index edfa905e9..86af5dae7 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -456,7 +456,12 @@ If you create a `RestTemplate` instance with a `new` keyword then the instrument Custom instrumentation is set to create and close Spans upon sending and receiving requests. You can customize the `ClientHttpRequestFactory` and the `AsyncClientHttpRequestFactory` by registering your beans. Remember to use tracing compatible implementations (e.g. don't forget to -wrap `ThreadPoolTaskScheduler` in a `TraceAsyncListenableTaskExecutor`). +wrap `ThreadPoolTaskScheduler` in a `TraceAsyncListenableTaskExecutor`). Example of custom request factories: + +[source,java] +---- +include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTest.java[tags=async_template_factories,indent=0] +---- To block the `AsyncRestTemplate` features set `spring.sleuth.web.async.client.enabled` to `false`. To disable creation of the default `TraceAsyncClientHttpRequestFactoryWrapper` set `spring.sleuth.web.async.client.factory.enabled` diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java index 96be114cc..49e26f6c4 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java @@ -43,8 +43,8 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; public class TraceAsyncClientHttpRequestFactoryWrapper extends AbstractTraceHttpRequestInterceptor implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory { - private final AsyncClientHttpRequestFactory asyncDelegate; - private final ClientHttpRequestFactory syncDelegate; + final AsyncClientHttpRequestFactory asyncDelegate; + final ClientHttpRequestFactory syncDelegate; /** * According to the JavaDocs all Spring {@link AsyncClientHttpRequestFactory} implement diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTest.java new file mode 100644 index 000000000..6f3c01626 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTest.java @@ -0,0 +1,105 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web.client; + +import java.io.IOException; +import java.net.URI; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.AsyncClientHttpRequest; +import org.springframework.http.client.AsyncClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.client.AsyncRestTemplate; + +import com.netflix.hystrix.strategy.HystrixPlugins; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = { + TraceWebAsyncClientAutoConfigurationTest.TestConfiguration.class }) +@WebIntegrationTest(randomPort = true) +@DirtiesContext +public class TraceWebAsyncClientAutoConfigurationTest { + + @Autowired AsyncRestTemplate asyncRestTemplate; + @Autowired MySyncClientHttpRequestFactory mySyncClientHttpRequestFactory; + @Autowired MyAsyncClientHttpRequestFactory myAsyncClientHttpRequestFactory; + + @After + public void close() { + TestSpanContextHolder.removeCurrentSpan(); + HystrixPlugins.reset(); + } + + @Test + public void should_inject_to_async_rest_template_custom_client_http_request_factory() { + then(this.asyncRestTemplate.getAsyncRequestFactory()).isInstanceOf(TraceAsyncClientHttpRequestFactoryWrapper.class); + TraceAsyncClientHttpRequestFactoryWrapper wrapper = (TraceAsyncClientHttpRequestFactoryWrapper) this.asyncRestTemplate.getAsyncRequestFactory(); + then(wrapper.syncDelegate).isSameAs(this.mySyncClientHttpRequestFactory); + then(wrapper.asyncDelegate).isSameAs(this.myAsyncClientHttpRequestFactory); + then(this.asyncRestTemplate).isInstanceOf(TraceAsyncRestTemplate.class); + } + + // tag::async_template_factories[] + @EnableAutoConfiguration + @Configuration + public static class TestConfiguration { + + @Bean + ClientHttpRequestFactory mySyncClientFactory() { + return new MySyncClientHttpRequestFactory(); + } + + @Bean + AsyncClientHttpRequestFactory myAsyncClientFactory() { + return new MyAsyncClientHttpRequestFactory(); + } + } + // end::async_template_factories[] + + private static class MySyncClientHttpRequestFactory implements ClientHttpRequestFactory { + @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) + throws IOException { + return null; + } + } + private static class MyAsyncClientHttpRequestFactory implements AsyncClientHttpRequestFactory { + @Override + public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) + throws IOException { + return null; + } + } + +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java index 3dbae387a..b98735da3 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java @@ -25,8 +25,10 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Random; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import com.jayway.awaitility.Awaitility; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; @@ -113,13 +115,19 @@ public class WebClientTests { then(getHeader(response, Span.TRACE_ID_NAME)).isNotNull(); then(getHeader(response, Span.SPAN_ID_NAME)).isNotNull(); then(this.listener.getSpans()).isNotEmpty(); - Optional noTraceSpan = this.listener.getSpans().stream().filter(span -> - "http:/notrace".equals(span.getName()) && !span.tags().isEmpty()).findFirst(); - then(noTraceSpan.isPresent()).isTrue(); - // TODO: matches cause there is an issue with Feign not providing the full URL at the interceptor level - then(noTraceSpan.get()).matchesATag("http.url", ".*/notrace") - .hasATag("http.path", "/notrace") - .hasATag("http.method", "GET"); + Awaitility.await().atMost(3, TimeUnit.SECONDS).until(() -> { + log.info("Seraching for a notrace span in " + this.listener.getSpans()); + Optional noTraceSpan = this.listener.getSpans().stream().filter(span -> + "http:/notrace".equals(span.getName()) && !span.tags().isEmpty()).findFirst(); + then(noTraceSpan.isPresent()).isTrue(); + log.info("No trace span found"); + // TODO: matches cause there is an issue with Feign not providing the full URL at the interceptor level + then(noTraceSpan.get()).matchesATag("http.url", ".*/notrace") + .hasATag("http.path", "/notrace") + .hasATag("http.method", "GET"); + log.info("Notrace has all the necessary tags"); + }); + } Object[] parametersForShouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracingWasPresent() {