diff --git a/README.adoc b/README.adoc index 964988f2e..556fce901 100644 --- a/README.adoc +++ b/README.adoc @@ -1,6 +1,10 @@ // Do not edit this file (e.g. go instead to src/main/asciidoc) :jdkversion: 1.8 +:github-tag: master +:github-repo: spring-cloud/spring-cloud-sleuth +:github-raw: http://raw.github.com/{github-repo}/{github-tag} +:github-code: http://github.com/{github-repo}/tree/{github-tag} image::https://circleci.com/gh/spring-cloud/spring-cloud-sleuth.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-sleuth"] image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/master/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"] @@ -72,7 +76,7 @@ the start and stop of a request are: Visualization of what *Span* and *Trace* will look in a system together with the Zipkin annotations: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/trace-id.png[Trace Info propagation] +image::{github-raw}/docs/src/main/asciidoc/images/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: @@ -86,7 +90,7 @@ That means that the current span has *Trace-Id* set to *X*, *Span-Id* set to *D* This is how the visualization of the parent / child relationship of spans would look like: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/parents.png[Parent child relationship] +image::{github-raw}/docs/src/main/asciidoc/images/parents.png[Parent child relationship] === Purpose @@ -96,11 +100,11 @@ In the following sections the example from the image above will be taken into co Altogether there are *10 spans* . If you go to traces in Zipkin you will see this number: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/zipkin-traces.png[Traces] +image::{github-raw}/docs/src/main/asciidoc/images/zipkin-traces.png[Traces] However if you pick a particular trace then you will see *7 spans*: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/zipkin-ui.png[Traces Info propagation] +image::{github-raw}/docs/src/main/asciidoc/images/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 @@ -128,15 +132,15 @@ Altogether *10* spans. .Click Pivotal Web Services icon to see it live! [caption="Click Pivotal Web Services icon to see it live!"] -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/", width=150, height=74] +image::{github-raw}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/", width=150, height=74] The dependency graph in Zipkin would look like this: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/dependencies.png[Dependencies] +image::{github-raw}/docs/src/main/asciidoc/images/dependencies.png[Dependencies] .Click Pivotal Web Services icon to see it live! [caption="Click Pivotal Web Services icon to see it live!"] -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/dependency", width=150, height=74] +image::{github-raw}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/dependency", width=150, height=74] ==== Log correlation @@ -156,7 +160,7 @@ If you're using a log aggregating tool like https://www.elastic.co/products/kiba http://www.splunk.com/[Splunk] etc. you can order the events that took place. An example of Kibana would look like this: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/kibana.png[Log correlation with Kibana] +image::{github-raw}/docs/src/main/asciidoc/images/kibana.png[Log correlation with Kibana] If you want to use https://www.elastic.co/guide/en/logstash/current/index.html[Logstash] here is the Grok pattern for Logstash: @@ -275,6 +279,49 @@ Below you can find an example of a Logback configuration (file named `https://gi NOTE: If you're using a custom `logback-spring.xml` then you have to pass the `spring.application.name` in `bootstrap` instead of `application` property file. Otherwise your custom logback file won't read the property properly. +===== Propagating Span Context + +The span context is the state that must get propagated to any child Spans across process boundaries. +Part of the Span Context is the Baggage. The trace and span IDs are a required part of the span context. +Baggage is an optional part. + +Baggage is a set of key:value pairs stored in the span context. Baggage travels together with the trace +and is attached to every span. Spring Cloud Sleuth will understand that a header is baggage related if the HTTP + header is prefixed with `baggage-` and for messaging it starts with `baggage_`. + +IMPORTANT: There's currently no limitation of the count or size of baggage items. However, keep in mind that +too many can decrease system throughput or increase RPC latency. In extreme cases, it could crash the app due +to exceeding transport-level message or header capacity. + +Example of setting span: + +[source,java] +---- +Unresolved directive in intro.adoc - include::http://raw.github.com/spring-cloud/spring-cloud-sleuth/master/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java[tags=baggage,indent=0] +---- + +====== Baggage vs. Span Tags + +Baggage travels with the trace (i.e. every child span contains the baggage of its parent). Zipkin has no knowledge of +baggage and will not even receive that information. + +Tags are attached to a specific span - they are presented for that particular span only. However you +can search by tag to find the trace, where there exists a span having the searched tag value. + +If you want to be able to lookup a span based on baggage, you should add corresponding entry as a tag in the root span. + +[source,java] +---- +@Autowired Tracer tracer; + +Span span = tracer.getCurrentSpan(); +String baggageKey = "key"; +String baggageValue = "foo"; +span.setBaggageItem(baggageKey, baggageValue); +tracer.addTag(baggageKey, baggageValue); +---- + + === Adding to the project ==== Only Sleuth (log correlation) @@ -290,7 +337,7 @@ the `spring-cloud-starter-sleuth` module to your project. org.springframework.cloud spring-cloud-dependencies - Brixton.RELEASE + Camden.RELEASE pom import @@ -311,7 +358,7 @@ the Spring BOM ---- dependencyManagement { <1> imports { - mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE" + mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.RELEASE" } } @@ -335,7 +382,7 @@ If you want both Sleuth and Zipkin just add the `spring-cloud-starter-zipkin` de org.springframework.cloud spring-cloud-dependencies - Brixton.RELEASE + Camden.RELEASE pom import @@ -356,7 +403,7 @@ the Spring BOM ---- dependencyManagement { <1> imports { - mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE" + mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.RELEASE" } } @@ -380,7 +427,7 @@ If you want both Sleuth and Zipkin just add the `spring-cloud-sleuth-stream` dep org.springframework.cloud spring-cloud-dependencies - Brixton.RELEASE + Camden.RELEASE pom import @@ -412,7 +459,7 @@ the Spring BOM ---- dependencyManagement { <1> imports { - mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE" + mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.RELEASE" } } @@ -442,7 +489,7 @@ dependency org.springframework.cloud spring-cloud-dependencies - Brixton.RELEASE + Camden.RELEASE pom import @@ -474,7 +521,7 @@ the Spring BOM ---- dependencyManagement { <1> imports { - mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE" + mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.RELEASE" } } @@ -495,22 +542,7 @@ and then just annotate your main class with `@EnableZipkinStreamServer` annotati [source,java] ---- -package example; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.sleuth.zipkin.stream.EnableZipkinStreamServer; - -@SpringBootApplication -@EnableZipkinStreamServer -public class ZipkinStreamServerApplication { - - public static void main(String[] args) throws Exception { - SpringApplication.run(ZipkinStreamServerApplication.class, args); - } - -} ----- +Unresolved directive in intro.adoc - include::http://raw.github.com/spring-cloud/spring-cloud-sleuth/master/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/main/java/example/ZipkinStreamServerApplication.java[] == Additional resources @@ -556,6 +588,9 @@ rest template, scheduled actions, message channels, zuul filters, feign client). works via Zipkin-compatible request headers. This propagation logic is defined and customized via `SpanInjector` and `SpanExtractor` implementations. +* Sleuth gives you the possibility to propagate context (also known as baggage) between processes. That means that if you set on a Span +a baggage element then it will be sent downstream either via HTTP or messaging to other processes. + * Provides simple metrics of accepted / dropped spans. * If `spring-cloud-sleuth-zipkin` then the app will generate and collect Zipkin-compatible traces. diff --git a/docs/src/main/asciidoc/README.adoc b/docs/src/main/asciidoc/README.adoc index 388990e86..f0f404eba 100644 --- a/docs/src/main/asciidoc/README.adoc +++ b/docs/src/main/asciidoc/README.adoc @@ -1,4 +1,8 @@ :jdkversion: 1.8 +:github-tag: master +:github-repo: spring-cloud/spring-cloud-sleuth +:github-raw: http://raw.github.com/{github-repo}/{github-tag} +:github-code: http://github.com/{github-repo}/tree/{github-tag} image::https://circleci.com/gh/spring-cloud/spring-cloud-sleuth.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-sleuth"] image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/master/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"] diff --git a/docs/src/main/asciidoc/features.adoc b/docs/src/main/asciidoc/features.adoc index f702a5058..44f81daa2 100644 --- a/docs/src/main/asciidoc/features.adoc +++ b/docs/src/main/asciidoc/features.adoc @@ -34,6 +34,9 @@ rest template, scheduled actions, message channels, zuul filters, feign client). works via Zipkin-compatible request headers. This propagation logic is defined and customized via `SpanInjector` and `SpanExtractor` implementations. +* Sleuth gives you the possibility to propagate context (also known as baggage) between processes. That means that if you set on a Span +a baggage element then it will be sent downstream either via HTTP or messaging to other processes. + * Provides simple metrics of accepted / dropped spans. * If `spring-cloud-sleuth-zipkin` then the app will generate and collect Zipkin-compatible traces. diff --git a/docs/src/main/asciidoc/intro.adoc b/docs/src/main/asciidoc/intro.adoc index c11a21da8..8c118fcea 100644 --- a/docs/src/main/asciidoc/intro.adoc +++ b/docs/src/main/asciidoc/intro.adoc @@ -33,7 +33,7 @@ the start and stop of a request are: Visualization of what *Span* and *Trace* will look in a system together with the Zipkin annotations: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/trace-id.png[Trace Info propagation] +image::{github-raw}/docs/src/main/asciidoc/images/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: @@ -47,7 +47,7 @@ That means that the current span has *Trace-Id* set to *X*, *Span-Id* set to *D* This is how the visualization of the parent / child relationship of spans would look like: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/parents.png[Parent child relationship] +image::{github-raw}/docs/src/main/asciidoc/images/parents.png[Parent child relationship] === Purpose @@ -57,11 +57,11 @@ In the following sections the example from the image above will be taken into co Altogether there are *10 spans* . If you go to traces in Zipkin you will see this number: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/zipkin-traces.png[Traces] +image::{github-raw}/docs/src/main/asciidoc/images/zipkin-traces.png[Traces] However if you pick a particular trace then you will see *7 spans*: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/zipkin-ui.png[Traces Info propagation] +image::{github-raw}/docs/src/main/asciidoc/images/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 @@ -89,15 +89,15 @@ Altogether *10* spans. .Click Pivotal Web Services icon to see it live! [caption="Click Pivotal Web Services icon to see it live!"] -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/", width=150, height=74] +image::{github-raw}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/", width=150, height=74] The dependency graph in Zipkin would look like this: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/dependencies.png[Dependencies] +image::{github-raw}/docs/src/main/asciidoc/images/dependencies.png[Dependencies] .Click Pivotal Web Services icon to see it live! [caption="Click Pivotal Web Services icon to see it live!"] -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/dependency", width=150, height=74] +image::{github-raw}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/dependency", width=150, height=74] ==== Log correlation @@ -117,7 +117,7 @@ If you're using a log aggregating tool like https://www.elastic.co/products/kiba http://www.splunk.com/[Splunk] etc. you can order the events that took place. An example of Kibana would look like this: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/kibana.png[Log correlation with Kibana] +image::{github-raw}/docs/src/main/asciidoc/images/kibana.png[Log correlation with Kibana] If you want to use https://www.elastic.co/guide/en/logstash/current/index.html[Logstash] here is the Grok pattern for Logstash: @@ -164,6 +164,49 @@ include::https://raw.githubusercontent.com/spring-cloud-samples/sleuth-documenta NOTE: If you're using a custom `logback-spring.xml` then you have to pass the `spring.application.name` in `bootstrap` instead of `application` property file. Otherwise your custom logback file won't read the property properly. +===== Propagating Span Context + +The span context is the state that must get propagated to any child Spans across process boundaries. +Part of the Span Context is the Baggage. The trace and span IDs are a required part of the span context. +Baggage is an optional part. + +Baggage is a set of key:value pairs stored in the span context. Baggage travels together with the trace +and is attached to every span. Spring Cloud Sleuth will understand that a header is baggage related if the HTTP + header is prefixed with `baggage-` and for messaging it starts with `baggage_`. + +IMPORTANT: There's currently no limitation of the count or size of baggage items. However, keep in mind that +too many can decrease system throughput or increase RPC latency. In extreme cases, it could crash the app due +to exceeding transport-level message or header capacity. + +Example of setting span: + +[source,java] +---- +include::{github-raw}/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java[tags=baggage,indent=0] +---- + +====== Baggage vs. Span Tags + +Baggage travels with the trace (i.e. every child span contains the baggage of its parent). Zipkin has no knowledge of +baggage and will not even receive that information. + +Tags are attached to a specific span - they are presented for that particular span only. However you +can search by tag to find the trace, where there exists a span having the searched tag value. + +If you want to be able to lookup a span based on baggage, you should add corresponding entry as a tag in the root span. + +[source,java] +---- +@Autowired Tracer tracer; + +Span span = tracer.getCurrentSpan(); +String baggageKey = "key"; +String baggageValue = "foo"; +span.setBaggageItem(baggageKey, baggageValue); +tracer.addTag(baggageKey, baggageValue); +---- + + === Adding to the project ==== Only Sleuth (log correlation) @@ -179,7 +222,7 @@ the `spring-cloud-starter-sleuth` module to your project. org.springframework.cloud spring-cloud-dependencies - Brixton.RELEASE + Camden.RELEASE pom import @@ -200,7 +243,7 @@ the Spring BOM ---- dependencyManagement { <1> imports { - mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE" + mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.RELEASE" } } @@ -224,7 +267,7 @@ If you want both Sleuth and Zipkin just add the `spring-cloud-starter-zipkin` de org.springframework.cloud spring-cloud-dependencies - Brixton.RELEASE + Camden.RELEASE pom import @@ -245,7 +288,7 @@ the Spring BOM ---- dependencyManagement { <1> imports { - mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE" + mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.RELEASE" } } @@ -269,7 +312,7 @@ If you want both Sleuth and Zipkin just add the `spring-cloud-sleuth-stream` dep org.springframework.cloud spring-cloud-dependencies - Brixton.RELEASE + Camden.RELEASE pom import @@ -301,7 +344,7 @@ the Spring BOM ---- dependencyManagement { <1> imports { - mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE" + mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.RELEASE" } } @@ -331,7 +374,7 @@ dependency org.springframework.cloud spring-cloud-dependencies - Brixton.RELEASE + Camden.RELEASE pom import @@ -363,7 +406,7 @@ the Spring BOM ---- dependencyManagement { <1> imports { - mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE" + mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.RELEASE" } } @@ -384,7 +427,7 @@ and then just annotate your main class with `@EnableZipkinStreamServer` annotati [source,java] ---- -include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/main/java/example/ZipkinStreamServerApplication.java[] +include::{github-raw}/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/main/java/example/ZipkinStreamServerApplication.java[] ---- == Additional resources diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index e44e6d44a..25fdbd1e5 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -28,13 +28,13 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + /** * Class for gathering and reporting statistics about a block of execution. *

@@ -73,7 +73,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; */ @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) @JsonInclude(JsonInclude.Include.NON_DEFAULT) -public class Span { +public class Span implements SpanContext { public static final String SAMPLED_NAME = "X-B3-Sampled"; public static final String PROCESS_ID_NAME = "X-Process-Id"; @@ -82,6 +82,7 @@ public class Span { public static final String SPAN_NAME_NAME = "X-Span-Name"; public static final String SPAN_ID_NAME = "X-B3-SpanId"; public static final String SPAN_EXPORT_NAME = "X-Span-Export"; + public static final String SPAN_BAGGAGE_HEADER_PREFIX = "baggage"; public static final Set SPAN_HEADERS = new HashSet<>( Arrays.asList(SAMPLED_NAME, PROCESS_ID_NAME, PARENT_ID_NAME, TRACE_ID_NAME, SPAN_ID_NAME, SPAN_NAME_NAME, SPAN_EXPORT_NAME)); @@ -146,6 +147,8 @@ public class Span { private final String processId; private final Collection logs; private final Span savedSpan; + @JsonIgnore + private final Map baggage; // Null means we don't know the start tick, so fallback to time @JsonIgnore @@ -176,6 +179,7 @@ public class Span { this.logs = current.logs; this.startNanos = current.startNanos; this.durationMicros = current.durationMicros; + this.baggage = current.baggage; this.savedSpan = savedSpan; } @@ -209,6 +213,7 @@ public class Span { this.savedSpan = savedSpan; this.tags = new ConcurrentHashMap<>(); this.logs = new ConcurrentLinkedQueue<>(); + this.baggage = new ConcurrentHashMap<>(); } public static SpanBuilder builder() { @@ -304,6 +309,40 @@ public class Span { this.logs.add(new Log(System.currentTimeMillis(), event)); } + /** + * Sets a baggage item in the Span (and its SpanContext) as a key/value pair. + * + * Baggage enables powerful distributed context propagation functionality where arbitrary application data can be + * carried along the full path of request execution throughout the system. + * + * Note 1: Baggage is only propagated to the future (recursive) children of this SpanContext. + * + * Note 2: Baggage is sent in-band with every subsequent local and remote calls, so this feature must be used with + * care. + * + * @return this Span instance, for chaining + */ + public Span setBaggageItem(String key, String value) { + this.baggage.put(key, value); + return this; + } + + /** + * @return the value of the baggage item identified by the given key, or null if no such item could be found + */ + public String getBaggageItem(String key) { + return this.baggage.get(key); + } + + @Override + public final Iterable> baggageItems() { + return this.baggage.entrySet(); + } + + public final Map getBaggage() { + return Collections.unmodifiableMap(this.baggage); + } + /** * Get tag data associated with this span (read only) *

@@ -493,6 +532,7 @@ public class Span { private Span savedSpan; private List logs = new ArrayList<>(); private Map tags = new LinkedHashMap<>(); + private Map baggage = new LinkedHashMap<>(); SpanBuilder() { } @@ -554,6 +594,16 @@ public class Span { return this; } + public Span.SpanBuilder baggage(String baggageKey, String baggageValue) { + this.baggage.put(baggageKey, baggageValue); + return this; + } + + public Span.SpanBuilder baggage(Map baggage) { + this.baggage.putAll(baggage); + return this; + } + public Span.SpanBuilder spanId(long spanId) { this.spanId = spanId; return this; @@ -585,6 +635,7 @@ public class Span { this.processId, this.savedSpan); span.logs.addAll(this.logs); span.tags.putAll(this.tags); + span.baggage.putAll(this.baggage); return span; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanContext.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanContext.java new file mode 100644 index 000000000..9752d0b00 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanContext.java @@ -0,0 +1,31 @@ +package org.springframework.cloud.sleuth; + +import java.util.Map; + +/** + * Adopted from: https://github.com/opentracing/opentracing-java/blob/0.16.0/opentracing-api/src/main/java/io/opentracing/SpanContext.java + * + * SpanContext represents Span state that must propagate to descendant Spans and across process boundaries. + * + * SpanContext is logically divided into two pieces: (1) the user-level "Baggage" that propagates across Span + * boundaries and (2) any Tracer-implementation-specific fields that are needed to identify or otherwise contextualize + * the associated Span instance (e.g., a tuple). + * + * The {@link SpanContext#baggageItems()} returns the map of user-level "Baggage". + * + * @see Span#setBaggageItem(String, String) + * @see Span#getBaggageItem(String) + * + * @author Marcin Grzejszczak + * @since 1.2.0 + */ +public interface SpanContext { + + /** + * @return all zero or more baggage items propagating along with the associated Span + * + * @see Span#setBaggageItem(String, String) + * @see Span#getBaggageItem(String) + */ + Iterable> baggageItems(); +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanTextMap.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanTextMap.java index 54e77e2d2..3fed538fb 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanTextMap.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanTextMap.java @@ -4,7 +4,7 @@ import java.util.Iterator; import java.util.Map; /** - * Adopted from: https://github.com/opentracing/opentracing-java/blob/master/opentracing-api/src/main/java/io/opentracing/propagation/TextMap.java + * Adopted from: https://github.com/opentracing/opentracing-java/blob/0.16.0/opentracing-api/src/main/java/io/opentracing/propagation/TextMap.java * * TextMap is a built-in carrier for {@link SpanInjector} and {@link SpanExtractor}. TextMap implementations allows Tracers to * read and write key:value String pairs from arbitrary underlying sources of data. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractor.java index 7d1349af1..a2c0d14c0 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractor.java @@ -43,6 +43,11 @@ public class HeaderBasedMessagingExtractor implements MessagingSpanTextMapExtrac } setParentIdIfApplicable(carrier, spanBuilder, TraceMessageHeaders.PARENT_ID_NAME); spanBuilder.remote(true); + for (Map.Entry entry : carrier.entrySet()) { + if (entry.getKey().startsWith(Span.SPAN_BAGGAGE_HEADER_PREFIX + TraceMessageHeaders.HEADER_DELIMITER)) { + spanBuilder.baggage(unprefixedKey(entry.getKey()), entry.getValue()); + } + } return spanBuilder.build(); } @@ -58,4 +63,8 @@ public class HeaderBasedMessagingExtractor implements MessagingSpanTextMapExtrac } } + private String unprefixedKey(String key) { + return key.substring(key.indexOf(TraceMessageHeaders.HEADER_DELIMITER) + 1); + } + } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjector.java index f5e840d8a..b74a8e4b2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjector.java @@ -56,6 +56,9 @@ public class HeaderBasedMessagingInjector implements MessagingSpanTextMapInjecto else { addHeader(textMap, TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); } + for (Map.Entry entry : span.baggageItems()) { + textMap.put(prefixedKey(entry.getKey()), entry.getValue()); + } } private void addAnnotations(TraceKeys traceKeys, SpanTextMap spanTextMap, Span span) { @@ -99,4 +102,11 @@ public class HeaderBasedMessagingInjector implements MessagingSpanTextMapInjecto return parents.isEmpty() ? null : parents.get(0); } + private String prefixedKey(String key) { + if (key.startsWith(Span.SPAN_BAGGAGE_HEADER_PREFIX + TraceMessageHeaders.HEADER_DELIMITER )) { + return key; + } + return Span.SPAN_BAGGAGE_HEADER_PREFIX + TraceMessageHeaders.HEADER_DELIMITER + key; + } + } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java index 4ed97a6b1..b5e123266 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java @@ -34,6 +34,7 @@ public class TraceMessageHeaders { public static final String SPAN_NAME_NAME = "spanName"; static final String MESSAGE_SENT_FROM_CLIENT = "messageSent"; + static final String HEADER_DELIMITER = "_"; private TraceMessageHeaders() {} } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanExtractor.java index d4e04865c..5f8aceffe 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanExtractor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanExtractor.java @@ -20,6 +20,7 @@ public class ZipkinHttpSpanExtractor implements HttpSpanExtractor { private static final org.apache.commons.logging.Log log = LogFactory.getLog( MethodHandles.lookup().lookupClass()); + private static final String HEADER_DELIMITER = "-"; static final String URI_HEADER = "X-Span-Uri"; private static final String HTTP_COMPONENT = "http"; @@ -83,7 +84,16 @@ public class ZipkinHttpSpanExtractor implements HttpSpanExtractor { if (skip) { span.exportable(false); } + for (Map.Entry entry : carrier.entrySet()) { + if (entry.getKey().startsWith(Span.SPAN_BAGGAGE_HEADER_PREFIX + HEADER_DELIMITER)) { + span.baggage(unprefixedKey(entry.getKey()), entry.getValue()); + } + } return span.build(); } + private String unprefixedKey(String key) { + return key.substring(key.indexOf(HEADER_DELIMITER) + 1); + } + } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjector.java index 5313950fd..9b05ca17d 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjector.java @@ -1,5 +1,7 @@ package org.springframework.cloud.sleuth.instrument.web; +import java.util.Map; + import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanTextMap; import org.springframework.util.StringUtils; @@ -12,6 +14,8 @@ import org.springframework.util.StringUtils; */ public class ZipkinHttpSpanInjector implements HttpSpanInjector { + private static final String HEADER_DELIMITER = "-"; + @Override public void inject(Span span, SpanTextMap carrier) { setIdHeader(carrier, Span.TRACE_ID_NAME, span.getTraceId()); @@ -20,22 +24,32 @@ public class ZipkinHttpSpanInjector implements HttpSpanInjector { setHeader(carrier, Span.SPAN_NAME_NAME, span.getName()); setIdHeader(carrier, Span.PARENT_ID_NAME, getParentId(span)); setHeader(carrier, Span.PROCESS_ID_NAME, span.getProcessId()); + for (Map.Entry entry : span.baggageItems()) { + carrier.put(prefixedKey(entry.getKey()), entry.getValue()); + } + } + + private String prefixedKey(String key) { + if (key.startsWith(Span.SPAN_BAGGAGE_HEADER_PREFIX + HEADER_DELIMITER)) { + return key; + } + return Span.SPAN_BAGGAGE_HEADER_PREFIX + HEADER_DELIMITER + key; } private Long getParentId(Span span) { return !span.getParents().isEmpty() ? span.getParents().get(0) : null; } - private void setHeader(SpanTextMap carrier, String name, String value) { - if (StringUtils.hasText(value)) { - carrier.put(name, value); - } - } - private void setIdHeader(SpanTextMap carrier, String name, Long value) { if (value != null) { setHeader(carrier, name, Span.idToHex(value)); } } + private void setHeader(SpanTextMap carrier, String name, String value) { + if (StringUtils.hasText(value)) { + carrier.put(name, value); + } + } + } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTracer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTracer.java index e1bc938b6..56bb4cefe 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTracer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTracer.java @@ -138,7 +138,7 @@ public class DefaultTracer implements Tracer { return savedSpan; } - protected Span createChild(Span parent, String name) { + Span createChild(Span parent, String name) { long id = createId(); if (parent == null) { Span span = Span.builder().name(name) @@ -154,7 +154,9 @@ public class DefaultTracer implements Tracer { Span span = Span.builder().name(name) .traceId(parent.getTraceId()).parent(parent.getSpanId()).spanId(id) .processId(parent.getProcessId()).savedSpan(parent) - .exportable(parent.isExportable()).build(); + .exportable(parent.isExportable()) + .baggage(parent.getBaggage()) + .build(); this.spanLogger.logStartedSpan(parent, span); return span; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java index 2a412d167..6636b9f58 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java @@ -51,4 +51,10 @@ public class ArrayListSpanAccumulator implements SpanReporter { this.spans.add(span); } } + + public void clear() { + synchronized (this.spans) { + this.spans.clear(); + } + } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java index 06fb23942..a81a5cd14 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java @@ -21,14 +21,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import com.fasterxml.jackson.databind.ObjectMapper; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.assertj.core.api.AbstractAssert; import org.springframework.cloud.sleuth.Span; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; @@ -89,6 +88,26 @@ public class ListOfSpansAssert extends AbstractAssert \nto ALL contain baggage with key " + + "equal to <%s>, and value equal to <%s>", spansToString(), baggageKey, baggageValue); + } + return this; + } + + public ListOfSpansAssert anySpanHasABaggage(String baggageKey, String baggageValue) { + isNotNull(); + printSpans(); + if (!hasBaggage(baggageKey, baggageValue)) { + failWithMessage("Expected spans \n <%s> \nto contain at least one span with baggage key " + + "equal to <%s>, and value equal to <%s>", spansToString(), baggageKey, baggageValue); + } + return this; + } + private boolean spanWithKeyTagExists(String tagKey) { for (Span span : this.actual.spans) { if (span.tags().containsKey(tagKey)) { @@ -98,6 +117,37 @@ public class ListOfSpansAssert extends AbstractAssert baggage : span.baggageItems()) { + if (baggage.getKey().equals(baggageKey)) { + if (baggage.getValue().equals(baggageValue)) { + exists = true; + break; + } + } + } + if (!exists) { + return false; + } + } + return exists; + } + + private boolean hasBaggage(String baggageKey, String baggageValue) { + for (Span span : this.actual.spans) { + for (Map.Entry baggage : span.baggageItems()) { + if (baggage.getKey().equals(baggageKey)) { + if (baggage.getValue().equals(baggageValue)) { + return true; + } + } + } + } + return false; + } + public ListOfSpansAssert hasASpanWithTagEqualTo(String tagKey, String tagValue) { isNotNull(); printSpans(); @@ -113,7 +163,8 @@ public class ListOfSpansAssert extends AbstractAssert "\nSPAN: " + span.toString() + " with name [" + span.getName() + "] " + - "\nwith tags " + span.tags() + "\nwith logs " + span.logs()).collect(joining("\n")); + "\nwith tags " + span.tags() + "\nwith logs " + span.logs() + + "\nwith baggage " + span.getBaggage()).collect(joining("\n")); } public ListOfSpansAssert doesNotHaveASpanWithName(String name) { @@ -143,16 +194,12 @@ public class ListOfSpansAssert extends AbstractAssert(this.actual.spans))); - } - catch (JsonProcessingException e) { - } + log.info("Stored spans " + spansToString()); } @Override protected void failWithMessage(String errorMessage, Object... arguments) { - log.error(errorMessage); + log.error(String.format(errorMessage, arguments)); super.failWithMessage(errorMessage, arguments); } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java index f0f5bd5ef..e0d942cea 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java @@ -99,6 +99,19 @@ public class SpanAssert extends AbstractAssert { return this; } + public SpanAssert hasBaggageItem(String baggageKey, String baggageValue) { + isNotNull(); + assertThatBaggageContainsKey(baggageKey); + String foundValue = this.actual.getBaggageItem(baggageKey); + if (!foundValue.equals(baggageValue)) { + String message = String.format("Expected span to have the baggage with key <%s> and value <%s>. " + + "Found value for that baggage is <%s>", baggageKey, baggageValue, foundValue); + log.error(message); + failWithMessage(message); + } + return this; + } + public SpanAssert hasATagWithKey(String tagKey) { isNotNull(); assertThatTagIsPresent(tagKey); @@ -134,6 +147,15 @@ public class SpanAssert extends AbstractAssert { } } + private void assertThatBaggageContainsKey(String baggageKey) { + if (!this.actual.getBaggage().containsKey(baggageKey)) { + String message = String.format("Expected span to have the baggage with key <%s>. " + + "Found baggage are <%s>", baggageKey, this.actual.getBaggage()); + log.error(message); + failWithMessage(message); + } + } + public SpanAssert hasLoggedAnEvent(String event) { isNotNull(); if (!this.actual.logs().stream().map(org.springframework.cloud.sleuth.Log::getEvent) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java index 6ce92052e..70e2b042f 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java @@ -1,51 +1,60 @@ package org.springframework.cloud.sleuth.instrument.web.multiple; +import java.net.URI; +import java.util.Collections; + +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanReporter; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.assertions.ListOfSpans; import org.springframework.cloud.sleuth.instrument.web.TraceFilter; -import org.springframework.cloud.sleuth.instrument.web.common.AbstractMvcIntegrationTest; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; +import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.ContextConfiguration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.RequestEntity; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; +import org.springframework.web.client.RestTemplate; import static com.jayway.awaitility.Awaitility.await; import static java.util.Arrays.asList; import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.BDDAssertions.then; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = MultipleHopsIntegrationTests.Config.class) -public class MultipleHopsIntegrationTests extends AbstractMvcIntegrationTest { +@SpringBootTest(classes = MultipleHopsIntegrationTests.Config.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class MultipleHopsIntegrationTests { @Autowired Tracer tracer; @Autowired TraceKeys traceKeys; @Autowired TraceFilter traceFilter; @Autowired ArrayListSpanAccumulator arrayListSpanAccumulator; @Autowired SpanReporter spanReporter; + @Autowired RestTemplate restTemplate; + @Autowired Config config; - @Override - protected void configureMockMvcBuilder(DefaultMockMvcBuilder mockMvcBuilder) { - mockMvcBuilder.addFilters(this.traceFilter); + @Before + public void setup() { + this.arrayListSpanAccumulator.clear(); } @Test public void should_prepare_spans_for_export() throws Exception { - this.mockMvc.perform(get("/greeting")).andExpect( - MockMvcResultMatchers.status().isOk()); + this.restTemplate.getForObject("http://localhost:" + this.config.port + "/greeting", String.class); await().atMost(5, SECONDS).until(() -> { then(this.arrayListSpanAccumulator.getSpans().stream().map(Span::getName) @@ -55,9 +64,46 @@ public class MultipleHopsIntegrationTests extends AbstractMvcIntegrationTest { }); } + // issue #237 - baggage + @Test + public void should_propagate_the_baggage() throws Exception { + //tag::baggage[] + Span initialSpan = this.tracer.createSpan("span"); + initialSpan.setBaggageItem("foo", "bar"); + //end::baggage[] + + try { + HttpHeaders headers = new HttpHeaders(); + headers.put("baggage-baz", Collections.singletonList("baz")); + RequestEntity requestEntity = new RequestEntity(headers, HttpMethod.GET, + URI.create("http://localhost:" + this.config.port + "/greeting")); + this.restTemplate.exchange(requestEntity, String.class); + + await().atMost(5, SECONDS).until(() -> { + then(new ListOfSpans(this.arrayListSpanAccumulator.getSpans())) + .everySpanHasABaggage("foo", "bar") + .anySpanHasABaggage("baz", "baz"); + }); + } finally { + this.tracer.close(initialSpan); + } + } + @Configuration @SpringBootApplication - public static class Config { + public static class Config implements + ApplicationListener { + int port; + + @Override + public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) { + this.port = event.getEmbeddedServletContainer().getPort(); + } + + @Bean + RestTemplate restTemplate() { + return new RestTemplate(); + } @Bean ArrayListSpanAccumulator arrayListSpanAccumulator() { return new ArrayListSpanAccumulator(); 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 74058665e..207b7564c 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 @@ -185,6 +185,38 @@ public class DefaultTracerTests { then(span).isEqualTo(continuedSpan); } + @Test + public void shouldPropagateBaggageFromParentToChild() { + DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), + this.spanNamer, this.spanLogger, this.spanReporter); + Span parent = Span.builder().name(IMPORTANT_WORK_1).traceId(1L).spanId(1L) + .baggage("foo", "bar").build(); + Span child = tracer.createSpan("child", parent); + + then(parent).hasBaggageItem("foo", "bar"); + then(child).hasBaggageItem("foo", "bar"); + } + + @Test + public void shouldPropagateBaggageToContinuedSpan() { + DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), + this.spanNamer, this.spanLogger, this.spanReporter); + Span parent = Span.builder().name(IMPORTANT_WORK_1).traceId(1L).spanId(1L) + .baggage("foo", "bar").build(); + Span continuedSpan = tracer.continueSpan(parent); + + parent.setBaggageItem("baz1", "baz1"); + continuedSpan.setBaggageItem("baz2", "baz2"); + + then(parent).hasBaggageItem("foo", "bar") + .hasBaggageItem("baz1", "baz1") + .hasBaggageItem("baz2", "baz2"); + then(continuedSpan).hasBaggageItem("foo", "bar") + .hasBaggageItem("baz1", "baz1") + .hasBaggageItem("baz2", "baz2"); + then(parent).isEqualTo(continuedSpan); + } + private Span assertSpan(List spans, Long parentId, String name) { List found = findSpans(spans, parentId); assertThat(found).as("More than one span with parentId %s", parentId).hasSize(1); diff --git a/spring-cloud-sleuth-samples/README.adoc b/spring-cloud-sleuth-samples/README.adoc index 3dc8573a0..936904d5c 100644 --- a/spring-cloud-sleuth-samples/README.adoc +++ b/spring-cloud-sleuth-samples/README.adoc @@ -27,7 +27,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[Sample Zipkin Screenshot] +image::{github-raw}/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). @@ -42,4 +42,4 @@ Instead of POSTing trace data directly to a Zipkin server, you can export them o The UI should look more or less like this: -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/zipkin-traces.png[Zipkin Web Screenshot] +image::{github-raw}/docs/src/main/asciidoc/images/zipkin-traces.png[Zipkin Web Screenshot]