[#79] Updated documentation

* first iteration

fixes #79
This commit is contained in:
Marcin Grzejszczak
2016-03-03 11:25:38 +01:00
parent 0fe82089a2
commit 4b61439ab9
14 changed files with 522 additions and 28 deletions

View File

@@ -25,6 +25,7 @@
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.3</version>
<inherited>false</inherited>
</plugin>
<plugin>

View File

@@ -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[]

View File

@@ -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*.

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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) {

View File

@@ -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[]