diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 351831ec0..25e906795 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -179,6 +179,7 @@ include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/ will lead in creating a span named `calculateTax`. + == Span Data as Messages You can accumulate and send span data over @@ -273,10 +274,111 @@ the number of dropped spans will get increased. == Integrations +=== Runnable and Callable + +If you're wrapping your logic in `Runnable` or `Callable` it's enough to wrap those classes in their Sleuth representative. + +Example for `Runnable`: + +[source,java] +---- +include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[tags=trace_runnable,indent=0] +---- + +Example for `Callable`: + +[source,java] +---- +include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[tags=trace_callable,indent=0] +---- + +That way you will ensure that a new Span is created and closed for each execution. + +=== Hystrix + +==== Custom Concurrency Strategy + +We're registering a custom https://github.com/Netflix/Hystrix/wiki/Plugins#concurrencystrategy[`HystrixConcurrencyStrategy`] +that wraps all `Callable` instances into their Sleuth representative - +the `TraceCallable`. The strategy either starts or continues a span depending on the fact whether tracing was already going +on before the Hystrix command was called. + +To disable the custom Hystrix Concurrency Strategy set the `spring.sleuth.hystrix.strategy.enabled` to `false`. + +==== Manual Command setting + +Assuming that you have the following `HystrixCommand`: + +[source,java] +---- +include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java[tags=hystrix_command,indent=0] +---- + +In order to pass the tracing information you have to wrap the same logic in the Sleuth version of the `HystrixCommand` which is the +`TraceCommand`: + +[source,java] +---- +include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java[tags=trace_hystrix_command,indent=0] +---- + +=== HTTP integration + +Features from this section can be disabled by providing the `spring.sleuth.web.enabled` property with value equal to `false`. + +==== HTTP Filter + +Via the `TraceFilter` all sampled incoming requests result in creation of a Span. That Span's name is `http:` + the path to which + the request was sent. E.g. if the request was sent to `/foo/bar` then the name will be `http:/foo/bar`. You can configure which URIs you would + like to skip via the `spring.sleuth.instrument.web.skipPattern` property. + +==== Async Servlet support + +If your controller returns a `Callable` or a `WebAsyncTask` Spring Cloud Sleuth will continue the existing span instead of creating a new one. + +=== HTTP client integration + +==== Synchronous Rest Template + +We're injecting a `RestTemplate` interceptor that ensures that all the tracing information is passed to the requests. Each time a +call is made a new Span is created. It gets closed upon receiving the response. In order to block the synchronous `RestTemplate` features +just set `spring.sleuth.client.enabled` to `false`. + +==== Asynchronous Rest Template + +Custom instrumentation is set to create and close Spans upon sending and receiving requests. To block the `AsyncRestTemplate` +features set `spring.sleuth.async.client.enabled` to `false`. + === Feign -By default Spring Cloud Sleuth provides integration with feign via the `TraceFeignClientAutoConfiguration`. You can disable it +By default Spring Cloud Sleuth provides integration with feign via the `TraceFeignClientAutoConfiguration`. You can disable it entirely by setting `spring.sleuth.feign.enabled` to false. -We're taking care of Feign instrumentation by means of a `FeignBeanPostProcessor`. -You can disable the post processor by providing the `spring.sleuth.feign.processor.enabled` equal to `false`. \ No newline at end of file +Part of Feign instrumentation is done via a `FeignBeanPostProcessor`. You can disable it by providing the `spring.sleuth.feign.processor.enabled` equal to `false`. + +=== Asynchronous communication + +In Spring Cloud Sleuth we're instrumenting async related components so that the tracing information is passed between threads. You can disable this behaviour +by setting the value of `spring.sleuth.async.enabled` to `false`. + +==== @Async / @Scheduled annotated methods + +If you annotate your method with `@Async` / `@Scheduled` then we'll automatically create a new Span with the following characteristics: + + - the Span name will be the annotated method name + - the Span will be tagged with that method's class name and the method name too + +==== Executor, ExecutorService and ScheduledExecutorService + +We're providing `LazyTraceExecutor`, `TraceableExecutorService` and `TraceableScheduledExecutorService`. Those implementations +are creating Spans each time a new task is submitted, invoked or scheduled. + +=== Messaging + +All http://projects.spring.io/spring-integration/[Spring Integration] creates spans for publish and subscribe events. +To disable Spring Integration instrumentation, set spring.sleuth.integration.enabled to false. + +=== Zuul + +We registering Zuul filters to propagate the tracing information (the request header is enriched with tracing data). +To disable Zuul support set the `spring.sleuth.zuul.enabled` property to `false`. \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java index c7a503b86..812baaf90 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java @@ -17,6 +17,7 @@ package org.springframework.cloud.sleuth.documentation; import java.util.Random; +import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -30,6 +31,7 @@ import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanName; import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.TraceCallable; import org.springframework.cloud.sleuth.TraceRunnable; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; @@ -216,4 +218,66 @@ public class SpringCloudSleuthDocTests { assertThat(initialSpan.logs()).extracting("event").doesNotContain("commissionCalculated"); executorService.shutdown(); } + + @Test + public void should_wrap_runnable_in_its_sleuth_representative() { + SpanNamer spanNamer = new DefaultSpanNamer(); + Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), this.publisher, spanNamer); + Span initialSpan = tracer.createSpan("initialSpan"); + // tag::trace_runnable[] + Runnable runnable = new Runnable() { + @Override + public void run() { + // do some work + } + + @Override + public String toString() { + return "spanNameFromToStringMethod"; + } + }; + // Manual `TraceRunnable` creation with explicit "calculateTax" Span name + Runnable traceRunnable = new TraceRunnable(tracer, spanNamer, runnable, "calculateTax"); + // Wrapping `Runnable` with `Tracer`. The Span name will be taken either from the + // `@SpanName` annotation or from `toString` method + Runnable traceRunnableFromTracer = tracer.wrap(runnable); + // end::trace_runnable[] + + then(traceRunnable).isExactlyInstanceOf(TraceRunnable.class); + then(traceRunnableFromTracer).isExactlyInstanceOf(TraceRunnable.class); + tracer.close(initialSpan); + } + + @Test + public void should_wrap_callable_in_its_sleuth_representative() { + SpanNamer spanNamer = new DefaultSpanNamer(); + Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), this.publisher, spanNamer); + Span initialSpan = tracer.createSpan("initialSpan"); + // tag::trace_callable[] + Callable callable = new Callable() { + @Override + public String call() throws Exception { + return someLogic(); + } + + @Override + public String toString() { + return "spanNameFromToStringMethod"; + } + }; + // Manual `TraceCallable` creation with explicit "calculateTax" Span name + Callable traceCallable = new TraceCallable<>(tracer, spanNamer, callable, "calculateTax"); + // Wrapping `Callable` with `Tracer`. The Span name will be taken either from the + // `@SpanName` annotation or from `toString` method + Callable traceCallableFromTracer = tracer.wrap(callable); + // end::trace_callable[] + + then(traceCallable).isExactlyInstanceOf(TraceCallable.class); + then(traceCallableFromTracer).isExactlyInstanceOf(TraceCallable.class); + tracer.close(initialSpan); + } + + private String someLogic() { + return "some logic"; + } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java index 6b1920ce6..aa6d92bd9 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java @@ -8,13 +8,15 @@ import org.junit.Test; import org.mockito.Mockito; import org.springframework.cloud.sleuth.DefaultSpanNamer; import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.cloud.sleuth.trace.DefaultTracer; import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import org.springframework.context.ApplicationEventPublisher; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; import com.netflix.hystrix.HystrixCommandKey; import com.netflix.hystrix.HystrixCommandProperties; import com.netflix.hystrix.HystrixThreadPoolProperties; @@ -22,6 +24,7 @@ import com.netflix.hystrix.strategy.HystrixPlugins; import static com.netflix.hystrix.HystrixCommand.Setter.withGroupKey; import static com.netflix.hystrix.HystrixCommandGroupKey.Factory.asKey; +import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; public class TraceCommandTests { @@ -79,6 +82,42 @@ public class TraceCommandTests { .hasATag("threadPoolKey", "group"); } + @Test + public void should_pass_tracing_information_when_using_Hystrix_commands() { + Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), + Mockito.mock(ApplicationEventPublisher.class), new DefaultSpanNamer()); + TraceKeys traceKeys = new TraceKeys(); + HystrixCommand.Setter setter = HystrixCommand.Setter + .withGroupKey(HystrixCommandGroupKey.Factory.asKey("group")) + .andCommandKey(HystrixCommandKey.Factory.asKey("command")); + // tag::hystrix_command[] + HystrixCommand hystrixCommand = new HystrixCommand(setter) { + @Override + protected String run() throws Exception { + return someLogic(); + } + }; + // end::hystrix_command[] + // tag::trace_hystrix_command[] + TraceCommand traceCommand = new TraceCommand(tracer, traceKeys, setter) { + @Override + public String doRun() throws Exception { + return someLogic(); + } + }; + // end::trace_hystrix_command[] + + String resultFromHystrixCommand = hystrixCommand.execute(); + String resultFromTraceCommand = traceCommand.execute(); + + then(resultFromHystrixCommand).isEqualTo(resultFromTraceCommand); + then(tracer.getCurrentSpan()).isNull(); + } + + private String someLogic(){ + return "some logic"; + } + private Span givenATraceIsPresentInTheCurrentThread() { return this.tracer.createSpan("http:test", Span.builder().traceId(EXPECTED_TRACE_ID).build()); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/DefaultTracerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/DefaultTracerTests.java index f15db097b..f8c8bf88f 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/DefaultTracerTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/DefaultTracerTests.java @@ -182,6 +182,7 @@ public class DefaultTracerTests { then(span).hasATag("key", "value").hasLoggedAnEvent("event"); then(continuedSpan).hasATag("key", "value").hasLoggedAnEvent("event"); + then(span).isEqualTo(continuedSpan); } private Span assertSpan(List spans, Long parentId, String name) {