Added baggage support (#440)

without this change there is no support for context propagation
with this change whenever you pass the `baggage-...` for http or `baggage_` for messaging headers then such a value will be propagated through your system

fixes #237
This commit is contained in:
Marcin Grzejszczak
2016-11-10 15:36:47 +01:00
committed by GitHub
parent 9ab37c34fa
commit 96df523557
19 changed files with 456 additions and 90 deletions

View File

@@ -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.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<version>Camden.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -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
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<version>Camden.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -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
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<version>Camden.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -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
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<version>Camden.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -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.

View File

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

View File

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

View File

@@ -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.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<version>Camden.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -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
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<version>Camden.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -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
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<version>Camden.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -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
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<version>Camden.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -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

View File

@@ -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.
* <p>
@@ -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<String> 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<Log> logs;
private final Span savedSpan;
@JsonIgnore
private final Map<String,String> 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<Map.Entry<String,String>> baggageItems() {
return this.baggage.entrySet();
}
public final Map<String,String> getBaggage() {
return Collections.unmodifiableMap(this.baggage);
}
/**
* Get tag data associated with this span (read only)
* <p/>
@@ -493,6 +532,7 @@ public class Span {
private Span savedSpan;
private List<Log> logs = new ArrayList<>();
private Map<String, String> tags = new LinkedHashMap<>();
private Map<String, String> 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<String, String> 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;
}

View File

@@ -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 <trace_id, span_id, sampled> 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<Map.Entry<String, String>> baggageItems();
}

View File

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

View File

@@ -43,6 +43,11 @@ public class HeaderBasedMessagingExtractor implements MessagingSpanTextMapExtrac
}
setParentIdIfApplicable(carrier, spanBuilder, TraceMessageHeaders.PARENT_ID_NAME);
spanBuilder.remote(true);
for (Map.Entry<String, String> 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);
}
}

View File

@@ -56,6 +56,9 @@ public class HeaderBasedMessagingInjector implements MessagingSpanTextMapInjecto
else {
addHeader(textMap, TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED);
}
for (Map.Entry<String, String> 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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -51,4 +51,10 @@ public class ArrayListSpanAccumulator implements SpanReporter {
this.spans.add(span);
}
}
public void clear() {
synchronized (this.spans) {
this.spans.clear();
}
}
}

View File

@@ -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<ListOfSpansAssert, ListOfS
return this;
}
public ListOfSpansAssert everySpanHasABaggage(String baggageKey, String baggageValue) {
isNotNull();
printSpans();
if (!everySpanHasBaggage(baggageKey, baggageValue)) {
failWithMessage("Expected spans \n <%s> \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<ListOfSpansAssert, ListOfS
return false;
}
private boolean everySpanHasBaggage(String baggageKey, String baggageValue) {
boolean exists = false;
for (Span span : this.actual.spans) {
for (Map.Entry<String, String> 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<String, String> 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<ListOfSpansAssert, ListOfS
private String spansToString() {
return this.actual.spans.stream().map(span -> "\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<ListOfSpansAssert, ListOfS
}
private void printSpans() {
try {
log.info("Stored spans " + this.objectMapper.writeValueAsString(new ArrayList<>(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);
}
}

View File

@@ -99,6 +99,19 @@ public class SpanAssert extends AbstractAssert<SpanAssert, Span> {
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<SpanAssert, Span> {
}
}
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)

View File

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

View File

@@ -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<Span> spans, Long parentId, String name) {
List<Span> found = findSpans(spans, parentId);
assertThat(found).as("More than one span with parentId %s", parentId).hasSize(1);

View File

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