In distributed tracing the data volumes can be very high so sampling
can be important (you usually don’t need to export all spans to get a
good picture of what is happening). Spring Cloud Sleuth has a
Sampler
strategy that you can implement to take control of the
sampling algorithm. Samplers do not stop span (correlation) ids from
being generated, but they do prevent the tags and events being
attached and exported. By default you get a strategy that continues to
trace if a span is already active, but new ones are always marked as
non-exportable. If all your apps run with this sampler you will see
traces in logs, but not in any remote store. For testing the default
is often enough, and it probably is all you need if you are only using
the logs (e.g. with an ELK aggregator). If you are exporting span data
to Zipkin or Spring Cloud Stream, there is also an AlwaysSampler
that exports everything and a PercentageBasedSampler
that samples a
fixed fraction of spans.
Note
|
the PercentageBasedSampler is the default if you are using
spring-cloud-sleuth-zipkin or spring-cloud-sleuth-stream . You can
configure the exports using spring.sleuth.sampler.percentage .
|
A sampler can be installed just by creating a bean definition, e.g:
link:../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[role=include]
Spring Cloud Sleuth instruments all your Spring application
automatically, so you shouldn’t have to do anything to activate
it. The instrumentation is added using a variety of technologies
according to the stack that is available, e.g. for a servlet web
application we use a Filter
, and for Spring Integration we use
ChannelInterceptors
.
You can customize the keys used in span tags. To limit the volume of
span data, by default an HTTP request will be tagged only with a
handful of metadata like the status code, host and URL. You can add
request headers by configuring spring.sleuth.keys.http.headers
(a
list of header names).
Note
|
Remember that tags are only collected and exported if there is a
Sampler that allows it (by default there is not, so there is no
danger of accidentally collecting too much data without configuring
something).
|
Note
|
Currently the instrumentation in Spring Cloud Sleuth is eager - it means that we’re actively trying to pass the tracing context between threads. Also timing events are captured even when sleuth isn’t exporting data to a tracing system. This approach may change in the future towards being lazy on this matter. |
You can do the following operations on the Span by means of Tracer interface:
-
start - when you start a span its name is assigned and start timestamp is recorded.
-
close - the span gets finished (the end time of the span is recorded) and if the span is exportable then it will be eligible for collection to Zipkin. The span is also removed from the current thread.
-
continue - a new instance of span will be created whereas it will be a copy of the one that it continues.
-
detach - the span doesn’t get stopped or closed. It only gets removed from the current thread.
-
create with explicit parent - you can create a new span and set an explicit parent to it
You can manually create spans by using the Tracer interface.
link:../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[role=include]
In this example we could see how to create a new instance of span. Assuming that there already was a span present in this thread then it would become the parent of that span.
Important
|
Always clean after you create a span! Don’t forget to close a span if you want to send it to Zipkin. |
Sometimes you don’t want to create a new span but you want to continue one. Example of such a situation might be (of course it all depends on the use-case):
-
AOP - If there was already a span created before an aspect was reached then you might not want to create a new span.
-
Hystrix - executing a Hystrix command is most likely a logical part of the current processing. It’s in fact only a technical implementation detail that you wouldn’t necessarily want to reflect in tracing as a separate being.
The continued instance of span is equal to the one that it continues:
Span continuedSpan = this.tracer.continueSpan(spanToContinue);
assertThat(continuedSpan).isEqualTo(spanToContinue);
To continue a span you can use the Tracer interface.
link:../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[role=include]
Important
|
Always clean after you create a span! Don’t forget to detach a span if some work was done started in one thread (e.g. thread X) and it’s waiting for other threads (e.g. Y, Z) to finish. Then the spans in the threads Y, Z should be detached at the end of their work. When the results are collected the span in thread X should be closed. |
There is a possibility that you want to start a new span and provide an explicit parent of that span.
Let’s assume that the parent of a span is in one thread and you want to start a new span in another thread. The
startSpan
method of the Tracer
interface is the method you are looking for.
link:../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[role=include]
Important
|
After having created such a span remember to close it. Otherwise you will see a lot of warnings in your logs related to the fact that you have a span present in the current thread other than the one you’re trying to close. What’s worse your spans won’t get closed properly thus will not get collected to Zipkin. |
Picking a span name is not a trivial task. Span name should depict an operation name. The name should be low cardinality (e.g. not include identifiers).
Since there is a lot of instrumentation going on some of the span names will be artificial like:
-
controller-method-name
when received by a Controller with a method nameconrollerMethodName
-
async
for asynchronous operations done via wrappedCallable
andRunnable
. -
@Scheduled
annotated methods will return the simple name of the class.
Fortunately, for the asynchronous processing you can provide explicit naming.
You can do name the span explicitly via the @SpanName
annotation.
link:../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[role=include]
In this case, when processed in the following manner:
link:../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[role=include]
The span will be named calculateTax
.
It’s pretty rare to create separate classes for Runnable
or Callable
. Typically one creates an anonymous
instance of those classes. You can’t annotate such classes thus to override that, if there is no @SpanName
annotation present,
we’re checking if the class has a custom implementation of the toString()
method.
So executing such code:
link:../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[role=include]
will lead in creating a span named calculateTax
.
Thanks to the SpanInjector
and SpanExtractor
you can customize the way spans
are created and propagated.
There are currently two built-in ways to pass tracing information between processes:
-
via Spring Integration
-
via HTTP
Span ids are extracted from Zipkin-compatible (B3) headers (either Message
or HTTP headers), to start or join an existing trace. Trace information is
injected into any outbound requests so the next hop can extract them.
For Spring Integration these are the beans responsible for creation of a Span from a Message
and filling in the MessageBuilder
with tracing information.
@Bean
public SpanExtractor<Message> messagingSpanExtractor() {
...
}
@Bean
public SpanInjector<MessageBuilder> messagingSpanInjector() {
...
}
You can override them by providing your own implementation and by adding a @Primary
annotation
to your bean definition.
For HTTP these are the beans responsible for creation of a Span from a HttpServletRequest
and filling in the HttpServletResponse
with tracing information.
@Bean
public SpanExtractor<HttpServletRequest> httpServletRequestSpanExtractor() {
...
}
@Bean
public SpanInjector<HttpServletResponse> httpServletResponseSpanInjector() {
...
}
You can override them by providing your own implementation and by adding a @Primary
annotation
to your bean definition.
Let’s assume that instead of the standard Zipkin compatible tracing HTTP header names you have
-
for trace id -
correlationId
-
for span id -
mySpanId
This is a an example of a SpanExtractor
link:../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java[role=include]
The following SpanInjector
could be created
link:../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java[role=include]
And you could register them like this:
link:../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java[role=include]
Sometimes you want to create a manual Span that will wrap a call to an external service which is not instrumented.
What you can do is to create a span with the peer.service
tag that will contain a value of the service that you want to call.
Below you can see an example of a call to Redis that is wrapped in such a span.
link:../../../..//spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin/HttpZipkinSpanReporterTest.java[role=include]
Important
|
Remember not to add both peer.service tag and the SA tag! You have to add only peer.service .
|
You can accumulate and send span data over
Spring Cloud Stream by
including the spring-cloud-sleuth-stream
jar as a dependency, and
adding a Channel Binder implementation
(e.g. spring-cloud-starter-stream-rabbit
for RabbitMQ or
spring-cloud-starter-stream-kafka
for Kafka). This will
automatically turn your app into a producer of messages with payload
type Spans
.
There is a special convenience annotation for setting up a message consumer
for the Span data and pushing it into a Zipkin SpanStore
. This application
link:../../../../spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/documentation/Consumer.java[role=include]
will listen for the Span data on whatever transport you provide via a
Spring Cloud Stream Binder
(e.g. include
spring-cloud-starter-stream-rabbit
for RabbitMQ, and similar
starters exist for Redis and Kafka). If you add the following UI dependency
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
Then you’ll have your app a Zipkin server, which hosts the UI and api on port 9411.
The default SpanStore
is in-memory (good for demos and getting
started quickly). For a more robust solution you can add MySQL and
spring-boot-starter-jdbc
to your classpath and enable the JDBC
SpanStore
via configuration, e.g.:
spring:
rabbitmq:
host: ${RABBIT_HOST:localhost}
datasource:
schema: classpath:/mysql.sql
url: jdbc:mysql://${MYSQL_HOST:localhost}/test
username: root
password: root
# Switch this on to create the schema on startup:
initialize: true
continueOnError: true
sleuth:
enabled: false
zipkin:
storage:
type: mysql
Note
|
The @EnableZipkinStreamServer is also annotated with
@EnableZipkinServer so the process will also expose the standard
Zipkin server endpoints for collecting spans over HTTP, and for
querying in the Zipkin Web UI.
|
A custom consumer can also easily be implemented using
spring-cloud-sleuth-stream
and binding to the SleuthSink
. Example:
@EnableBinding(SleuthSink.class)
@SpringBootApplication(exclude = SleuthStreamAutoConfiguration.class)
@MessageEndpoint
public class Consumer {
@ServiceActivator(inputChannel = SleuthSink.INPUT)
public void sink(Spans input) throws Exception {
// ... process spans
}
}
Note
|
the sample consumer application above explicitly excludes
SleuthStreamAutoConfiguration so it doesn’t send messages to itself,
but this is optional (you might actually want to trace requests into
the consumer app).
|
In order to customize the polling mechanism you can create a bean of PollerMetadata
type
with name equal to StreamSpanReporter.POLLER
. Here you can find an example of such a configuration.
link:../../../../spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfigurationTest.java[role=include]
Currently Spring Cloud Sleuth registers very simple metrics related to spans. It’s using the Spring Boot’s metrics support to calculate the number of accepted and dropped spans. Each time a span gets sent to Zipkin the number of accepted spans will increase. If there’s an error then the number of dropped spans will get increased.
If you’re wrapping your logic in Runnable
or Callable
it’s enough to wrap those classes in their Sleuth representative.
Example for Runnable
:
link:../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[role=include]
Example for Callable
:
link:../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[role=include]
That way you will ensure that a new Span is created and closed for each execution.
We’re registering a custom 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
.
Assuming that you have the following HystrixCommand
:
link:../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java[role=include]
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
:
link:../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java[role=include]
We’re registering a custom RxJavaSchedulersHook
that wraps all Action0
instances into their Sleuth representative -
the TraceAction
. The hook either starts or continues a span depending on the fact whether tracing was already going
on before the Action was scheduled. To disable the custom RxJavaSchedulersHook set the spring.sleuth.rxjava.schedulers.hook.enabled
to false
.
You can define a list of regular expressions for thread names, for which you don’t want a Span to be created. Just provide a comma separated list
of regular expressions in the spring.sleuth.rxjava.schedulers.ignoredthreads
property.
Features from this section can be disabled by providing the spring.sleuth.web.enabled
property with value equal to false
.
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.web.skipPattern
property. If you have ManagementServerProperties
on classpath then
its value of contextPath
gets appended to the provided skip pattern.
Since we want the span names to be precise we’re using a TraceHandlerInterceptor
that either wraps an
existing HandlerInterceptor
or is added directly to the list of existing HandlerInterceptors
. The
TraceHandlerInterceptor
adds a special request attribute to the given HttpServletRequest
. If the
the TraceFilter
doesn’t see this attribute set it will create a "fallback" span which is an additional
span created on the server side so that the trace is presented properly in the UI. Seeing that most likely
signifies that there is a missing instrumentation. In that case please file an issue in Spring Cloud Sleuth.
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.web.client.enabled
to false
.
Important
|
You have to register RestTemplate as a bean so that the interceptors will get injected.
If you create a RestTemplate instance with a new keyword then the instrumentation WILL NOT work.
|
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
). Example of custom request factories:
link:../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java[role=include]
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
to false
. If you don’t want to create AsyncRestClient
at all set spring.sleuth.web.async.client.template.enabled
to false
.
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. If you do so then no Feign related instrumentation will take place.
Part of Feign instrumentation is done via a FeignBeanPostProcessor
. You can disable it by providing the spring.sleuth.feign.processor.enabled
equal to false
.
If you set it like this then Spring Cloud Sleuth will not instrument any of your custom Feign components. All the default instrumentation
however will be still there.
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
.
If you annotate your method with @Async
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
In Spring Cloud Sleuth we’re instrumenting scheduled method execution so that the tracing information is passed between threads. You can disable this behaviour
by setting the value of spring.sleuth.scheduled.enabled
to false
.
If you annotate your method with @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
If you want to skip Span creation for some @Scheduled
annotated classes you can set the
spring.sleuth.scheduled.skipPattern
with a regular expression that will match the fully qualified name of the
@Scheduled
annotated class.
We’re providing LazyTraceExecutor
, TraceableExecutorService
and TraceableScheduledExecutorService
. Those implementations
are creating Spans each time a new task is submitted, invoked or scheduled.
Here you can see an example of how to pass tracing information with TraceableExecutorService
when working with CompletableFuture
:
link:../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorServiceTests.java[role=include]
Spring Cloud Sleuth integrates with Spring Integration. It creates spans for publish and
subscribe events. To disable Spring Integration instrumentation, set spring.sleuth.integration.enabled
to false.
Spring Cloud Sleuth up till version 1.0.4 is sending invalid tracing headers when using messaging. Those headers are actually
the same as the ones sent in HTTP (they contain a -
) in its name. For the sake of
backwards compatibility in 1.0.4 we’ve started sending both valid and invalid headers. Please upgrade to 1.0.4 because
in Spring Cloud Sleuth 1.1 we will remove the support for the deprecated headers.
You can provide the spring.sleuth.integration.patterns
pattern to explicitly
provide the names of channels that you want to include for tracing. By default all channels
are included.
Important
|
When using the Executor to build a Spring Integration IntegrationFlow remember to use the untraced version of the Executor .
Decorating Spring Integration Executor Channel with TraceableExecutorService will cause the spans to be improperly closed.
|
You can find the running examples deployed in the Pivotal Web Services. Check them out in the following links: