@@ -25,6 +25,7 @@
|
||||
<plugin>
|
||||
<groupId>org.asciidoctor</groupId>
|
||||
<artifactId>asciidoctor-maven-plugin</artifactId>
|
||||
<version>1.5.3</version>
|
||||
<inherited>false</inherited>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
||||
@@ -31,7 +31,7 @@ The Ribbon sample makes an interesting demo or playground for learning about zip
|
||||
|
||||
NOTE: You can see the zipkin spans without the UI (in logs) if you run the sample with `sample.zipkin.enabled=false`.
|
||||
|
||||
image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/zipkin-trace-screenshot.png[Eample Zipkin Screenshot]
|
||||
image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/zipkin-trace-screenshot.png[Sample Zipkin Screenshot]
|
||||
|
||||
> The fact that the first trace in says "testSleuthMessaging" seems to be a bug in the UI (it has some annotations from that service, but it originates in the "testSleuthRibbon" service).
|
||||
|
||||
@@ -49,6 +49,11 @@ The UI should look like the screenshot above.
|
||||
|
||||
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/building.adoc[]
|
||||
|
||||
IMPORTANT: There are 2 different versions of language level used in Spring Cloud Sleuth. Java 1.7 is used for main sources and
|
||||
Java 1.8 is used for tests. When importing your project to an IDE please activate the `ide` Maven profile to turn on
|
||||
Java 1.8 for both main and test sources. Of course remember that you MUST NOT use Java 1.8 features in the main sources. If you do
|
||||
so your app will break during the Maven build.
|
||||
|
||||
== Contributing
|
||||
|
||||
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/contributing.adoc[]
|
||||
|
||||
@@ -8,18 +8,41 @@
|
||||
2016-02-02 15:31:01.936 INFO [bar,46ab0d418373cbc9,46ab0d418373cbc9,false] 23030 --- [nio-8081-exec-4] ...
|
||||
----
|
||||
+
|
||||
(notice the `[appname,traceId,spanId,exportable]` entries from the MDC).
|
||||
notice the `[appname,traceId,spanId,exportable]` entries from the MDC:
|
||||
|
||||
* Optionally log span data in JSON format for harvesting in a log aggregator (set `spring.sleuth.log.json.enabled=true`).
|
||||
- *spanId* - the id of a specific operation that took place
|
||||
- *appname* - the name of the application that logged the span
|
||||
- *traceId* - the id of the latency graph that contains the span
|
||||
- *exportable* - whether the log should be exported to Zipkin or not
|
||||
|
||||
* Provides an abstraction over common distributed tracing data models: traces, spans (forming a DAG), annotations, key-value annotations. Loosely based on HTrace, but Zipkin (Dapper) compatible.
|
||||
* Provides an abstraction over common distributed tracing data models: traces, spans (forming a DAG), annotations,
|
||||
key-value annotations. Loosely based on HTrace, but Zipkin (Dapper) compatible.
|
||||
|
||||
* Instruments common ingress and egress points from Spring applications (servlet filter, rest template, scheduled actions, message channels, zuul filters, feign client).
|
||||
* Sleuth records timing information to aid in latency analysis. Using sleuth, you can pinpoint causes of
|
||||
latency in your applications. Sleuth is written to not log too much, and to not cause your production application to crash.
|
||||
- propagates structural data about your call-graph in-band, and the rest out-of-band.
|
||||
- includes opinionated instrumentation of layers such as HTTP
|
||||
- includes sampling policy to manage volume
|
||||
- can report to a Zipkin system for query and visualization
|
||||
|
||||
* If `spring-cloud-sleuth-zipkin` then the app will generate and collect Zipkin-compatible traces (using Brave). By default it sends them via HTTP to a Zipkin server on localhost (port 9411). Configure the location of the service using `spring.zipkin.[host,port]`.
|
||||
* Instruments common ingress and egress points from Spring applications (servlet filter, async endpoints,
|
||||
rest template, scheduled actions, message channels, zuul filters, feign client).
|
||||
|
||||
* If `spring-cloud-sleuth-stream` then the app will generate and collect traces via Spring Cloud Stream. Your app automatically becomes a producer of tracer messages that are sent over your broker of choice (e.g. RabbitMQ, Apache Kafka, Redis).
|
||||
* Provides simple metrics of accepted / dropped spans.
|
||||
|
||||
If using Zipkin or Stream, configure the percentage of spans exported using `spring.sleuth.sampler.percentage` (default 0.1, i.e. 10%).
|
||||
* 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`.
|
||||
|
||||
NOTE: the SLF4J MDC is always set and logback users will immediately see the trace and span ids in logs per the example above. Other logging systems have to configure their own formatter to get the same result. The default is `logging.pattern.level` set to `%clr(%5p) %clr([${spring.application.name:},%X{X-Trace-Id:-},%X{X-Span-Id:-},%X{X-Span-Export:-}]){yellow}` (this is a Spring Boot feature for logback users).
|
||||
* If `spring-cloud-sleuth-stream` then the app will generate and collect traces via https://github.com/spring-cloud/spring-cloud-stream[Spring Cloud Stream].
|
||||
Your app automatically becomes a producer of tracer messages that are sent over your broker of choice
|
||||
(e.g. RabbitMQ, Apache Kafka, Redis).
|
||||
|
||||
IMPORTANT: If using Zipkin or Stream, 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.*
|
||||
|
||||
NOTE: the SLF4J MDC is always set and logback users will immediately see the trace and span ids in logs per the example
|
||||
above. Other logging systems have to configure their own formatter to get the same result. The default is
|
||||
`logging.pattern.level` set to `%clr(%5p) %clr([${spring.application.name:},%X{X-Trace-Id:-},%X{X-Span-Id:-},%X{X-Span-Export:-}]){yellow}`
|
||||
(this is a Spring Boot feature for logback users).
|
||||
*This means that if you're not using SLF4J this pattern WILL NOT be automatically applied*.
|
||||
BIN
docs/src/main/asciidoc/images/parents.png
Normal file
BIN
docs/src/main/asciidoc/images/parents.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/src/main/asciidoc/images/trace-id.png
Normal file
BIN
docs/src/main/asciidoc/images/trace-id.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 233 KiB After Width: | Height: | Size: 218 KiB |
BIN
docs/src/main/asciidoc/images/zipkin-traces.png
Normal file
BIN
docs/src/main/asciidoc/images/zipkin-traces.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
BIN
docs/src/main/asciidoc/images/zipkin-ui.png
Normal file
BIN
docs/src/main/asciidoc/images/zipkin-ui.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 139 KiB |
@@ -6,10 +6,97 @@ Spring Cloud Sleuth implements a distributed tracing solution for http://cloud.s
|
||||
|
||||
Spring Cloud Sleuth borrows http://research.google.com/pubs/pub36356.html[Dapper's] terminology.
|
||||
|
||||
*Span:* The basic unit of work. For example, sending an RPC is a new span, as is sending a response to an RPC. Span's are identified by a unique 64-bit ID for the span and another 64-bit ID for the trace the span is a part of. Spans also have other data, such as descriptions, timestamped events, key-value annotations (tags), the ID of the span that caused them, and process ID's (normally IP address).
|
||||
*Span:* The basic unit of work. For example, sending an RPC is a new span, as is sending a response to an
|
||||
RPC. Span's are identified by a unique 64-bit ID for the span and another 64-bit ID for the trace the span
|
||||
is a part of. Spans also have other data, such as descriptions, timestamped events, key-value
|
||||
annotations (tags), the ID of the span that caused them, and process ID's (normally IP address).
|
||||
|
||||
Spans are started and stopped, and they keep track of their timing information. Once you create a span, you must stop it at some point in the future.
|
||||
Spans are started and stopped, and they keep track of their timing information. Once you create a
|
||||
span, you must stop it at some point in the future.
|
||||
|
||||
*Trace:* A set of spans forming a tree-like structure. For example, if you are running a distributed big-data store, a trace might be formed by a put request.
|
||||
*Trace:* A set of spans forming a tree-like structure. For example, if you are running a distributed
|
||||
big-data store, a trace might be formed by a put request.
|
||||
|
||||
*Annotation:* is used to record existence of an event in time. Some of the core annotations used to define
|
||||
the start and stop of a request are:
|
||||
|
||||
- *cs* - Client Sent - The client has made a request. This annotation depicts the start of the span.
|
||||
- *sr* - Server Received - The server side got the request and will start processing it.
|
||||
If one subtracts the cs timestamp from this timestamp one will receive the network latency.
|
||||
- *ss* - Server Sent - Annotated upon completion of request processing (when the response
|
||||
got sent back to the client). If one subtracts the sr timestamp from this timestamp one
|
||||
will receive the time needed by the server side to process the request.
|
||||
- *cr* - Client Received - Signifies the end of the span. The client has successfully received the
|
||||
response from the server side. If one subtracts the cs timestamp from this timestamp one
|
||||
will receive the whole time needed by the client to receive the response from the server.
|
||||
|
||||
Visualization of what *Span* and *Trace* will look in a system together with the Zipkin annotations:
|
||||
|
||||
image::trace-id.png[Trace Info propagation]
|
||||
|
||||
Each color of a note signifies a span (7 spans - from *A* to *G*). If you have such information in the note:
|
||||
|
||||
[source]
|
||||
Trace Id = X
|
||||
Span Id = D
|
||||
Client Sent
|
||||
|
||||
That means that the current span has *Trace-Id* set to *X*, *Span-Id* set to *D*. It also has emitted
|
||||
*Client Sent* event.
|
||||
|
||||
This is how the visualization of the parent / child relationship of spans would look like:
|
||||
|
||||
image::parents.png[Parent child relationship]
|
||||
|
||||
=== Purpose
|
||||
|
||||
In the following sections the example from the image above will be taken into consideration.
|
||||
|
||||
==== Distributed tracing with Zipkin
|
||||
|
||||
Altogether there are *10 spans* . If you go to traces in Zipkin you will see this number:
|
||||
|
||||
image::zipkin-traces.png[Traces]
|
||||
|
||||
However if you pick a particular trace then you will see *7 spans*:
|
||||
|
||||
image::zipkin-ui.png[Traces Info propagation]
|
||||
|
||||
NOTE: When picking a particular trace you will see merged spans. That means that if there were 2 spans sent to
|
||||
Zipkin with Server Received and Server Sent / Client Received and Client Sent
|
||||
annotations then they will presented as a single span.
|
||||
|
||||
In the image depicting the visualization of what *Span* and *Trace* is you can see 20
|
||||
colorful labels. How does it happen that in Zipkin 10 spans are received?
|
||||
|
||||
- 2 span *A* labels signify span started and closed. Upon closing a single span is sent to Zipkin.
|
||||
- 4 span *B* labels are in fact are single span with 4 annotations. However this span is composed of
|
||||
two separate instances. One sent from service 1 and one from service 2. So in fact two span instances will be sent
|
||||
to Zipkin and merged there.
|
||||
- 2 span *C* labels signify span started and closed. Upon closing a single span is sent to Zipkin.
|
||||
- 4 span *B* labels are in fact are single span with 4 annotations. However this span is composed of
|
||||
two separate instances. One sent from service 2 and one from service 3. So in fact two span instances will be sent
|
||||
to Zipkin and merged there.
|
||||
- 2 span *E* labels signify span started and closed. Upon closing a single span is sent to Zipkin.
|
||||
- 4 span *B* labels are in fact are single span with 4 annotations. However this span is composed of
|
||||
two separate instances. One sent from service 2 and one from service 4. So in fact two span instances will be sent
|
||||
to Zipkin and merged there.
|
||||
- 2 span *G* labels signify span started and closed. Upon closing a single span is sent to Zipkin.
|
||||
|
||||
So 1 span from *A*, 2 spans from *B*, 1 span from *C*, 2 spans from *D*, 1 span from *E*, 2 spans from *F* and 1 from *G*.
|
||||
Altogether *10* spans.
|
||||
|
||||
==== Log correlation
|
||||
|
||||
When grepping the logs of those four applications by trace id equal to e.g. `2485ec27856c56f4` one would get the following:
|
||||
|
||||
[source]
|
||||
service1.log:2016-02-26 11:15:47.561 INFO [service1,2485ec27856c56f4,2485ec27856c56f4,true] 68058 --- [nio-8081-exec-1] i.s.c.sleuth.docs.service1.Application : Hello from service1. Calling service2
|
||||
service2.log:2016-02-26 11:15:47.710 INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application : Hello from service2. Calling service3 and then service4
|
||||
service3.log:2016-02-26 11:15:47.895 INFO [service3,2485ec27856c56f4,1210be13194bfe5,true] 68060 --- [nio-8083-exec-1] i.s.c.sleuth.docs.service3.Application : Hello from service3
|
||||
service2.log:2016-02-26 11:15:47.924 INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application : Got response from service3 [Hello from service3]
|
||||
service4.log:2016-02-26 11:15:48.134 INFO [service4,2485ec27856c56f4,1b1845262ffba49d,true] 68061 --- [nio-8084-exec-1] i.s.c.sleuth.docs.service4.Application : Hello from service4
|
||||
service2.log:2016-02-26 11:15:48.156 INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application : Got response from service4 [Hello from service4]
|
||||
service1.log:2016-02-26 11:15:48.182 INFO [service1,2485ec27856c56f4,2485ec27856c56f4,true] 68058 --- [nio-8081-exec-1] i.s.c.sleuth.docs.service1.Application : Got response from service2 [Hello from service2, response from service3 [Hello from service3] and from service4 [Hello from service4]]
|
||||
|
||||
If you're using a log aggregating tool like Kibana, Splunk etc. you can order the events that took place
|
||||
@@ -34,10 +34,7 @@ A sampler can be installed just by creating a bean definition, e.g:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public Sampler defaultSampler() {
|
||||
return new AlwaysSampler();
|
||||
}
|
||||
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[tags=always_sampler,indent=0]
|
||||
----
|
||||
|
||||
== Instrumentation
|
||||
@@ -60,6 +57,128 @@ NOTE: Remember that tags are only collected and exported if there is a
|
||||
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.
|
||||
|
||||
== Span lifecycle
|
||||
|
||||
You can do the following operations on the Span by means of *Tracer* interface:
|
||||
|
||||
- <<creating-and-closing-spans, start>> - when you start a span its name is assigned and start timestamp is recorded.
|
||||
- <<creating-and-closing-spans, 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.
|
||||
- <<continuing-spans, continue>> - a new instance of span will be created whereas it will be a copy of the
|
||||
one that it continues.
|
||||
- <<continuing-spans, detach>> - the span doesn't get stopped or closed. It only gets removed from the current thread.
|
||||
- <<joining-spans, join>> - you can create a new span and set an explicit parent to it
|
||||
|
||||
=== Creating and closing spans [[creating-and-closing-spans]]
|
||||
|
||||
You can manually create spans by using the *Tracer* interface.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[tags=manual_span_creation,indent=0]
|
||||
----
|
||||
|
||||
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.
|
||||
|
||||
=== Continuing spans [[continuing-spans]]
|
||||
|
||||
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:
|
||||
[source,java]
|
||||
----
|
||||
Span continuedSpan = this.tracer.continueSpan(spanToContinue);
|
||||
assertThat(continuedSpan).isEqualTo(spanToContinue);
|
||||
----
|
||||
|
||||
To continue a span you can use the *Tracer* interface.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[tags=manual_span_continuation,indent=0]
|
||||
----
|
||||
|
||||
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.
|
||||
|
||||
=== Joining spans [[joining-spans]]
|
||||
|
||||
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
|
||||
`joinTrace` method of the `Tracer` interface is the method you are looking for.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[tags=manual_span_joining,indent=0]
|
||||
----
|
||||
|
||||
IMPORTANT: After having joined the 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.
|
||||
|
||||
== Naming spans
|
||||
|
||||
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:
|
||||
|
||||
- `http:path` when received an http request on a given path
|
||||
- `async` for asynchronous operations done via wrapped `Callable` and `Runnable`.
|
||||
- `@Scheduled` annotated methods will return the simple name of the class.
|
||||
|
||||
Fortunately, for the asynchronous processing you can provide explicit naming.
|
||||
|
||||
==== @SpanName annotation
|
||||
|
||||
You can do name the span explicitly via the `@SpanName` annotation.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[tags=span_name_annotation,indent=0]
|
||||
----
|
||||
|
||||
In this case, when processed in the following manner:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[tags=span_name_annotated_runnable_execution,indent=0]
|
||||
----
|
||||
|
||||
The span will be named `calculateTax`.
|
||||
|
||||
==== toString() method
|
||||
|
||||
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:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[tags=span_name_to_string_runnable_execution,indent=0]
|
||||
----
|
||||
|
||||
will lead in creating a span named `calculateTax`.
|
||||
|
||||
== Span Data as Messages
|
||||
|
||||
You can accumulate and send span data over
|
||||
@@ -78,13 +197,7 @@ for the Span data and pushing it into a Zipkin `SpanStore`. This application
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@SpringBootApplication
|
||||
@EnableZipkinStreamServer
|
||||
public class Consumer {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Consumer.class, args);
|
||||
}
|
||||
}
|
||||
include::../../../../spring-cloud-sleuth-zipkin-stream/src/test/java/org/springframework/cloud/sleuth/zipkin/stream/documentation/Consumer.java[tags=zipkin_consumer,indent=0]
|
||||
----
|
||||
|
||||
will listen for the Span data on whatever transport you provide via a
|
||||
@@ -96,7 +209,7 @@ can point a standard Zipkin UI at it (e.g. run the consumer app on
|
||||
port 9411 if you want the query server on the same host and the
|
||||
default configuration).
|
||||
|
||||
The deafult `SpanStore` is in-memory (good for demos and getting
|
||||
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.:
|
||||
@@ -149,3 +262,11 @@ 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).
|
||||
|
||||
== Metrics
|
||||
|
||||
Currently Spring Cloud Sleuth registers very simple metrics related to spans.
|
||||
It's using the http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-metrics.html#production-ready-recording-metrics[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.
|
||||
@@ -11,12 +11,12 @@ import org.springframework.cloud.sleuth.Span;
|
||||
* <p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>We're taking the TraceID into consideration for sampling to be consistent</li>
|
||||
* <li>We're taking the trace id into consideration for sampling to be consistent</li>
|
||||
* <li>We apply the Zipkin algorithm to define whether we should sample or not (we're comparing against threshold)
|
||||
* - https://github.com/openzipkin/zipkin-java/blob/master/zipkin/src/main/java/zipkin/Sampler.java</li>
|
||||
* </ul>
|
||||
*
|
||||
* The value provided from SamplerConfiguration in terms of percentage is an estimation. It might occur that amount
|
||||
* The value provided from sampler configuration in terms of percentage is an estimation. It might occur that amount
|
||||
* of data sampled differs from the provided percentage.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* 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.documentation;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.BDDMockito;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.cloud.sleuth.DefaultSpanNamer;
|
||||
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.TraceRunnable;
|
||||
import org.springframework.cloud.sleuth.Tracer;
|
||||
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
|
||||
import org.springframework.cloud.sleuth.trace.DefaultTracer;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
|
||||
|
||||
/**
|
||||
* Test class to be embedded in the
|
||||
* {@code docs/src/main/asciidoc/spring-cloud-sleuth.adoc} file
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
public class SpringCloudSleuthDocTests {
|
||||
|
||||
|
||||
@Configuration
|
||||
public class SamplingConfiguration {
|
||||
// tag::always_sampler[]
|
||||
@Bean
|
||||
public Sampler defaultSampler() {
|
||||
return new AlwaysSampler();
|
||||
}
|
||||
// end::always_sampler[]
|
||||
}
|
||||
|
||||
// tag::span_name_annotation[]
|
||||
@SpanName("calculateTax")
|
||||
class TaxCountingRunnable implements Runnable {
|
||||
|
||||
@Override public void run() {
|
||||
// perform logic
|
||||
}
|
||||
}
|
||||
// end::span_name_annotation[]
|
||||
|
||||
@Test
|
||||
public void should_set_runnable_name_to_annotated_value()
|
||||
throws ExecutionException, InterruptedException {
|
||||
ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
SpanNamer spanNamer = new DefaultSpanNamer();
|
||||
Tracer tracer = Mockito.mock(Tracer.class);
|
||||
|
||||
// tag::span_name_annotated_runnable_execution[]
|
||||
Runnable runnable = new TraceRunnable(tracer, spanNamer, new TaxCountingRunnable());
|
||||
Future<?> future = executorService.submit(runnable);
|
||||
// ... some additional logic ...
|
||||
future.get();
|
||||
// end::span_name_annotated_runnable_execution[]
|
||||
|
||||
BDDMockito.then(tracer).should().joinTrace(BDDMockito.eq("calculateTax"), BDDMockito.any(Span.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_set_runnable_name_to_to_string_value()
|
||||
throws ExecutionException, InterruptedException {
|
||||
ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
SpanNamer spanNamer = new DefaultSpanNamer();
|
||||
Tracer tracer = Mockito.mock(Tracer.class);
|
||||
|
||||
// tag::span_name_to_string_runnable_execution[]
|
||||
Runnable runnable = new TraceRunnable(tracer, spanNamer, new Runnable() {
|
||||
@Override public void run() {
|
||||
// perform logic
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "calculateTax";
|
||||
}
|
||||
});
|
||||
Future<?> future = executorService.submit(runnable);
|
||||
// ... some additional logic ...
|
||||
future.get();
|
||||
// end::span_name_to_string_runnable_execution[]
|
||||
|
||||
BDDMockito.then(tracer).should().joinTrace(BDDMockito.eq("calculateTax"), BDDMockito.any(Span.class));
|
||||
executorService.shutdown();
|
||||
}
|
||||
|
||||
ApplicationEventPublisher publisher = Mockito.mock(ApplicationEventPublisher.class);
|
||||
Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), this.publisher, new DefaultSpanNamer());
|
||||
|
||||
@Test
|
||||
public void should_create_a_span_with_tracer() {
|
||||
String taxValue = "10";
|
||||
|
||||
// tag::manual_span_creation[]
|
||||
// Start a span. If there was a span present in this thread it will become
|
||||
// the `newSpan`'s parent.
|
||||
Span newSpan = this.tracer.startTrace("calculateTax");
|
||||
try {
|
||||
// ...
|
||||
// You can tag a span
|
||||
this.tracer.addTag("taxValue", taxValue);
|
||||
// ...
|
||||
// You can log an event on a span
|
||||
newSpan.logEvent("taxCalculated");
|
||||
} finally {
|
||||
// Once done remember to close the span. This will allow collecting
|
||||
// the span to send it to Zipkin
|
||||
this.tracer.close(newSpan);
|
||||
}
|
||||
// end::manual_span_creation[]
|
||||
|
||||
then(this.tracer.getCurrentSpan()).isNull();
|
||||
then(newSpan).isNotNull();
|
||||
then(newSpan.getAccumulatedMillis()).isGreaterThan(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_continue_a_span_with_tracer() throws Exception {
|
||||
ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
String taxValue = "10";
|
||||
Span initialSpan = this.tracer.startTrace("calculateTax");
|
||||
assertThat(initialSpan.tags()).doesNotContainKeys("taxValue");
|
||||
assertThat(initialSpan.logs()).extracting("event").doesNotContain("taxCalculated");
|
||||
|
||||
executorService.submit(() -> {
|
||||
// tag::manual_span_continuation[]
|
||||
// let's assume that we're in a thread Y and we've received
|
||||
// the `initialSpan` from thread X
|
||||
Span continuedSpan = this.tracer.continueSpan(initialSpan);
|
||||
try {
|
||||
// ...
|
||||
// You can tag a span
|
||||
this.tracer.addTag("taxValue", taxValue);
|
||||
// ...
|
||||
// You can log an event on a span
|
||||
continuedSpan.logEvent("taxCalculated");
|
||||
} finally {
|
||||
// Once done remember to detach the span. That way you'll
|
||||
// safely remove it from the current thread without closing it
|
||||
this.tracer.detach(continuedSpan);
|
||||
}
|
||||
// end::manual_span_continuation[]
|
||||
}
|
||||
).get();
|
||||
|
||||
this.tracer.close(initialSpan);
|
||||
then(this.tracer.getCurrentSpan()).isNull();
|
||||
then(initialSpan)
|
||||
.hasATag("taxValue", taxValue)
|
||||
.hasLoggedAnEvent("taxCalculated");
|
||||
executorService.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_join_a_span_with_tracer() throws Exception {
|
||||
ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
String commissionValue = "10";
|
||||
Span initialSpan = this.tracer.startTrace("calculateTax");
|
||||
assertThat(initialSpan.tags()).doesNotContainKeys("commissionValue");
|
||||
assertThat(initialSpan.logs()).extracting("event").doesNotContain("commissionCalculated");
|
||||
|
||||
executorService.submit(() -> {
|
||||
// tag::manual_span_joining[]
|
||||
// let's assume that we're in a thread Y and we've received
|
||||
// the `initialSpan` from thread X. `initialSpan` will be the parent
|
||||
// of the `joinedSpan`
|
||||
Span joinedSpan = this.tracer.joinTrace("calculateCommission", initialSpan);
|
||||
try {
|
||||
// ...
|
||||
// You can tag a span
|
||||
this.tracer.addTag("commissionValue", commissionValue);
|
||||
// ...
|
||||
// You can log an event on a span
|
||||
joinedSpan.logEvent("commissionCalculated");
|
||||
} finally {
|
||||
// Once done remember to close the span. This will allow collecting
|
||||
// the span to send it to Zipkin. The tags and events set on the
|
||||
// joinedSpan will not be present on the parent
|
||||
this.tracer.close(joinedSpan);
|
||||
}
|
||||
// end::manual_span_joining[]
|
||||
}
|
||||
).get();
|
||||
|
||||
this.tracer.close(initialSpan);
|
||||
then(this.tracer.getCurrentSpan()).isNull();
|
||||
assertThat(initialSpan.tags()).doesNotContainKeys("commissionValue");
|
||||
assertThat(initialSpan.logs()).extracting("event").doesNotContain("commissionCalculated");
|
||||
executorService.shutdown();
|
||||
}
|
||||
}
|
||||
@@ -106,9 +106,9 @@ public class TraceRunnableTests {
|
||||
this.executor.submit(new TraceRunnable(this.tracer, new DefaultSpanNamer(), runnable)).get();
|
||||
}
|
||||
|
||||
private void whenNonTraceableRunnableGetsSubmitted(Runnable callable)
|
||||
private void whenNonTraceableRunnableGetsSubmitted(Runnable runnable)
|
||||
throws Exception {
|
||||
this.executor.submit(callable).get();
|
||||
this.executor.submit(runnable).get();
|
||||
}
|
||||
|
||||
private Runnable runnableWithCustomToString(final AtomicReference<Span> span) {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.zipkin.stream.documentation;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.sleuth.zipkin.stream.EnableZipkinStreamServer;
|
||||
|
||||
/**
|
||||
* Test class to be embedded in the Zipkin Consumer part of
|
||||
* {@code docs/src/main/asciidoc/spring-cloud-sleuth.adoc}
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
// tag::zipkin_consumer[]
|
||||
@SpringBootApplication
|
||||
@EnableZipkinStreamServer
|
||||
public class Consumer {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Consumer.class, args);
|
||||
}
|
||||
}
|
||||
// end::zipkin_consumer[]
|
||||
Reference in New Issue
Block a user