diff --git a/README.adoc b/README.adoc index 7b1ef6ef6..0c6b98e51 100644 --- a/README.adoc +++ b/README.adoc @@ -169,9 +169,6 @@ to `true`. ==== Live examples -.First, send a request -http://docssleuth-service1.cfapps.io/start[Click here to send the request] - .Click Pivotal Web Services icon to see it live! [caption="Click Pivotal Web Services icon to see it live!"] image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/", width=150, height=74] @@ -183,8 +180,8 @@ image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branc .Click Pivotal Web Services icon to see it live! [caption="Click Pivotal Web Services icon to see it live!"] -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/zipkin/dependency/", width=150, height=74] -http://docssleuth-zipkin-server.cfapps.io/zipkin/dependency/[Click here to see it live!] +image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/dependency", width=150, height=74] +http://docssleuth-zipkin-server.cfapps.io/dependency[Click here to see it live!] ==== Log correlation @@ -343,7 +340,10 @@ Example of setting baggage on a span: [source,java] ---- -Unresolved directive in intro.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/brave/instrument/web/multiple/MultipleHopsIntegrationTests.java[tags=baggage,indent=0] +Span initialSpan = this.tracer.nextSpan().name("span").start(); +try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(initialSpan)) { + ExtraFieldPropagation.set("foo", "bar"); + ExtraFieldPropagation.set("UPPER_CASE", "someValue"); } ---- @@ -361,7 +361,10 @@ IMPORTANT: Remember that the span needs to be in scope! [source,java] ---- -Unresolved directive in intro.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/brave/instrument/web/multiple/MultipleHopsIntegrationTests.java[tags=baggage_tag,indent=0] +initialSpan.tag("foo", + ExtraFieldPropagation.get(initialSpan.context(), "foo")); +initialSpan.tag("UPPER_CASE", + ExtraFieldPropagation.get(initialSpan.context(), "UPPER_CASE")); ---- === Adding to the project @@ -567,14 +570,14 @@ a baggage element then it will be sent downstream either via HTTP or messaging t * Provides a way to create / continue spans and add tags and logs via annotations. -* Provides simple metrics of accepted / dropped spans. - * If `spring-cloud-sleuth-zipkin` then the app will generate and collect Zipkin-compatible traces. By default it sends them via HTTP to a Zipkin server on localhost (port 9411). Configure the location of the service using `spring.zipkin.baseUrl`. - If you depend on `spring-rabbit` or `spring-kafka` your app will send traces to a broker instead of http. - Note: `spring-cloud-sleuth-stream` is deprecated and should no longer be used. +* Spring Cloud Sleuth is http://opentracing.io/[OpenTracing] compatible + IMPORTANT: If using Zipkin, configure the percentage of spans exported using `spring.sleuth.sampler.percentage` (default 0.1, i.e. 10%). *Otherwise you might think that Sleuth is not working cause it's omitting some spans.* diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java index 489a7a7ee..936d665f6 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java @@ -17,22 +17,23 @@ package org.springframework.cloud.sleuth.instrument.web.client; import java.util.ArrayList; -import java.util.Collection; import java.util.List; +import java.util.Map; import javax.annotation.PostConstruct; import brave.http.HttpTracing; import brave.spring.web.TracingClientHttpRequestInterceptor; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.web.client.RestTemplateCustomizer; import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.web.client.RestTemplate; @@ -63,44 +64,32 @@ public class TraceWebClientAutoConfiguration { @Configuration protected static class TraceInterceptorConfiguration { - @Autowired(required = false) - private Collection restTemplates; + @Autowired + private ApplicationContext applicationContext; @Autowired private TracingClientHttpRequestInterceptor clientInterceptor; @PostConstruct public void init() { - if (this.restTemplates != null) { - for (RestTemplate restTemplate : this.restTemplates) { - new RestTemplateInterceptorInjector(this.clientInterceptor) - .inject(restTemplate); - } + Map restTemplates = BeanFactoryUtils + .beansOfTypeIncludingAncestors(this.applicationContext, + RestTemplate.class); + for (RestTemplate restTemplate : restTemplates.values()) { + new RestTemplateInterceptorInjector( + this.clientInterceptor).inject(restTemplate); } } - } - @Autowired(required = false) - private Collection restTemplates; - - @Autowired - private TracingClientHttpRequestInterceptor traceRestTemplateInterceptor; - - @Bean - @Order(Ordered.HIGHEST_PRECEDENCE) - RestTemplateCustomizer traceRestTemplateCustomizer() { - final TracingClientHttpRequestInterceptor interceptor = this.traceRestTemplateInterceptor; - return restTemplate -> - new RestTemplateInterceptorInjector(interceptor).inject(restTemplate); - } - - @PostConstruct - public void init() { - if (this.restTemplates != null) { - for (RestTemplate restTemplate : this.restTemplates) { - new RestTemplateInterceptorInjector( - this.traceRestTemplateInterceptor).inject(restTemplate); - } + @Bean + @Order + RestTemplateCustomizer traceRestTemplateCustomizer() { + return new RestTemplateCustomizer() { + @Override public void customize(RestTemplate restTemplate) { + new RestTemplateInterceptorInjector(TraceInterceptorConfiguration.this.clientInterceptor) + .inject(restTemplate); + } + }; } } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfigurationTests.java new file mode 100644 index 000000000..5b86b68a8 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfigurationTests.java @@ -0,0 +1,133 @@ +/* + * Copyright 2013-2018 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.util.Arrays; +import java.util.List; + +import brave.spring.web.TracingClientHttpRequestInterceptor; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.boot.web.client.RestTemplateCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TraceWebClientAutoConfigurationTests.Config.class) +public class TraceWebClientAutoConfigurationTests { + + @Autowired @Qualifier("firstRestTemplate") RestTemplate restTemplate; + @Autowired @Qualifier("secondRestTemplate") RestTemplate secondRestTemplate; + + @Test + public void should_add_rest_template_interceptors() { + assertInterceptorsOrder(assertInterceptorsNotEmpty(this.restTemplate)); + assertInterceptorsOrder(assertInterceptorsNotEmpty(this.secondRestTemplate)); + } + + private List assertInterceptorsNotEmpty(RestTemplate restTemplate) { + then(restTemplate).isNotNull(); + List interceptors = restTemplate + .getInterceptors(); + then(interceptors).isNotEmpty(); + return interceptors; + } + + private void assertInterceptorsOrder( + List interceptors) { + int traceInterceptorIndex = -1; + int myInterceptorIndex = -1; + int mySecondInterceptorIndex = -1; + for (int i = 0; i < interceptors.size(); i++) { + if (interceptors.get(i) instanceof TracingClientHttpRequestInterceptor) { + traceInterceptorIndex = i; + } else if (interceptors.get(i) instanceof MyClientHttpRequestInterceptor) { + myInterceptorIndex = i; + } else if (interceptors.get(i) instanceof MySecondClientHttpRequestInterceptor) { + mySecondInterceptorIndex = i; + } + } + then(traceInterceptorIndex) + .isGreaterThanOrEqualTo(0) + .isLessThan(myInterceptorIndex) + .isLessThan(mySecondInterceptorIndex); + } + + @Configuration + @EnableAutoConfiguration + static class Config { + + @Bean + @Qualifier("firstRestTemplate") + RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { + return restTemplateBuilder + .additionalInterceptors(new MyClientHttpRequestInterceptor()) + .build(); + } + + @Bean + @Qualifier("secondRestTemplate") + RestTemplate secondRestTemplate() { + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setInterceptors( + Arrays.asList(new MyClientHttpRequestInterceptor(), + new MySecondClientHttpRequestInterceptor())); + return restTemplate; + } + + @Bean + RestTemplateCustomizer myRestTemplateCustomizer() { + return restTemplate -> { + restTemplate.getInterceptors().add(0, new MySecondClientHttpRequestInterceptor()); + }; + } + + } +} + +class MyClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { + + @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, + ClientHttpRequestExecution execution) throws IOException { + return execution.execute(request, body); + } +} + +class MySecondClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { + + @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, + ClientHttpRequestExecution execution) throws IOException { + return execution.execute(request, body); + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java index ce0dd59b5..5b309ad6a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java @@ -403,8 +403,6 @@ public class WebClientTests { @Override public void customize(RestTemplate restTemplate) { this.executed = true; - then(restTemplate.getInterceptors().get(0)).isInstanceOf( - TracingClientHttpRequestInterceptor.class); } public boolean isExecuted() {