Sleuth now uses Brave (#829)

with this pull request we have rewritten the whole Sleuth internals to use Brave. That way we can leverage all the functionalities & instrumentations that Brave already has (https://github.com/openzipkin/brave/tree/master/instrumentation).

Migration guide is available here: https://github.com/spring-cloud/spring-cloud-sleuth/wiki/Spring-Cloud-Sleuth-2.0-Migration-Guide

fixes #711 - Brave instrumentation
fixes #92 - we move to Brave's Sampler
fixes #143 - Brave is capable of passing context
fixes #255 - we've moved away from Zipkin Stream server
fixes #305 - Brave has GRPC instrumentation (https://github.com/openzipkin/brave/tree/master/instrumentation/grpc)
fixes #459 - Brave (openzipkin/brave#510) & Zipkin (openzipkin/zipkin#1754) will deal with the AWS XRay instrumentation
fixes #577 - Messaging instrumentation has been rewritten
This commit is contained in:
Marcin Grzejszczak
2018-01-19 22:45:47 +01:00
committed by GitHub
parent 9f716d7d92
commit 7eb374b5a5
370 changed files with 8165 additions and 18115 deletions

View File

@@ -8,7 +8,7 @@
:github-code: https://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"]
image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/{github-tag}/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"]
image::https://badges.gitter.im/spring-cloud/spring-cloud-sleuth.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-sleuth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"]
== Spring Cloud Sleuth
@@ -66,8 +66,12 @@ of that span is equal to trace id.
*Trace:* A set of spans forming a tree-like structure. For example, if you are running a distributed
big-data store, a trace might be formed by a put request.
*Annotation:* is used to record existence of an event in time. Some of the core annotations used to define
the start and stop of a request are:
*Annotation:* is used to record existence of an event in time. With
https://github.com/openzipkin/brave[Brave] instrumentation we no longer need to set special events
for https://zipkin.io/[Zipkin] to understand who the client and server are and where
the request started and where it has ended. For learning purposes
however we will mark these events to highlight what kind
of an action took place.
- *cs* - Client Sent - The client has made a request. This annotation depicts the start of the span.
- *sr* - Server Received - The server side got the request and will start processing it.
@@ -90,8 +94,8 @@ Trace Id = X
Span Id = D
Client Sent
That means that the current span has *Trace-Id* set to *X*, *Span-Id* set to *D*. It also has emitted
*Client Sent* event.
That means that the current span has *Trace-Id* set to *X*, *Span-Id* set to *D*. Also, the
*Client Sent* event took place.
This is how the visualization of the parent / child relationship of spans would look like:
@@ -118,14 +122,14 @@ annotations then they will presented as a single span.
Why is there a difference between the 7 and 4 spans in this case?
- 2 spans come from `http:/start` span. It has the Server Received (SR) and Server Sent (SS) annotations.
- 2 spans come from the RPC call from `service1` to `service2` to the `http:/foo` endpoint. It has the Client Sent (CS)
and Client Received (CR) annotations on `service1` side. It also has Server Received (SR) and Server Sent (SS) annotations
- 2 spans come from the RPC call from `service1` to `service2` to the `http:/foo` endpoint. The Client Sent (CS)
and Client Received (CR) events took place on `service1` side. Server Received (SR) and Server Sent (SS) events took place
on the `service2` side. Physically there are 2 spans but they form 1 logical span related to an RPC call.
- 2 spans come from the RPC call from `service2` to `service3` to the `http:/bar` endpoint. It has the Client Sent (CS)
and Client Received (CR) annotations on `service2` side. It also has Server Received (SR) and Server Sent (SS) annotations
- 2 spans come from the RPC call from `service2` to `service3` to the `http:/bar` endpoint. The Client Sent (CS)
and Client Received (CR) events took place on `service2` side. Server Received (SR) and Server Sent (SS) events took place
on the `service3` side. Physically there are 2 spans but they form 1 logical span related to an RPC call.
- 2 spans come from the RPC call from `service2` to `service4` to the `http:/baz` endpoint. It has the Client Sent (CS)
and Client Received (CR) annotations on `service2` side. It also has Server Received (SR) and Server Sent (SS) annotations
- 2 spans come from the RPC call from `service2` to `service4` to the `http:/baz` endpoint. The Client Sent (CS)
and Client Received (CR) events took place on `service2` side. Server Received (SR) and Server Sent (SS) events took place
on the `service4` side. Physically there are 2 spans but they form 1 logical span related to an RPC call.
So if we count the physical spans we have *1* from `http:/start`, *2* from `service1` calling `service2`, *2* form `service2`
@@ -150,11 +154,25 @@ image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branc
As you can see you can easily see the reason for an error and the whole stacktrace related to it.
==== Distributed tracing with Brave
Starting with version `2.0.0`, Spring Cloud Sleuth uses
https://github.com/openzipkin/brave[Brave] as the tracing library. That means
that Sleuth no longer takes care of storing the context but it delegates
that work to Brave.
Due to the fact that Sleuth had different naming / tagging
conventions than Brave, we've decided to follow the Brave's
conventions from now on. However, if you want to use the legacy
Sleuth approaches, it's enough to set the `spring.sleuth.http.legacy.enabled` property
to `true`.
==== Live examples
.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/{branch}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/", width=150, height=74]
http://docssleuth-zipkin-server.cfapps.io/[Click here to see it live!]
The dependency graph in Zipkin would look like this:
@@ -163,7 +181,7 @@ image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branc
.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/{branch}/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]
http://docssleuth-zipkin-server.cfapps.io/dependency[Click here to see it live!]
==== Log correlation
@@ -322,9 +340,8 @@ Example of setting baggage on a span:
[source,java]
----
Span initialSpan = this.tracer.createSpan("span");
initialSpan.setBaggageItem("foo", "bar");
initialSpan.setBaggageItem("UPPER_CASE", "someValue");
Unresolved directive in intro.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/brave/instrument/web/multiple/MultipleHopsIntegrationTests.java[tags=baggage,indent=0]
}
----
===== Baggage vs. Span Tags
@@ -337,15 +354,11 @@ can search by tag to find the trace, where there exists a span having the search
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.
IMPORTANT: Remember that the span needs to be in scope!
[source,java]
----
@Autowired Tracer tracer;
Span span = tracer.getCurrentSpan();
String baggageKey = "key";
String baggageValue = "foo";
span.setBaggageItem(baggageKey, baggageValue);
tracer.addTag(baggageKey, baggageValue);
Unresolved directive in intro.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/brave/instrument/web/multiple/MultipleHopsIntegrationTests.java[tags=baggage_tag,indent=0]
----
=== Adding to the project
@@ -361,7 +374,7 @@ the `spring-cloud-starter-sleuth` module to your project.
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<dependencyManagement> <1>
<dependencyManagement> <1>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
@@ -406,7 +419,7 @@ If you want both Sleuth and Zipkin just add the `spring-cloud-starter-zipkin` de
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<dependencyManagement> <1>
<dependencyManagement> <1>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
@@ -457,7 +470,7 @@ dependencies.
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<dependencyManagement> <1>
<dependencyManagement> <1>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.annotation.PreDestroy;
import org.apache.commons.logging.Log;
@@ -30,9 +29,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
@@ -40,9 +39,7 @@ import org.springframework.cloud.sleuth.annotation.ContinueSpan;
import org.springframework.cloud.sleuth.annotation.NewSpan;
import org.springframework.cloud.sleuth.annotation.SpanTag;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
@@ -102,9 +99,9 @@ public class SleuthBenchmarkingSpringApp implements
}
@Bean
public EmbeddedServletContainerFactory servletContainer(@Value("${server.port:0}") int serverPort) {
public ServletWebServerFactory servletContainer(@Value("${server.port:0}") int serverPort) {
log.info("Starting container at port [" + serverPort + "]");
return new TomcatEmbeddedServletContainerFactory(serverPort == 0 ? SocketUtils.findAvailableTcpPort() : serverPort);
return new TomcatServletWebServerFactory(serverPort == 0 ? SocketUtils.findAvailableTcpPort() : serverPort);
}
@PreDestroy

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
#
# Copyright 2013-2017 the original author or authors.
# Copyright 2013-2018 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -6,7 +6,7 @@
:github-code: https://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"]
image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/{github-tag}/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"]
image::https://badges.gitter.im/spring-cloud/spring-cloud-sleuth.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-sleuth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"]
== Spring Cloud Sleuth

View File

@@ -22,8 +22,12 @@ of that span is equal to trace id.
*Trace:* A set of spans forming a tree-like structure. For example, if you are running a distributed
big-data store, a trace might be formed by a put request.
*Annotation:* is used to record existence of an event in time. Some of the core annotations used to define
the start and stop of a request are:
*Annotation:* is used to record existence of an event in time. With
https://github.com/openzipkin/brave[Brave] instrumentation we no longer need to set special events
for https://zipkin.io/[Zipkin] to understand who the client and server are and where
the request started and where it has ended. For learning purposes
however we will mark these events to highlight what kind
of an action took place.
- *cs* - Client Sent - The client has made a request. This annotation depicts the start of the span.
- *sr* - Server Received - The server side got the request and will start processing it.
@@ -46,8 +50,8 @@ Trace Id = X
Span Id = D
Client Sent
That means that the current span has *Trace-Id* set to *X*, *Span-Id* set to *D*. It also has emitted
*Client Sent* event.
That means that the current span has *Trace-Id* set to *X*, *Span-Id* set to *D*. Also, the
*Client Sent* event took place.
This is how the visualization of the parent / child relationship of spans would look like:
@@ -74,14 +78,14 @@ annotations then they will presented as a single span.
Why is there a difference between the 7 and 4 spans in this case?
- 2 spans come from `http:/start` span. It has the Server Received (SR) and Server Sent (SS) annotations.
- 2 spans come from the RPC call from `service1` to `service2` to the `http:/foo` endpoint. It has the Client Sent (CS)
and Client Received (CR) annotations on `service1` side. It also has Server Received (SR) and Server Sent (SS) annotations
- 2 spans come from the RPC call from `service1` to `service2` to the `http:/foo` endpoint. The Client Sent (CS)
and Client Received (CR) events took place on `service1` side. Server Received (SR) and Server Sent (SS) events took place
on the `service2` side. Physically there are 2 spans but they form 1 logical span related to an RPC call.
- 2 spans come from the RPC call from `service2` to `service3` to the `http:/bar` endpoint. It has the Client Sent (CS)
and Client Received (CR) annotations on `service2` side. It also has Server Received (SR) and Server Sent (SS) annotations
- 2 spans come from the RPC call from `service2` to `service3` to the `http:/bar` endpoint. The Client Sent (CS)
and Client Received (CR) events took place on `service2` side. Server Received (SR) and Server Sent (SS) events took place
on the `service3` side. Physically there are 2 spans but they form 1 logical span related to an RPC call.
- 2 spans come from the RPC call from `service2` to `service4` to the `http:/baz` endpoint. It has the Client Sent (CS)
and Client Received (CR) annotations on `service2` side. It also has Server Received (SR) and Server Sent (SS) annotations
- 2 spans come from the RPC call from `service2` to `service4` to the `http:/baz` endpoint. The Client Sent (CS)
and Client Received (CR) events took place on `service2` side. Server Received (SR) and Server Sent (SS) events took place
on the `service4` side. Physically there are 2 spans but they form 1 logical span related to an RPC call.
So if we count the physical spans we have *1* from `http:/start`, *2* from `service1` calling `service2`, *2* form `service2`
@@ -106,11 +110,25 @@ image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branc
As you can see you can easily see the reason for an error and the whole stacktrace related to it.
==== Distributed tracing with Brave
Starting with version `2.0.0`, Spring Cloud Sleuth uses
https://github.com/openzipkin/brave[Brave] as the tracing library. That means
that Sleuth no longer takes care of storing the context but it delegates
that work to Brave.
Due to the fact that Sleuth had different naming / tagging
conventions than Brave, we've decided to follow the Brave's
conventions from now on. However, if you want to use the legacy
Sleuth approaches, it's enough to set the `spring.sleuth.http.legacy.enabled` property
to `true`.
==== Live examples
.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/{branch}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/", width=150, height=74]
http://docssleuth-zipkin-server.cfapps.io/[Click here to see it live!]
The dependency graph in Zipkin would look like this:
@@ -119,7 +137,7 @@ image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branc
.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/{branch}/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]
http://docssleuth-zipkin-server.cfapps.io/dependency[Click here to see it live!]
==== Log correlation
@@ -203,7 +221,8 @@ Example of setting baggage on a 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]
include::{github-raw}/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/brave/instrument/web/multiple/MultipleHopsIntegrationTests.java[tags=baggage,indent=0]
}
----
===== Baggage vs. Span Tags
@@ -216,15 +235,11 @@ can search by tag to find the trace, where there exists a span having the search
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.
IMPORTANT: Remember that the span needs to be in scope!
[source,java]
----
@Autowired Tracer tracer;
Span span = tracer.getCurrentSpan();
String baggageKey = "key";
String baggageValue = "foo";
span.setBaggageItem(baggageKey, baggageValue);
tracer.addTag(baggageKey, baggageValue);
include::{github-raw}/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/brave/instrument/web/multiple/MultipleHopsIntegrationTests.java[tags=baggage_tag,indent=0]
----
=== Adding to the project
@@ -240,7 +255,7 @@ the `spring-cloud-starter-sleuth` module to your project.
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<dependencyManagement> <1>
<dependencyManagement> <1>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
@@ -285,7 +300,7 @@ If you want both Sleuth and Zipkin just add the `spring-cloud-starter-zipkin` de
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<dependencyManagement> <1>
<dependencyManagement> <1>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
@@ -336,7 +351,7 @@ dependencies.
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<dependencyManagement> <1>
<dependencyManagement> <1>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>

View File

@@ -19,29 +19,287 @@ include::intro.adoc[]
include::features.adoc[]
=== Introduction to Brave
IMPORTANT: Starting with version `2.0.0` Spring Cloud Sleuth uses
https://github.com/openzipkin/brave[Brave] as the tracing library.
For your convenience we're embedding part of the Brave's docs here.
Brave is a library used to capture and report latency information about
distributed operations to Zipkin. Most users won't use Brave directly,
rather libraries or frameworks than employ Brave on their behalf.
This module includes tracer creates and joins spans that model the
latency of potentially distributed work. It also includes libraries to
propagate the trace context over network boundaries, for example, via
http headers.
==== Tracing
Most importantly, you need a `brave.Tracer`, configured to [report to Zipkin]
(https://github.com/openzipkin/zipkin-reporter-java).
Here's an example setup that sends trace data (spans) to Zipkin over
http (as opposed to Kafka).
```java
class MyClass {
private final Tracer tracer;
// Tracer will be autowired
MyClass(Tracer tracer) {
this.tracer = tracer;
}
void doSth() {
Span span = tracer.newTrace().name("encode").start();
// ...
}
}
```
IMPORTANT: If your span contains a name greater than 50 chars, then that name will
be truncated to 50 chars. Your names have to be explicit and concrete. Big names lead to
latency issues and sometimes even thrown exceptions.
==== Tracing
The tracer creates and joins spans that model the latency of potentially
distributed work. It can employ sampling to reduce overhead in process
or to reduce the amount of data sent to Zipkin.
Spans returned by a tracer report data to Zipkin when finished, or do
nothing if unsampled. After starting a span, you can annotate events of
interest or add tags containing details or lookup keys.
Spans have a context which includes trace identifiers that place it at
the correct spot in the tree representing the distributed operation.
==== Local Tracing
When tracing local code, just run it inside a span.
```java
Span span = tracer.newTrace().name("encode").start();
try {
doSomethingExpensive();
} finally {
span.finish();
}
```
In the above example, the span is the root of the trace. In many cases,
you will be a part of an existing trace. When this is the case, call
`newChild` instead of `newTrace`
```java
Span span = tracer.newChild(root.context()).name("encode").start();
try {
doSomethingExpensive();
} finally {
span.finish();
}
```
==== Customizing spans
Once you have a span, you can add tags to it, which can be used as lookup
keys or details. For example, you might add a tag with your runtime
version.
```java
span.tag("clnt/finagle.version", "6.36.0");
```
When exposing the ability to customize spans to third parties, prefer
`brave.SpanCustomizer` as opposed to `brave.Span`. The former is simpler to
understand and test, and doesn't tempt users with span lifecycle hooks.
```java
interface MyTraceCallback {
void request(Request request, SpanCustomizer customizer);
}
```
Since `brave.Span` implements `brave.SpanCustomizer`, it is just as easy for you
to pass to users.
Ex.
```java
for (MyTraceCallback callback : userCallbacks) {
callback.request(request, span);
}
```
==== Implicitly looking up the current span
Sometimes you won't know if a trace is in progress or not, and you don't
want users to do null checks. `brave.CurrentSpanCustomizer` adds to any
span that's in progress or drops data accordingly.
Ex.
```java
// user code can then inject this without a chance of it being null.
@Autowire SpanCustomizer span;
void userCode() {
span.annotate("tx.started");
...
}
```
==== RPC tracing
Check for https://github.com/openzipkin/sleuth/tree/master/instrumentation[instrumentation written here]
and http://zipkin.io/pages/existing_instrumentations.html[Zipkin's list]
before rolling your own RPC instrumentation!
RPC tracing is often done automatically by interceptors. Under the scenes,
they add tags and events that relate to their role in an RPC operation.
Here's an example of a client span:
```java
// before you send a request, add metadata that describes the operation
span = tracer.newTrace().name("get").type(CLIENT);
span.tag("clnt/finagle.version", "6.36.0");
span.tag(TraceKeys.HTTP_PATH, "/api");
span.remoteEndpoint(Endpoint.builder()
.serviceName("backend")
.ipv4(127 << 24 | 1)
.port(8080).build());
// when the request is scheduled, start the span
span.start();
// if you have callbacks for when data is on the wire, note those events
span.annotate(Constants.WIRE_SEND);
span.annotate(Constants.WIRE_RECV);
// when the response is complete, finish the span
span.finish();
```
===== One-Way tracing
Sometimes you need to model an asynchronous operation, where there is a
request, but no response. In normal RPC tracing, you use `span.finish()`
which indicates the response was received. In one-way tracing, you use
`span.flush()` instead, as you don't expect a response.
Here's how a client might model a one-way operation
```java
// start a new span representing a client request
oneWaySend = tracer.newSpan(parent).kind(Span.Kind.CLIENT);
// Add the trace context to the request, so it can be propagated in-band
tracing.propagation().injector(Request::addHeader)
.inject(oneWaySend.context(), request);
// fire off the request asynchronously, totally dropping any response
request.execute();
// start the client side and flush instead of finish
oneWaySend.start().flush();
```
And here's how a server might handle this..
```java
// pull the context out of the incoming request
extractor = tracing.propagation().extractor(Request::getHeader);
// convert that context to a span which you can name and add tags to
oneWayReceive = nextSpan(tracer, extractor.extract(request))
.name("process-request")
.kind(SERVER)
... add tags etc.
// start the server side and flush instead of finish
oneWayReceive.start().flush();
// you should not modify this span anymore as it is complete. However,
// you can create children to represent follow-up work.
next = tracer.newSpan(oneWayReceive.context()).name("step2").start();
```
**Note** The above propagation logic is a simplified version of our [http handlers](https://github.com/openzipkin/sleuth/tree/master/instrumentation/http#http-server).
There's a working example of a one-way span [here](src/test/java/sleuth/features/async/OneWaySpanTest.java).
== Sampling
In distributed tracing the data volumes can be very high so sampling
can be important (you usually don't need to export all spans to get a
good picture of what is happening). Spring Cloud Sleuth has a
`Sampler` strategy that you can implement to take control of the
sampling algorithm. Samplers do not stop span (correlation) ids from
being generated, but they do prevent the tags and events being
attached and exported. By default you get a strategy that continues to
trace if a span is already active, but new ones are always marked as
non-exportable. If all your apps run with this sampler you will see
traces in logs, but not in any remote store. For testing the default
is often enough, and it probably is all you need if you are only using
the logs (e.g. with an ELK aggregator). If you are exporting span data
to Zipkin or Spring Cloud Stream, there is also an `AlwaysSampler`
that exports everything and a `PercentageBasedSampler` that samples a
Sampling may be employed to reduce the data collected and reported out
of process. When a span isn't sampled, it adds no overhead (noop).
Sampling is an up-front decision, meaning that the decision to report
data is made at the first operation in a trace, and that decision is
propagated downstream.
By default, there's a global sampler that applies a single rate to all
traced operations. `Tracer.Builder.sampler` is how you indicate this,
and it defaults to trace every request.
=== Declarative sampling
Some need to sample based on the type or annotations of a java method.
Most users will use a framework interceptor which automates this sort of
policy. Here's how they might work internally.
```java
// derives a sample rate from an annotation on a java method
DeclarativeSampler<Traced> sampler = DeclarativeSampler.create(Traced::sampleRate);
@Around("@annotation(traced)")
public Object traceThing(ProceedingJoinPoint pjp, Traced traced) throws Throwable {
Span span = tracing.tracer().newTrace(sampler.sample(traced))...
try {
return pjp.proceed();
} finally {
span.finish();
}
}
```
=== Custom sampling
You may want to apply different policies depending on what the operation
is. For example, you might not want to trace requests to static resources
such as images, or you might want to trace all requests to a new api.
Most users will use a framework interceptor which automates this sort of
policy. Here's how they might work internally.
```java
Span newTrace(Request input) {
SamplingFlags flags = SamplingFlags.NONE;
if (input.url().startsWith("/experimental")) {
flags = SamplingFlags.SAMPLED;
} else if (input.url().startsWith("/static")) {
flags = SamplingFlags.NOT_SAMPLED;
}
return tracer.newTrace(flags);
}
```
Note: the above is the basis for the built-in https://github.com/openzipkin/sleuth/tree/master/instrumentation/http[http sampler]
=== Sampling in Spring Cloud Sleuth
Spring Cloud Sleuth by default sets all spans to non-exportable.
That means that you will see traces in logs, but not in any remote store.
For testing the default is often enough, and it probably is all you need
if you are only using the logs (e.g. with an ELK aggregator). If you are
exporting span data to Zipkin, there is also an `Sampler.ALWAYS_SAMPLE`
that exports everything and a `ProbabilityBasedSampler` that samples a
fixed fraction of spans.
NOTE: the `PercentageBasedSampler` is the default if you are using
`spring-cloud-sleuth-zipkin` or `spring-cloud-sleuth-stream`. You can
configure the exports using `spring.sleuth.sampler.percentage`. The passed
value needs to be a double from `0.0` to `1.0` so it's not a percentage.
For backwards compatibility reasons we're not changing the property name.
NOTE: The `ProbabilityBasedSampler` is the default if you are using
`spring-cloud-sleuth-zipkin`. You can
configure the exports using `spring.sleuth.sampler.probability`. The passed
value needs to be a double from `0.0` to `1.0`.
A sampler can be installed just by creating a bean definition, e.g:
@@ -54,6 +312,247 @@ TIP: You can set the HTTP header `X-B3-Flags` to `1` or when doing messaging you
set `spanFlags` header to `1`. Then the current span will be forced to be exportable
regardless of the sampling decision.
== Propagation
Propagation is needed to ensure activity originating from the same root
are collected together in the same trace. The most common propagation
approach is to copy a trace context from a client sending an RPC request
to a server receiving it.
For example, when an downstream Http call is made, its trace context is
sent along with it, encoded as request headers:
```
Client Span Server Span
┌──────────────────┐ ┌──────────────────┐
│ │ │ │
│ TraceContext │ Http Request Headers │ TraceContext │
│ ┌──────────────┐ │ ┌───────────────────┐ │ ┌──────────────┐ │
│ │ TraceId │ │ │ X─B3─TraceId │ │ │ TraceId │ │
│ │ │ │ │ │ │ │ │ │
│ │ ParentSpanId │ │ Extract │ X─B3─ParentSpanId │ Inject │ │ ParentSpanId │ │
│ │ ├─┼─────────>│ ├────────┼>│ │ │
│ │ SpanId │ │ │ X─B3─SpanId │ │ │ SpanId │ │
│ │ │ │ │ │ │ │ │ │
│ │ Sampled │ │ │ X─B3─Sampled │ │ │ Sampled │ │
│ └──────────────┘ │ └───────────────────┘ │ └──────────────┘ │
│ │ │ │
└──────────────────┘ └──────────────────┘
```
The names above are from https://github.com/openzipkin/b3-propagation[B3 Propagation],
which is built-in to Brave and has implementations in many languages and
frameworks.
Most users will use a framework interceptor which automates propagation.
Here's how they might work internally.
Here's what client-side propagation might look like
```java
// configure a function that injects a trace context into a request
injector = tracing.propagation().injector(Request.Builder::addHeader);
// before a request is sent, add the current span's context to it
injector.inject(span.context(), request);
```
Here's what server-side propagation might look like
```java
// configure a function that extracts the trace context from a request
extracted = tracing.propagation().extractor(Request::getHeader);
// when a server receives a request, it joins or starts a new trace
span = tracer.nextSpan(extracted, request);
```
=== Propagating extra fields
Sometimes you need to propagate extra fields, such as a request ID or an alternate trace context.
For example, if you are in a Cloud Foundry environment, you might want to pass the request ID:
```java
// when you initialize the builder, define the extra field you want to propagate
tracingBuilder.propagationFactory(
ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "x-vcap-request-id")
);
// later, you can tag that request ID or use it in log correlation
requestId = ExtraFieldPropagation.get("x-vcap-request-id");
```
You may also need to propagate a trace context you aren't using. For example, you may be in an
Amazon Web Services environment, but not reporting data to X-Ray. To ensure X-Ray can co-exist
correctly, pass-through its tracing header like so.
```java
tracingBuilder.propagationFactory(
ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "x-amzn-trace-id")
);
```
==== Prefixed fields
You can also prefix fields, if they follow a common pattern. For example, the following will
propagate the field "x-vcap-request-id" as-is, but send the fields "country-code" and "user-id"
on the wire as "x-baggage-country-code" and "x-baggage-user-id" respectively.
Setup your tracing instance with allowed fields:
```java
tracingBuilder.propagationFactory(
ExtraFieldPropagation.newFactoryBuilder(B3Propagation.FACTORY)
.addField("x-vcap-request-id")
.addPrefixedFields("baggage-", Arrays.asList("country-code", "user-id"))
.build()
);
```
Later, you can call below to affect the country code of the current trace context
```java
ExtraFieldPropagation.set("country-code", "FO");
String countryCode = ExtraFieldPropagation.get("country-code");
```
Or, if you have a reference to a trace context, use it explicitly
```java
ExtraFieldPropagation.set(span.context(), "country-code", "FO");
String countryCode = ExtraFieldPropagation.get(span.context(), "country-code");
```
IMPORTANT: In comparison to previous versions of Sleuth, with
Brave it's required to pass the list of baggage keys.
There are two properties to achieve this. Via the `spring.sleuth.baggage-keys` you set keys
that will get prefixed with `baggage-` for http calls and `baggage_` for messaging. You can also pass
a list of prefixed keys that will be whitelisted without any prefix via
`spring.sleuth.prefixed-keys` property.
==== Extracting a propagated context
The `TraceContext.Extractor<C>` reads trace identifiers and sampling status
from an incoming request or message. The carrier is usually a request object
or headers.
This utility is used in standard instrumentation like [HttpServerHandler](../instrumentation/http/src/main/java/sleuth/http/HttpServerHandler.java),
but can also be used for custom RPC or messaging code.
`TraceContextOrSamplingFlags` is usually only used with `Tracer.nextSpan(extracted)`, unless you are
sharing span IDs between a client and a server.
==== Sharing span IDs between client and server
A normal instrumentation pattern is creating a span representing the server
side of an RPC. `Extractor.extract` might return a complete trace context when
applied to an incoming client request. `Tracer.joinSpan` attempts to continue
the this trace, using the same span ID if supported, or creating a child span
if not. When span ID is shared, data reported includes a flag saying so.
Here's an example of B3 propagation:
```
┌───────────────────┐ ┌───────────────────┐
Incoming Headers │ TraceContext │ │ TraceContext │
┌───────────────────┐(extract)│ ┌───────────────┐ │(join)│ ┌───────────────┐ │
│ X─B3-TraceId │─────────┼─┼> TraceId │ │──────┼─┼> TraceId │ │
│ │ │ │ │ │ │ │ │ │
│ X─B3-ParentSpanId │─────────┼─┼> ParentSpanId │ │──────┼─┼> ParentSpanId │ │
│ │ │ │ │ │ │ │ │ │
│ X─B3-SpanId │─────────┼─┼> SpanId │ │──────┼─┼> SpanId │ │
└───────────────────┘ │ │ │ │ │ │ │ │
│ │ │ │ │ │ Shared: true │ │
│ └───────────────┘ │ │ └───────────────┘ │
└───────────────────┘ └───────────────────┘
```
Some propagation systems only forward the parent span ID, detected when
`Propagation.Factory.supportsJoin() == false`. In this case, a new span ID is
always provisioned and the incoming context determines the parent ID.
Here's an example of AWS propagation:
```
┌───────────────────┐ ┌───────────────────┐
x-amzn-trace-id │ TraceContext │ │ TraceContext │
┌───────────────────┐(extract)│ ┌───────────────┐ │(join)│ ┌───────────────┐ │
│ Root │─────────┼─┼> TraceId │ │──────┼─┼> TraceId │ │
│ │ │ │ │ │ │ │ │ │
│ Parent │─────────┼─┼> SpanId │ │──────┼─┼> ParentSpanId │ │
└───────────────────┘ │ └───────────────┘ │ │ │ │ │
└───────────────────┘ │ │ SpanId: New │ │
│ └───────────────┘ │
└───────────────────┘
```
Note: Some span reporters do not support sharing span IDs. For example, if you
set `Tracing.Builder.spanReporter(amazonXrayOrGoogleStackdrive)`, disable join
via `Tracing.Builder.supportsJoin(false)`. This will force a new child span on
`Tracer.joinSpan()`.
==== Implementing Propagation
`TraceContext.Extractor<C>` is implemented by a `Propagation.Factory` plugin. Internally, this code
will create the union type `TraceContextOrSamplingFlags` with one of the following:
* `TraceContext` if trace and span IDs were present.
* `TraceIdContext` if a trace ID was present, but not span IDs.
* `SamplingFlags` if no identifiers were present
Some `Propagation` implementations carry extra data from point of extraction (ex reading incoming
headers) to injection (ex writing outgoing headers). For example, it might carry a request ID. When
implementations have extra data, here's how they handle it.
* If a `TraceContext` was extracted, add the extra data as `TraceContext.extra()`
* Otherwise, add it as `TraceContextOrSamplingFlags.extra()`, which `Tracer.nextSpan` handles.
== Current Tracing Component
Brave supports a "current tracing component" concept which should only
be used when you have no other means to get a reference. This was made
for JDBC connections, as they often initialize prior to the tracing
component.
The most recent tracing component instantiated is available via
`Tracing.current()`. You there's also a shortcut to get only the tracer
via `Tracing.currentTracer()`. If you use either of these methods, do
noot cache the result. Instead, look them up each time you need them.
== Current Span
Brave supports a "current span" concept which represents the in-flight
operation. `Tracer.currentSpan()` can be used to add custom tags to a
span and `Tracer.nextSpan()` can be used to create a child of whatever
is in-flight.
=== Setting a span in scope manually
When writing new instrumentation, it is important to place a span you
created in scope as the current span. Not only does this allow users to
access it with `Tracer.currentSpan()`, but it also allows customizations
like SLF4J MDC to see the current trace IDs.
`Tracer.withSpanInScope(Span)` facilitates this and is most conveniently
employed via the try-with-resources idiom. Whenever external code might
be invoked (such as proceeding an interceptor or otherwise), place the
span in scope like this.
```java
try (SpanInScope ws = tracer.withSpanInScope(span)) {
return inboundRequest.invoke();
} finally { // note the scope is independent of the span
span.finish();
}
```
In edge cases, you may need to clear the current span temporarily. For
example, launching a task that should not be associated with the current
request. To do this, simply pass null to `withSpanInScope`.
```java
try (SpanInScope cleared = tracer.withSpanInScope(null)) {
startBackgroundThread();
}
```
== Instrumentation
Spring Cloud Sleuth instruments all your Spring application
@@ -74,29 +573,24 @@ NOTE: Remember that tags are only collected and exported if there is a
danger of accidentally collecting too much data without configuring
something).
NOTE: Currently the instrumentation in Spring Cloud Sleuth is eager - it means that
we're actively trying to pass the tracing context between threads. Also timing events
are captured even when sleuth isn't exporting data to a tracing system.
This approach may change in the future towards being lazy on this matter.
== Span lifecycle
You can do the following operations on the Span by means of *org.springframework.cloud.sleuth.Tracer* interface:
You can do the following operations on the Span by means of *brave.Tracer*:
- <<creating-and-closing-spans, start>> - when you start a span its name is assigned and start timestamp is recorded.
- <<creating-and-closing-spans, close>> - the span gets finished (the end time of the span is recorded) and if
the span is *exportable* then it will be eligible for collection to Zipkin.
The span is also removed from the current thread.
- <<creating-and-finishing-spans, start>> - when you start a span its name is assigned and start timestamp is recorded.
- <<creating-and-finishing-spans, close>> - the span gets finished (the end time of the span is recorded) and if
the span is *sampled* then it will be eligible for collection to e.g. Zipkin.
- <<continuing-spans, continue>> - a new instance of span will be created whereas it will be a copy of the
one that it continues.
- <<continuing-spans, detach>> - the span doesn't get stopped or closed. It only gets removed from the current thread.
- <<creating-spans-with-explicit-parent, create with explicit parent>> - you can create a new span and set an explicit parent to it
TIP: Spring creates the instance of `Tracer` for you. In order to use it all you need is to just autowire it.
TIP: Spring Cloud Sleuth creates the instance of `Tracer` for you. In order to use it,
all you need is to just autowire it.
=== Creating and closing spans [[creating-and-closing-spans]]
=== Creating and finishing spans [[creating-and-finishing-spans]]
You can manually create spans by using the *Tracer* interface.
You can manually create spans by using the *Tracer*.
[source,java]
----
@@ -106,7 +600,7 @@ include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/
In this example we could see how to create a new instance of span. Assuming that there already
was a span present in this thread then it would become the parent of that span.
IMPORTANT: Always clean after you create a span! Don't forget to close a span if you want to send it to Zipkin.
IMPORTANT: Always clean after you create a span! Don't forget to finish a span if you want to send it to Zipkin.
IMPORTANT: If your span contains a name greater than 50 chars, then that name will
be truncated to 50 chars. Your names have to be explicit and concrete. Big names lead to
@@ -121,39 +615,28 @@ situation might be (of course it all depends on the use-case):
- *Hystrix* - executing a Hystrix command is most likely a logical part of the current processing. It's in fact
only a technical implementation detail that you wouldn't necessarily want to reflect in tracing as a separate being.
The continued instance of span is equal to the one that it continues:
[source,java]
----
Span continuedSpan = this.tracer.continueSpan(spanToContinue);
assertThat(continuedSpan).isEqualTo(spanToContinue);
----
To continue a span you can use the *Tracer* interface.
To continue a span you can use *brave.Tracer*.
[source,java]
----
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[tags=manual_span_continuation,indent=0]
----
IMPORTANT: Always clean after you create a span! Don't forget to detach a span if some work was done started in one
thread (e.g. thread X) and it's waiting for other threads (e.g. Y, Z) to finish.
Then the spans in the threads Y, Z should be detached at the end of their work. When the results are collected
the span in thread X should be closed.
=== Creating spans with an explicit parent [[creating-spans-with-explicit-parent]]
There is a possibility that you want to start a new span and provide an explicit parent of that span.
Let's assume that the parent of a span is in one thread and you want to start a new span in another thread. The
`startSpan` method of the `Tracer` interface is the method you are looking for.
Let's assume that the parent of a span is in one thread and you want to start a new span in another thread.
In Brave, whenever you call `nextSpan()`, it's creating one in reference
to the span being currently in scope. It's enough to just put
the span in scope and then call `nextSpan()`, as presented in the example below:
[source,java]
----
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[tags=manual_span_joining,indent=0]
----
IMPORTANT: After having created such a span remember to close it. Otherwise you will see a lot of warnings in your logs
related to the fact that you have a span present in the current thread other than the one you're trying to close.
What's worse your spans won't get closed properly thus will not get collected to Zipkin.
IMPORTANT: After having created such a span remember to finish it, otherwise it will not get
reported to e.g. Zipkin
== Naming spans
@@ -344,82 +827,14 @@ if executed with a value of `15` will lead to setting of a tag with a String val
== Customizations
Thanks to the `SpanInjector` and `SpanExtractor` you can customize the way spans
are created and propagated.
There are currently two built-in ways to pass tracing information between processes:
* via Spring Integration
* via HTTP
Span ids are extracted from Zipkin-compatible (B3) headers (either `Message`
or HTTP headers), to start or join an existing trace. Trace information is
injected into any outbound requests so the next hop can extract them.
The key change in comparison to the previous versions of Sleuth is that Sleuth is implementing
the Open Tracing's `TextMap` notion. In Sleuth it's called `SpanTextMap`. Basically the idea
is that any means of communication (e.g. message, http request, etc.) can be abstracted via
a `SpanTextMap`. This abstraction defines how one can insert data into the carrier and
how to retrieve it from there. Thanks to this if you want to instrument a new HTTP library
that uses a `FooRequest` as a mean of sending HTTP requests then you have to create an
implementation of a `SpanTextMap` that delegates calls to `FooRequest` in terms of retrieval
and insertion of HTTP headers.
// TODO: Update this
=== Spring Integration
For Spring Integration there are 2 interfaces responsible for creation of a Span from a `Message`.
These are:
- `MessagingSpanTextMapExtractor`
- `MessagingSpanTextMapInjector`
You can override them by providing your own implementation.
=== HTTP
For HTTP there are 2 interfaces responsible for creation of a Span from a `Message`.
These are:
- `HttpSpanExtractor`
- `HttpSpanInjector`
You can override them by providing your own implementation.
=== Example
Let's assume that instead of the standard Zipkin compatible tracing HTTP header names
you have
* for trace id - `correlationId`
* for span id - `mySpanId`
This is a an example of a `SpanExtractor`
[source,java]
----
include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java[tags=extractor,indent=0]
----
And you could register it like this:
[source,java]
----
include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java[tags=configuration,indent=0]
----
Spring Cloud Sleuth does not add trace/span related headers to the Http Response for security reasons. If you need the headers then a custom `SpanInjector`
that injects the headers into the Http Response and a Servlet filter which makes use of this can be added the following way:
[source,java]
----
include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java[tags=injector,indent=0]
----
And you could register them like this:
[source,java]
----
include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java[tags=configuration,indent=0]
----
// TODO: Update this
=== TraceFilter
@@ -436,19 +851,6 @@ add to the Span a tag with key `custom` and a value `tag`.
include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java[tags=response_headers,indent=0]
----
=== Custom SA tag in Zipkin
Sometimes you want to create a manual Span that will wrap a call to an external service which is not instrumented.
What you can do is to create a span with the `peer.service` tag that will contain a value of the service that you want to call.
Below you can see an example of a call to Redis that is wrapped in such a span.
[source,java]
----
include::../../../..//spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/ZipkinSpanReporterTests.java[tags=service_name,indent=0]
----
IMPORTANT: Remember not to add both `peer.service` tag and the `SA` tag! You have to add only `peer.service`.
=== Custom service name
By default Sleuth assumes that when you send a span to Zipkin, you want the span's service name
@@ -467,26 +869,23 @@ spring.zipkin.service.name: foo
Before reporting spans to e.g. Zipkin you can be interested in modifying that span in some way.
You can achieve that by using the `SpanAdjuster` interface.
Example of usage:
In Sleuth we're generating spans with a fixed name. Some users want to modify the name depending on values
of tags. Implementation of the `SpanAdjuster` interface can be used to alter that name. Example:
[source,yaml]
Example. If you register two beans of `SpanAdjuster` type:
[source,java]
----
@Bean
SpanAdjuster customSpanAdjuster() {
return span -> span.toBuilder().name(scrub(span.getName())).build();
}
include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/SpanAdjusterAspectTests.java[tags=adjuster,indent=0]
----
This will lead in changing the name of the reported span just before it gets sent to Zipkin.
IMPORTANT: Your `SpanReporter` should inject the `SpanAdjuster` and
allow span manipulation before the actual reporting is done.
This will lead in changing the name of the reported span to `foo bar`, just before it gets reported (e.g. to Zipkin).
=== Host locator
IMPORTANT: This section is about defining *host* from service discovery. It's *NOT*
about finding Zipkin in service discovery.
In order to define the host that is corresponding to a particular span we need to resolve the host name
and port. The default approach is to take it from server properties. If those for some reason are not set
then we're trying to retrieve the host name from the network interfaces.
@@ -520,68 +919,15 @@ Zipkin's service id inside the URL (example for `zipkinserver` service id)
spring.zipkin.baseUrl: http://zipkinserver/
----
== Span Data as Messages
== Zipkin Stream Span Consumer
IMPORTANT: The suggested approach is to use the Zipkin's
native support for message based span sending. Starting from
Edgware Zipkin Stream server is deprecated and in Finchley
it got removed.
You can accumulate and send span data over
http://cloud.spring.io/spring-cloud-stream[Spring Cloud Stream] by
including the `spring-cloud-sleuth-stream` jar as a dependency, and
adding a Channel Binder implementation
(e.g. `spring-cloud-starter-stream-rabbit` for RabbitMQ or
`spring-cloud-starter-stream-kafka` for Kafka). This will
automatically turn your app into a producer of messages with payload
type `Spans`. The channel name to which the spans will be sent
is called `sleuth`.
=== Zipkin Consumer
Please refer to the http://cloud.spring.io/spring-cloud-static/Dalston.SR4/multi/multi__span_data_as_messages.html#_zipkin_consumer[Dalston Documentaion]
on how to create a Stream Zipkin server. That approach has been
deprecated in Edgware and removed in Finchley release.
=== Custom Consumer
A custom consumer can also easily be implemented using
`spring-cloud-sleuth-stream` and binding to the `SleuthSink`. Example:
[source,java]
----
@EnableBinding(SleuthSink.class)
@SpringBootApplication(exclude = SleuthStreamAutoConfiguration.class)
@MessageEndpoint
public class Consumer {
@ServiceActivator(inputChannel = SleuthSink.INPUT)
public void sink(Spans input) throws Exception {
// ... process spans
}
}
----
NOTE: the sample consumer application above explicitly excludes
`SleuthStreamAutoConfiguration` so it doesn't send messages to itself,
but this is optional (you might actually want to trace requests into
the consumer app).
In order to customize the polling mechanism you can create a bean of `PollerMetadata` type
with name equal to `StreamSpanReporter.POLLER`. Here you can find an example of such a configuration.
[source,java]
----
include::../../../../spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfigurationTest.java[tags=custom_poller,indent=0]
----
== Metrics
Currently Spring Cloud Sleuth registers very simple metrics related to spans.
It's using the http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-metrics.html#production-ready-recording-metrics[Spring Boot's metrics support]
to calculate the number of accepted and dropped spans. Each time a span gets
sent to Zipkin the number of accepted spans will increase. If there's an error then
the number of dropped spans will get increased.
on how to create a Stream Zipkin server.
== Integrations
@@ -672,7 +1018,6 @@ Via the `TraceWebFilter` all sampled incoming requests result in creation of a S
like to skip via the `spring.sleuth.web.skipPattern` property. If you have `ManagementServerProperties` on classpath then
its value of `contextPath` gets appended to the provided skip pattern.
=== HTTP client integration
==== Synchronous Rest Template
@@ -686,19 +1031,9 @@ If you create a `RestTemplate` instance with a `new` keyword then the instrument
==== Asynchronous Rest Template
IMPORTANT: A traced version of an `AsyncRestTemplate` bean is registered for you out of the box. If you
have your own bean you have to wrap it in a `TraceAsyncRestTemplate` representation. The best solution
is to only customize the `ClientHttpRequestFactory` and / or `AsyncClientHttpRequestFactory`.
*If you have your own `AsyncRestTemplate` and you don't wrap it your calls WILL NOT GET TRACED*.
Custom instrumentation is set to create and close Spans upon sending and receiving requests. You can customize the `ClientHttpRequestFactory`
and the `AsyncClientHttpRequestFactory` by registering your beans. Remember to use tracing compatible implementations (e.g. don't forget to
wrap `ThreadPoolTaskScheduler` in a `TraceAsyncListenableTaskExecutor`). Example of custom request factories:
[source,java]
----
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java[tags=async_template_factories,indent=0]
----
IMPORTANT: Starting with Sleuth `2.0.0` we no longer register
a bean of `AsyncRestTemplate` type. It's up to you to create such
a bean. Then we will instrument it.
To block the `AsyncRestTemplate` features set `spring.sleuth.web.async.client.enabled` to `false`.
To disable creation of the default `TraceAsyncClientHttpRequestFactoryWrapper` set `spring.sleuth.web.async.client.factory.enabled`
@@ -711,7 +1046,7 @@ can see an example of how to set up such a custom `AsyncRestTemplate`.
[source,java]
----
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/MultipleAsyncRestTemplateTests.java[tags=custom_async_rest_template,indent=0]
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/MultipleAsyncRestTemplateTests.java[tags=custom_async_rest_template,indent=0]
----
==== WebClient
@@ -800,7 +1135,7 @@ can see an example of how to set up such a custom `Executor`.
[source,java]
----
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/MultipleAsyncRestTemplateTests.java[tags=custom_executor,indent=0]
include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/MultipleAsyncRestTemplateTests.java[tags=custom_executor,indent=0]
----
=== Messaging

13
pom.xml
View File

@@ -29,7 +29,6 @@
<module>spring-cloud-sleuth-dependencies</module>
<module>spring-cloud-sleuth-core</module>
<module>spring-cloud-sleuth-zipkin</module>
<module>spring-cloud-sleuth-stream</module>
<module>spring-cloud-starter-sleuth</module>
<module>spring-cloud-starter-zipkin</module>
<module>spring-cloud-sleuth-samples</module>
@@ -253,6 +252,18 @@
<enabled>false</enabled>
</releases>
</repository>
<!-- TODO: REMOVE ME - ONLY FOR BRAVE SNAPSHOTS -->
<repository>
<id>jfrog-snapshots</id>
<name>JFrog Snapshots</name>
<url>https://oss.jfrog.org/oss-snapshot-local/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>

View File

@@ -135,6 +135,24 @@
<artifactId>commons-logging</artifactId>
<optional>true</optional>
</dependency>
<!-- BRAVE -->
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-context-log4j2</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-spring-web</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-spring-webmvc</artifactId>
</dependency>
<!-- BRAVE -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,7 +34,7 @@ import org.springframework.core.annotation.AnnotationUtils;
* @author Marcin Grzejszczak
* @since 1.0.0
*
* @see org.springframework.cloud.sleuth.SpanName
* @see SpanName
*/
public class DefaultSpanNamer implements SpanNamer {

View File

@@ -1,5 +1,7 @@
package org.springframework.cloud.sleuth;
import brave.SpanCustomizer;
/**
* Contract for hooking into process of adding error response tags.
* This interface is only called when an exception is thrown upon receiving a response.
@@ -12,11 +14,9 @@ public interface ErrorParser {
/**
* Allows setting of tags when an exception was thrown when the response was received.
* The implementation should not manipulate the {@link Span} in other way than just
* by adding the tags.
*
* @param span - current span in context
* @param error - error that was thrown upon receiving a response
*/
void parseErrorTags(Span span, Throwable error);
void parseErrorTags(SpanCustomizer span, Throwable error);
}

View File

@@ -1,9 +1,8 @@
package org.springframework.cloud.sleuth;
import brave.SpanCustomizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.sleuth.util.ExceptionUtils;
import java.lang.invoke.MethodHandles;
/**
* {@link ErrorParser} that sets the error tag for an exportable span.
@@ -13,16 +12,20 @@ import java.lang.invoke.MethodHandles;
*/
public class ExceptionMessageErrorParser implements ErrorParser {
private static final org.apache.commons.logging.Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private static final Log log = LogFactory.getLog(ExceptionMessageErrorParser.class);
@Override
public void parseErrorTags(Span span, Throwable error) {
if (span != null && span.isExportable()) {
String errorMsg = ExceptionUtils.getExceptionMessage(error);
public void parseErrorTags(SpanCustomizer span, Throwable error) {
if (span != null && error != null) {
String errorMsg = getExceptionMessage(error);
if (log.isDebugEnabled()) {
log.debug("Adding an error tag [" + errorMsg + "] to span " + span);
}
span.tag(Span.SPAN_ERROR_TAG_NAME, errorMsg);
span.tag("error", errorMsg);
}
}
private String getExceptionMessage(Throwable e) {
return e.getMessage() != null ? e.getMessage() : e.toString();
}
}

View File

@@ -1,14 +0,0 @@
package org.springframework.cloud.sleuth;
/**
* Internal API that can be changed any time so please do not use it!
*
* @author Marcin Grzejszczak
* @since 2.0.0
*/
public class InternalApi {
public static void renameSpan(Span span, String newName) {
span.name = newName;
}
}

View File

@@ -1,93 +0,0 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Represents an event in time associated with a span. Every span has zero or more Logs,
* each of which being a timestamped event name.
*
* @author Spencer Gibb
* @since 1.0.0
*/
public class Log {
/**
* The epoch timestamp of the log record; often set via {@link System#currentTimeMillis()}.
*/
private final long timestamp;
/**
* Event should be the stable name of some notable moment in the lifetime of a span.
* For instance, a span representing a browser page load might add an Event for each of the
* Performance.timing moments here: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming
*
* <p>While it is not a formal requirement, Event strings will be most useful if they are *not*
* unique; rather, tracing systems should be able to use them to understand how two similar spans
* relate from an internal timing perspective.
*/
private final String event;
@JsonCreator
public Log(
@JsonProperty(value = "timestamp", required = true) long timestamp,
@JsonProperty(value = "event", required = true) String event
) {
if (event == null) throw new NullPointerException("event");
this.timestamp = timestamp;
this.event = event;
}
public long getTimestamp() {
return this.timestamp;
}
public String getEvent() {
return this.event;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o instanceof Log) {
Log that = (Log) o;
return (this.timestamp == that.timestamp)
&& (this.event.equals(that.event));
}
return false;
}
@Override
public int hashCode() {
int h = 1;
h *= 1000003;
h ^= (this.timestamp >>> 32) ^ this.timestamp;
h *= 1000003;
h ^= this.event.hashCode();
return h;
}
@Override public String toString() {
return "Log{" +
"timestamp=" + this.timestamp +
", event='" + this.event + '\'' +
'}';
}
}

View File

@@ -1,29 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth;
/**
* Span adjuster that does nothing.
*
* @author Marcin Grzejszczak
* @since 1.1.4
*/
public class NoOpSpanAdjuster implements SpanAdjuster {
@Override public Span adjust(Span span) {
return span;
}
}

View File

@@ -1,30 +0,0 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth;
/**
* Span reporter that does nothing
*
* @author Marcin Grzejszczak
* @since 1.0.0
*/
public class NoOpSpanReporter implements SpanReporter {
@Override
public void report(Span span) {
}
}

View File

@@ -1,29 +0,0 @@
/*
* Copyright 2013-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth;
/**
* Extremely simple callback to determine the frequency that an action should be traced.
*
* @since 1.0.0
*/
public interface Sampler {
/**
* @return true if the span is not null and should be exported to the tracing system
*/
boolean isSampled(Span span);
}

View File

@@ -1,803 +0,0 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
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;
/**
* Class for gathering and reporting statistics about a block of execution.
* <p>
* Spans should form a directed acyclic graph structure. It should be possible to keep
* following the parents of a span until you arrive at a span with no parents.
* <p>
* Spans can be either annotated with tags or logs.
* <p>
* An <b>Annotation</b> is used to record existence of an event in time. Below you can
* find some of the core annotations used to define the start and stop of a request:
* <p>
* <ul>
* <li><b>cs</b> - Client Sent</li>
* <li><b>sr</b> - Server Received</li>
* <li><b>ss</b> - Server Sent</li>
* <li><b>cr</b> - Client Received</li>
* </ul>
*
* Spring Cloud Sleuth uses Zipkin compatible header names
*
* <ul>
* <li>X-B3-TraceId: 64 encoded bits</li>
* <li>X-B3-SpanId: 64 encoded bits</li>
* <li>X-B3-ParentSpanId: 64 encoded bits</li>
* <li>X-B3-Sampled: Boolean (either “1” or “0”)</li>
* </ul>
*
* @author Spencer Gibb
* @author Marcin Grzejszczak
* @since 1.0.0
*/
/*
* OpenTracing spans can affect the trace tree by creating children. In this way, they are
* like scoped tracers. Sleuth spans are DTOs, whose sole responsibility is the current
* span in the trace tree.
*/
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class Span implements SpanContext {
public static final String SAMPLED_NAME = "X-B3-Sampled";
public static final String PROCESS_ID_NAME = "X-Process-Id";
public static final String PARENT_ID_NAME = "X-B3-ParentSpanId";
public static final String TRACE_ID_NAME = "X-B3-TraceId";
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_FLAGS = "X-B3-Flags";
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));
public static final String SPAN_SAMPLED = "1";
public static final String SPAN_NOT_SAMPLED = "0";
public static final String SPAN_LOCAL_COMPONENT_TAG_NAME = "lc";
public static final String SPAN_ERROR_TAG_NAME = "error";
/**
* <b>cr</b> - Client Receive. Signifies the end of the span. The client has
* successfully received the response from the server side. If one subtracts the cs
* timestamp from this timestamp one will receive the whole time needed by the client
* to receive the response from the server.
*/
public static final String CLIENT_RECV = "cr";
/**
* <b>cs</b> - Client Sent. The client has made a request (a client can be e.g.
* {@link org.springframework.web.client.RestTemplate}. This annotation depicts the
* start of the span.
*/
// For an outbound RPC call, it should log a "cs" annotation.
// If possible, it should log a binary annotation of "sa", indicating the
// destination address.
public static final String CLIENT_SEND = "cs";
/**
* <b>sr</b> - Server Receive. The server side got the request and will start
* processing it. If one subtracts the cs timestamp from this timestamp one will
* receive the network latency.
*/
// If an inbound RPC call, it should log a "sr" annotation.
// If possible, it should log a binary annotation of "ca", indicating the
// caller's address (ex X-Forwarded-For header)
public static final String SERVER_RECV = "sr";
/**
* <b>ss</b> - Server Send. Annotated upon completion of request processing (when the
* response got sent back to the client). If one subtracts the sr timestamp from this
* timestamp one will receive the time needed by the server side to process the
* request.
*/
public static final String SERVER_SEND = "ss";
/**
* <a href="https://github.com/opentracing/opentracing-go/blob/master/ext/tags.go">As
* in Open Tracing</a>
*/
public static final String SPAN_PEER_SERVICE_TAG_NAME = "peer.service";
/**
* ID of the instance from which the span was originated.
*/
public static final String INSTANCEID = "spring.instance_id";
private final long begin;
private long end = 0;
volatile String name;
private final long traceIdHigh;
private final long traceId;
private List<Long> parents = new ArrayList<>();
private final long spanId;
private boolean remote = false;
private boolean exportable = true;
private final Map<String, String> tags;
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
private final Long startNanos;
private Long durationMicros; // serialized in json so micros precision isn't lost
/*
Using B3 propagation, it is most typical to share the same span ID across client and
the server. This has backend implications like who owns the timestamp (hint the
client does). When a SpanReporter receives a completed span, it should know if it
is shared or not.
*/
private final boolean shared;
@SuppressWarnings("unused")
private Span() {
this(-1, -1, "dummy", 0, Collections.<Long>emptyList(), 0, false, false, null);
}
/**
* Creates a new span that still tracks tags and logs of the current span. This is
* crucial when continuing spans since the changes in those collections done in the
* continued span need to be reflected until the span gets closed.
*
* @deprecated - use {@link SpanBuilder}
*/
@Deprecated
public Span(Span current, Span savedSpan) {
this.begin = current.getBegin();
this.end = current.getEnd();
this.name = current.getName();
this.traceIdHigh = current.getTraceIdHigh();
this.traceId = current.getTraceId();
this.parents = current.getParents();
this.spanId = current.getSpanId();
this.remote = current.isRemote();
this.exportable = current.isExportable();
this.processId = current.getProcessId();
this.tags = current.tags;
this.logs = current.logs;
this.startNanos = current.startNanos;
this.durationMicros = current.durationMicros;
this.baggage = current.baggage;
this.savedSpan = savedSpan;
this.shared = current.shared;
}
Span(long begin, long end, String name, long traceId, List<Long> parents,
long spanId, boolean remote, boolean exportable, String processId) {
this(begin, end, name, traceId, parents, spanId, remote, exportable, processId,
null, false);
}
Span(long begin, long end, String name, long traceId, List<Long> parents,
long spanId, boolean remote, boolean exportable, String processId,
Span savedSpan, boolean shared) {
this(new SpanBuilder()
.begin(begin)
.end(end)
.name(name)
.traceId(traceId)
.parents(parents)
.spanId(spanId)
.remote(remote)
.exportable(exportable)
.processId(processId)
.savedSpan(savedSpan)
.shared(shared));
}
Span(SpanBuilder builder) {
if (builder.begin > 0) { // conventionally, 0 indicates unset
this.startNanos = null; // don't know the start tick
this.begin = builder.begin;
} else {
this.startNanos = nanoTime();
this.begin = System.currentTimeMillis();
}
if (builder.end > 0) {
this.end = builder.end;
this.durationMicros = (this.end - this.begin) * 1000;
}
this.name = builder.name != null ? builder.name : "";
this.traceIdHigh = builder.traceIdHigh;
this.traceId = builder.traceId;
this.parents.addAll(builder.parents);
this.spanId = builder.spanId;
this.remote = builder.remote;
this.exportable = builder.exportable;
this.processId = builder.processId;
this.savedSpan = builder.savedSpan;
this.tags = new ConcurrentHashMap<>();
this.tags.putAll(builder.tags);
this.logs = new ConcurrentLinkedQueue<>();
this.logs.addAll(builder.logs);
this.baggage = new ConcurrentHashMap<>();
this.baggage.putAll(builder.baggage);
this.shared = builder.shared;
}
public static SpanBuilder builder() {
return new SpanBuilder();
}
/**
* The block has completed, stop the clock
*/
public synchronized void stop() {
if (this.durationMicros == null) {
if (this.begin == 0) {
throw new IllegalStateException(
"Span for " + this.name + " has not been started");
}
if (this.end == 0) {
this.end = System.currentTimeMillis();
}
if (this.startNanos != null) { // set a precise duration
this.durationMicros = Math.max(1, (nanoTime() - this.startNanos) / 1000);
} else {
this.durationMicros = (this.end - this.begin) * 1000;
}
}
}
/**
* Return the total amount of time elapsed since start was called, if running, or
* difference between stop and start, in microseconds.
*
* Note that in case of the spans that have CS / CR events we will not
* send to Zipkin the accumulated microseconds but will calculate the
* duration basing on the timestamps of the CS / CR events.
*
* @return zero if not running, or a positive number of microseconds.
*/
@JsonIgnore
public synchronized long getAccumulatedMicros() {
if (this.durationMicros != null) {
return this.durationMicros;
} else { // stop() hasn't yet been called
if (this.begin == 0) {
return 0;
}
if (this.startNanos != null) {
return Math.max(1, (nanoTime() - this.startNanos) / 1000);
} else {
return (System.currentTimeMillis() - this.begin) * 1000;
}
}
}
// Visible for testing
@JsonIgnore
long nanoTime() {
return System.nanoTime();
}
/**
* Has the span been started and not yet stopped?
*/
@JsonIgnore
public synchronized boolean isRunning() {
return this.begin != 0 && this.durationMicros == null;
}
/**
* Add a tag or data annotation associated with this span. The tag will be added only
* if it has a value.
*/
public void tag(String key, String value) {
if (StringUtils.hasText(value)) {
this.tags.put(key, value);
}
}
/**
* Add an {@link Log#event event} to the timeline associated with this span.
*/
public void logEvent(String event) {
logEvent(System.currentTimeMillis(), event);
}
/**
* Add a {@link Log#event event} to a specific point (a timestamp in milliseconds) in the timeline
* associated with this span.
*/
public void logEvent(long timestampMilliseconds, String event) {
this.logs.add(new Log(timestampMilliseconds, 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.toLowerCase(), 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.toLowerCase());
}
@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/>
* <p/>
* Will never be null.
*/
public Map<String, String> tags() {
return Collections.unmodifiableMap(new LinkedHashMap<>(this.tags));
}
/**
* Get any timestamped events (read only)
* <p/>
* <p/>
* Will never be null.
*/
public List<Log> logs() {
return Collections.unmodifiableList(new ArrayList<>(this.logs));
}
/**
* Returns the saved span. The one that was "current" before this span.
* <p>
* Might be null
*/
@JsonIgnore
public Span getSavedSpan() {
return this.savedSpan;
}
public boolean hasSavedSpan() {
return this.savedSpan != null;
}
/**
* A human-readable name assigned to this span instance.
* <p>
*/
public String getName() {
return this.name;
}
/**
* A pseudo-unique (random) number assigned to this span instance.
* <p>
* <p>
* The span id is immutable and cannot be changed. It is safe to access this from
* multiple threads.
*/
public long getSpanId() {
return this.spanId;
}
/**
* When non-zero, the trace containing this span uses 128-bit trace identifiers.
*
* <p>{@code traceIdHigh} corresponds to the high bits in big-endian format and
* {@link #getTraceId()} corresponds to the low bits.
*
* <p>Ex. to convert the two fields to a 128bit opaque id array, you'd use code like below.
* <pre>{@code
* ByteBuffer traceId128 = ByteBuffer.allocate(16);
* traceId128.putLong(span.getTraceIdHigh());
* traceId128.putLong(span.getTraceId());
* traceBytes = traceId128.array();
* }</pre>
*
* @see #traceIdString()
* @since 1.0.11
*/
public long getTraceIdHigh() {
return this.traceIdHigh;
}
/**
* Unique 8-byte identifier for a trace, set on all spans within it.
*
* @see #getTraceIdHigh() for notes about 128-bit trace identifiers
*/
public long getTraceId() {
return this.traceId;
}
/**
* Return a unique id for the process from which this span originated.
* <p>
* Might be null
*/
public String getProcessId() {
return this.processId;
}
/**
* Returns the parent IDs of the span.
* <p>
* <p>
* The collection will be empty if there are no parents.
*/
public List<Long> getParents() {
return this.parents;
}
/**
* Flag that tells us whether the span was started in another process. Useful in RPC
* tracing when the receiver actually has to add annotations to the senders span.
*/
public boolean isRemote() {
return this.remote;
}
/**
* Get the start time, in milliseconds
*/
public long getBegin() {
return this.begin;
}
/**
* Get the stop time, in milliseconds
*/
public long getEnd() {
return this.end;
}
/**
* Is the span eligible for export? If not then we may not need accumulate annotations
* (for instance).
*/
public boolean isExportable() {
return this.exportable;
}
/**
* Span and trace id got extracted from a carrier?
* We are adding data to the same span created by a remote client
*
* @since 1.3.0
*/
public boolean isShared() {
return this.shared;
}
/**
* Returns the 16 or 32 character hex representation of the span's trace ID
*
* @since 1.0.11
*/
public String traceIdString() {
if (this.traceIdHigh != 0) {
char[] result = new char[32];
writeHexLong(result, 0, this.traceIdHigh);
writeHexLong(result, 16, this.traceId);
return new String(result);
}
char[] result = new char[16];
writeHexLong(result, 0, this.traceId);
return new String(result);
}
/**
* Converts the span to a {@link SpanBuilder} format
*/
public SpanBuilder toBuilder() {
return builder().from(this);
}
/**
* Represents given long id as 16-character lower-hex string
*
* @see #traceIdString()
*/
public static String idToHex(long id) {
char[] data = new char[16];
writeHexLong(data, 0, id);
return new String(data);
}
/** Inspired by {@code okio.Buffer.writeLong} */
static void writeHexLong(char[] data, int pos, long v) {
writeHexByte(data, pos + 0, (byte) ((v >>> 56L) & 0xff));
writeHexByte(data, pos + 2, (byte) ((v >>> 48L) & 0xff));
writeHexByte(data, pos + 4, (byte) ((v >>> 40L) & 0xff));
writeHexByte(data, pos + 6, (byte) ((v >>> 32L) & 0xff));
writeHexByte(data, pos + 8, (byte) ((v >>> 24L) & 0xff));
writeHexByte(data, pos + 10, (byte) ((v >>> 16L) & 0xff));
writeHexByte(data, pos + 12, (byte) ((v >>> 8L) & 0xff));
writeHexByte(data, pos + 14, (byte) (v & 0xff));
}
static void writeHexByte(char[] data, int pos, byte b) {
data[pos + 0] = HEX_DIGITS[(b >> 4) & 0xf];
data[pos + 1] = HEX_DIGITS[b & 0xf];
}
static final char[] HEX_DIGITS =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/**
* Parses a 1 to 32 character lower-hex string with no prefix into an unsigned long, tossing any
* bits higher than 64.
*/
public static long hexToId(String hexString) {
Assert.hasText(hexString, "Can't convert empty hex string to long");
int length = hexString.length();
if (length < 1 || length > 32) throw new IllegalArgumentException("Malformed id: " + hexString);
// trim off any high bits
int beginIndex = length > 16 ? length - 16 : 0;
return hexToId(hexString, beginIndex);
}
/**
* Parses a 16 character lower-hex string with no prefix into an unsigned long, starting at the
* specified index.
*
* @since 1.0.11
*/
public static long hexToId(String lowerHex, int index) {
Assert.hasText(lowerHex, "Can't convert empty hex string to long");
long result = 0;
for (int endIndex = Math.min(index + 16, lowerHex.length()); index < endIndex; index++) {
char c = lowerHex.charAt(index);
result <<= 4;
if (c >= '0' && c <= '9') {
result |= c - '0';
} else if (c >= 'a' && c <= 'f') {
result |= c - 'a' + 10;
} else {
throw new IllegalArgumentException("Malformed id: " + lowerHex);
}
}
return result;
}
@Override
public String toString() {
return "[Trace: " + traceIdString() + ", Span: " + idToHex(this.spanId)
+ ", Parent: " + getParentIdIfPresent() + ", exportable:" + this.exportable + "]";
}
private String getParentIdIfPresent() {
return this.getParents().isEmpty() ? "null" : idToHex(this.getParents().get(0));
}
@Override
public int hashCode() {
int h = 1;
h *= 1000003;
h ^= (this.traceIdHigh >>> 32) ^ this.traceIdHigh;
h *= 1000003;
h ^= (this.traceId >>> 32) ^ this.traceId;
h *= 1000003;
h ^= (this.spanId >>> 32) ^ this.spanId;
h *= 1000003;
return h;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o instanceof Span) {
Span that = (Span) o;
return (this.traceIdHigh == that.traceIdHigh)
&& (this.traceId == that.traceId)
&& (this.spanId == that.spanId);
}
return false;
}
public static class SpanBuilder {
private long begin;
private long end;
private String name;
private long traceIdHigh;
private long traceId;
private final ArrayList<Long> parents = new ArrayList<>();
private long spanId;
private boolean remote;
private boolean exportable = true;
private String processId;
private Span savedSpan;
private final List<Log> logs = new ArrayList<>();
private final Map<String, String> tags = new LinkedHashMap<>();
private final Map<String, String> baggage = new LinkedHashMap<>();
private boolean shared;
SpanBuilder() {
}
/**
* Call this to record a begin time of a Span you didn't start. Don't call this when you are
* starting the span.
*
* <p>In other words, don't call {@code builder.begin(System.currentTimeMillis());}. doing so is
* redundant and will result in less precision when calculating elapsed time.
*/
public Span.SpanBuilder begin(long begin) {
this.begin = begin;
return this;
}
public Span.SpanBuilder end(long end) {
this.end = end;
return this;
}
public Span.SpanBuilder name(String name) {
this.name = name;
return this;
}
public Span.SpanBuilder traceIdHigh(long traceIdHigh) {
this.traceIdHigh = traceIdHigh;
return this;
}
public Span.SpanBuilder traceId(long traceId) {
this.traceId = traceId;
return this;
}
public Span.SpanBuilder parent(Long parent) {
this.parents.add(parent);
return this;
}
public Span.SpanBuilder parents(Collection<Long> parents) {
this.parents.clear();
this.parents.addAll(parents);
return this;
}
public Span.SpanBuilder log(Log log) {
this.logs.add(log);
return this;
}
public Span.SpanBuilder logs(Collection<Log> logs) {
this.logs.clear();
this.logs.addAll(logs);
return this;
}
public Span.SpanBuilder tag(String tagKey, String tagValue) {
this.tags.put(tagKey, tagValue);
return this;
}
public Span.SpanBuilder tags(Map<String, String> tags) {
this.tags.clear();
this.tags.putAll(tags);
return this;
}
public Span.SpanBuilder baggage(String baggageKey, String baggageValue) {
this.baggage.put(baggageKey.toLowerCase(), 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;
}
public Span.SpanBuilder remote(boolean remote) {
this.remote = remote;
return this;
}
public Span.SpanBuilder exportable(boolean exportable) {
this.exportable = exportable;
return this;
}
public Span.SpanBuilder processId(String processId) {
this.processId = processId;
return this;
}
public Span.SpanBuilder savedSpan(Span savedSpan) {
this.savedSpan = savedSpan;
return this;
}
public Span.SpanBuilder shared(boolean shared) {
this.shared = shared;
return this;
}
/**
* Creates a {@link Span.SpanBuilder} from the {@link Span}.
*/
public Span.SpanBuilder from(Span span) {
return begin(span.begin).end(span.end).name(span.name)
.traceIdHigh(span.traceIdHigh).traceId(span.traceId)
.parents(span.getParents()).logs(span.logs).tags(span.tags).baggage(span.baggage)
.spanId(span.spanId).remote(span.remote).exportable(span.exportable)
.processId(span.processId).savedSpan(span.savedSpan);
}
/**
* Builds a span. All collections lik baggage / tags / logs are *copied*, not continued.
* In other words if you add a tag to the input {@link Span}, the created span
* will not reflect that change.
*/
public Span build() {
return new Span(this);
}
@Override
public String toString() {
return new Span(this).toString();
}
}
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth;
/**
* Strategy for accessing the current span. This is the primary interface for use by user
* code (if it needs access to spans at all - in general it is better to leave span access
* to specialized and cross-cutting instrumentation code).
*
* @author Dave Syer
* @since 1.0.0
*/
public interface SpanAccessor {
/**
* Retrieves the span that is present in the context. If currently there is
* no tracing going on, then this method will return {@code null}.
*/
Span getCurrentSpan();
/**
* Returns {@code true} when a span is present in the current context. In other
* words if a span was started or continued then this method returns {@code true}.
*/
boolean isTracing();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,25 +16,29 @@
package org.springframework.cloud.sleuth;
import zipkin2.Span;
/**
* Adds ability to adjust a span before reporting it.
*
* IMPORTANT: Your {@link SpanReporter} should inject the collection of {@link SpanAdjuster} and
* allow {@link Span} manipulation before the actual reporting is done.
* <b>IMPORTANT</b> - if you override the default {@link brave.Tracing} implementation,
* remember to ensure that you pass to it an adjusted version of the {@link zipkin2.reporter.Reporter<zipkin2.Span>}
* bean. In other words you must reuse the list of available {@link SpanAdjuster}s and
* wrap the provided {@link zipkin2.reporter.Reporter} interface with it.
*
* @author Marcin Grzejszczak
* @since 1.1.4
*/
public interface SpanAdjuster {
/**
* You can adjust the {@link Span} by creating a new one using the {@link Span.SpanBuilder}
* You can adjust the {@link zipkin2.Span} by creating a new one using the {@link Span#toBuilder()}
* before reporting it.
*
* In Sleuth we're generating spans with a fixed name. Some users want to modify the name
* With the legacy Sleuth approach we're generating spans with a fixed name. Some users want to modify the name
* depending on some values of tags. Implementation of this interface can be used to alter
* then name. Example:
*
* {@code span -> span.toBuilder().name(scrub(span.getName())).build();}
*/
Span adjust(Span span);
}
}

View File

@@ -1,31 +0,0 @@
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

@@ -1,42 +0,0 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth;
/**
* Adopted from <a href=
* "https://github.com/opentracing/opentracing-java/pull/11/files#diff-eb9c3460aba76aabc0de04b05e4a2b3d">
* </a>OpenTracing</a>
*
* @author Marcin Grzejszczak
* @since 1.0.0
*/
public interface SpanExtractor<T> {
/**
* Returns a SpanBuilder provided a “carrier” object from which to extract identifying
* information needed by the new Span instance.
*
* If the carrier object has no such span stored within it, a new Span is created.
*
* Unless theres an error, it returns a Span. The Span generated from the builder can
* be used in the host process like any other.
*
* (Note that some OpenTracing implementations consider the Spans on either side of an
* RPC to have the same identity, and others consider the caller to be the parent and
* the receiver to be the child).
*/
Span joinTrace(T carrier);
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth;
/**
* Adopted from <a href=
* "https://github.com/opentracing/opentracing-java/blob/master/opentracing/src/main/java/opentracing/Tracer.java">
* </a>OpenTracing</a>
*
* @author Marcin Grzejszczak
* @since 1.0.0
*/
public interface SpanInjector<T> {
/**
* Takes two arguments:
* <ul>
* <li>a Span instance, and</li>
* <li>a “carrier” object in which to inject that Span for cross-process propagation.
* </li>
* </ul>
*
* A “carrier” object is some sort of http or rpc envelope, for example HeaderGroup
* (from Apache HttpComponents).
*
* Attempting to inject to a carrier that has been registered/configured to this
* Tracer will result in a IllegalStateException.
*/
void inject(Span span, T carrier);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@ import java.lang.annotation.Target;
/**
* Annotation to provide the name for the span. You should annotate all your
* custom {@link java.lang.Runnable Runnable} or {@link java.util.concurrent.Callable Callable} classes
* custom {@link Runnable Runnable} or {@link java.util.concurrent.Callable Callable} classes
* for the instrumentation logic to pick up how to name the span.
* <p>
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,34 +0,0 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth;
/**
* Contract for reporting Sleuth spans for collection. For example to Zipkin.
*
* IMPORTANT: Your {@link SpanReporter} should inject the {@link SpanAdjuster} and
* allow {@link Span} manipulation before the actual reporting is done.
*
* @author Marcin Grzejszczak
* @since 1.0.0
*/
public interface SpanReporter {
/**
* Reports a completed span out of band, usually out of process.
* This is typically to a trace depot (ex zipkin) or a log file.
*/
void report(Span span);
}

View File

@@ -1,31 +0,0 @@
package org.springframework.cloud.sleuth;
import java.util.Iterator;
import java.util.Map;
/**
* 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.
*
* @author Marcin Grzejszczak
* @since 1.2.0
*/
public interface SpanTextMap extends Iterable<Map.Entry<String, String>> {
/**
* Gets an iterator over arbitrary key:value pairs from the TextMapReader.
*
* @return entries in the TextMap backing store; note that for some Formats, the iterator may include entries that
* were never injected by a Tracer implementation (e.g., unrelated HTTP headers)
*/
Iterator<Map.Entry<String,String>> iterator();
/**
* Puts a key:value pair into the TextMapWriter's backing store.
*
* @param key a String, possibly with constraints dictated by the particular Format this TextMap is paired with
* @param value a String, possibly with constraints dictated by the particular Format this TextMap is paired with
*/
void put(String key, String value);
}

View File

@@ -1,100 +0,0 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth;
import java.util.concurrent.Callable;
/**
* Callable that passes Span between threads. The Span name is
* taken either from the passed value or from the {@link SpanNamer}
* interface.
*
* @author Spencer Gibb
* @author Marcin Grzejszczak
* @since 1.0.0
*/
public class TraceCallable<V> implements Callable<V> {
private final Tracer tracer;
private final SpanNamer spanNamer;
private final Callable<V> delegate;
private final String name;
private final Span parent;
public TraceCallable(Tracer tracer, SpanNamer spanNamer, Callable<V> delegate) {
this(tracer, spanNamer, delegate, null);
}
public TraceCallable(Tracer tracer, SpanNamer spanNamer, Callable<V> delegate, String name) {
this.tracer = tracer;
this.spanNamer = spanNamer;
this.delegate = delegate;
this.name = name;
this.parent = tracer.getCurrentSpan();
}
@Override
public V call() throws Exception {
Span span = startSpan();
try {
return this.getDelegate().call();
}
finally {
close(span);
}
}
protected Span startSpan() {
return this.tracer.createSpan(getSpanName(), this.parent);
}
protected String getSpanName() {
if (this.name != null) {
return this.name;
}
return this.spanNamer.name(this.delegate, "async");
}
protected void close(Span span) {
this.tracer.close(span);
}
protected Span continueSpan(Span span) {
return this.tracer.continueSpan(span);
}
protected Span detachSpan(Span span) {
return this.tracer.detach(span);
}
public Tracer getTracer() {
return this.tracer;
}
public Callable<V> getDelegate() {
return this.delegate;
}
public String getName() {
return this.name;
}
public Span getParent() {
return this.parent;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@ import java.util.LinkedHashSet;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Well-known {@link org.springframework.cloud.sleuth.Span#tag(String, String) span tag}
* Well-known {@link brave.Span#tag(String, String) span tag}
* keys.
*
* <h3>Overhead of adding Trace Data</h3>

View File

@@ -1,110 +0,0 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth;
/**
* Runnable that passes Span between threads. The Span name is
* taken either from the passed value or from the {@link SpanNamer}
* interface.
*
* @author Spencer Gibb
* @author Marcin Grzejszczak
* @since 1.0.0
*/
public class TraceRunnable implements Runnable {
/**
* Since we don't know the exact operation name we provide a default
* name for the Span
*/
private static final String DEFAULT_SPAN_NAME = "async";
private final Tracer tracer;
private final SpanNamer spanNamer;
private final Runnable delegate;
private final String name;
private final Span parent;
public TraceRunnable(Tracer tracer, SpanNamer spanNamer, Runnable delegate) {
this(tracer, spanNamer, delegate, null);
}
public TraceRunnable(Tracer tracer, SpanNamer spanNamer, Runnable delegate, String name) {
this.tracer = tracer;
this.spanNamer = spanNamer;
this.delegate = delegate;
this.name = name;
this.parent = tracer.getCurrentSpan();
}
@Override
public void run() {
Span span = startSpan();
try {
this.getDelegate().run();
}
finally {
close(span);
}
}
protected Span startSpan() {
return this.tracer.createSpan(getSpanName(), this.parent);
}
protected String getSpanName() {
if (this.name != null) {
return this.name;
}
return this.spanNamer.name(this.delegate, DEFAULT_SPAN_NAME);
}
protected void close(Span span) {
// race conditions - check #447
if (!this.tracer.isTracing()) {
this.tracer.continueSpan(span);
}
this.tracer.close(span);
}
protected Span continueSpan(Span span) {
return this.tracer.continueSpan(span);
}
protected Span detachSpan(Span span) {
if (this.tracer.isTracing()) {
return this.tracer.detach(span);
}
return span;
}
public Tracer getTracer() {
return this.tracer;
}
public Runnable getDelegate() {
return this.delegate;
}
public String getName() {
return this.name;
}
public Span getParent() {
return this.parent;
}
}

View File

@@ -1,145 +0,0 @@
/*
* Copyright 2013-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth;
import java.util.concurrent.Callable;
/**
* The Tracer class is the primary way for instrumentation code (note user code) to
* interact with the library. It provides methods to create and manipulate spans.
* <p>
*
* A 'span' represents a length of time. It has many other attributes such as a name, ID,
* and even potentially a set of key/value strings attached to it.
* <p>
*
* Each thread in your application has a single currently active currentSpan associated
* with it. When this is non-null, it represents the current operation that the thread is
* doing. spans are NOT thread-safe, and must never be used by multiple threads at once.
* With care, it is possible to safely pass a span object between threads, but in most
* cases this is not necessary.
* <p>
*
* Most crucial methods in terms of span lifecycle are:
* <ul>
* <li>The {@linkplain Tracer#createSpan(String) createSpan} method in this class
* starts a new span.</li>
* <li>The {@linkplain Tracer#createSpan(String, Span) createSpan} method creates a new span
* which has this thread's currentSpan as one of its parents</li>
* <li>The {@linkplain Tracer#continueSpan(Span) continueSpan} method creates a
* new instance of span that logically is a continuation of the provided span.</li>
* </ul>
*
* Closing a TraceScope does a few things:
* <ul>
* <li>It closes the span which the scope was managing.</li>
* <li>Set currentSpan to the previous currentSpan (which may be null).</li>
* </ul>
*
* @since 1.0.0
*/
public interface Tracer extends SpanAccessor {
/**
* Creates a new Span.
* <p/>
* If this thread has a currently active span, it will be the parent of the span we
* create here. If there is no currently active trace span, the trace scope we
* create will be empty.
*
* @param name The name field for the new span to create.
*/
Span createSpan(String name);
/**
* Creates a new Span with a specific parent. The parent might be in another
* process or thread.
* <p/>
* If this thread has a currently active trace span, it must be the 'parent' span that
* you pass in here as a parameter. The trace scope we create here will contain a new
* span which is a child of 'parent'.
*
* @param name The name field for the new span to create.
*/
Span createSpan(String name, Span parent);
/**
* Start a new span if the sampler allows it or if we are already tracing in this
* thread. A sampler can be used to limit the number of traces created.
*
* @param name the name of the span
* @param sampler a sampler to decide whether to create the span or not
*/
Span createSpan(String name, Sampler sampler);
/**
* Contributes to a span started in another thread. The returned span shares
* mutable state with the input.
*/
Span continueSpan(Span span);
/**
* Adds a tag to the current span if tracing is currently on.
* <p>
* Every span may also have zero or more key/value Tags, which do not have
* timestamps and simply annotate the spans.
*
* Check {@link TraceKeys} for examples of most common tag keys
*/
void addTag(String key, String value);
/**
* Remove this span from the current thread, but don't stop it yet nor send it for
* collection. This is useful if the span object is then passed to another thread for
* use with {@link Tracer#continueSpan(Span)}.
* <p>
* Example of usage:
* <pre>{@code
* // Span "A" was present in thread "X". Let's assume that we're in thread "Y" to which span "A" got passed
* Span continuedSpan = tracer.continueSpan(spanA);
* // Now span "A" got continued in thread "Y".
* ... // Some work is done... state of span "A" could get mutated
* Span previouslyStoredSpan = tracer.detach(continuedSpan);
* // Span "A" got removed from the thread Y but it wasn't yet sent for collection.
* // Additional work can be done on span "A" in thread "X" and finally it can get closed and sent for collection
* tracer.close(spanA);
* }</pre>
*
* @return the saved trace if there was one before the trace started (null otherwise)
*/
Span detach(Span span);
/**
* Remove this span from the current thread, stop it and send it for collection.
*
* @param span the span to close
* @return the saved span if there was one before the trace started (null otherwise)
*/
Span close(Span span);
/**
* Returns a wrapped {@link Callable} which will be recorded as a span
* in the current trace.
*/
<V> Callable<V> wrap(Callable<V> callable);
/**
* Returns a wrapped {@link Runnable} which will be recorded as a span
* in the current trace.
*/
Runnable wrap(Runnable runnable);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,13 +15,11 @@
*/
package org.springframework.cloud.sleuth.annotation;
import java.lang.invoke.MethodHandles;
import brave.Span;
import brave.Tracing;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.util.SpanNameUtil;
import org.springframework.util.StringUtils;
@@ -34,11 +32,11 @@ import org.springframework.util.StringUtils;
*/
class DefaultSpanCreator implements SpanCreator {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private static final Log log = LogFactory.getLog(DefaultSpanCreator.class);
private final Tracer tracer;
private final Tracing tracer;
DefaultSpanCreator(Tracer tracer) {
DefaultSpanCreator(Tracing tracer) {
this.tracer = tracer;
}
@@ -50,14 +48,7 @@ class DefaultSpanCreator implements SpanCreator {
log.debug("For the class [" + pjp.getThis().getClass() + "] method "
+ "[" + pjp.getMethod().getName() + "] will name the span [" + changedName + "]");
}
return createSpan(changedName);
}
private Span createSpan(String name) {
if (this.tracer.isTracing()) {
return this.tracer.createSpan(name, this.tracer.getCurrentSpan());
}
return this.tracer.createSpan(name);
return this.tracer.tracer().nextSpan().name(changedName);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,12 +16,14 @@
package org.springframework.cloud.sleuth.annotation;
import javax.annotation.PostConstruct;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.PostConstruct;
import brave.Span;
import brave.Tracer;
import brave.Tracing;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
@@ -37,8 +39,6 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.cloud.sleuth.ErrorParser;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
@@ -154,7 +154,7 @@ class SleuthAdvisorConfig extends AbstractPointcutAdvisor implements BeanFactor
return;
}
Annotation annotation = AnnotationUtils.findAnnotation(method,
AnnotationMethodsResolver.this.annotationType);
SleuthAdvisorConfig.AnnotationMethodsResolver.this.annotationType);
if (annotation != null) { found.set(true); }
}
});
@@ -168,15 +168,15 @@ class SleuthAdvisorConfig extends AbstractPointcutAdvisor implements BeanFactor
* Interceptor that creates or continues a span depending on the provided
* annotation. Also it adds logs and tags if necessary.
*/
class SleuthInterceptor implements IntroductionInterceptor, BeanFactoryAware {
class SleuthInterceptor implements IntroductionInterceptor, BeanFactoryAware {
private static final Log logger = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private static final Log logger = LogFactory.getLog(SleuthInterceptor.class);
private static final String CLASS_KEY = "class";
private static final String METHOD_KEY = "method";
private BeanFactory beanFactory;
private SpanCreator spanCreator;
private Tracer tracer;
private Tracing tracing;
private SpanTagAnnotationHandler spanTagAnnotationHandler;
private ErrorParser errorParser;
@@ -193,13 +193,13 @@ class SleuthInterceptor implements IntroductionInterceptor, BeanFactoryAware {
if (newSpan == null && continueSpan == null) {
return invocation.proceed();
}
Span span = tracer().getCurrentSpan();
Span span = tracing().tracer().currentSpan();
if (newSpan != null || span == null) {
span = spanCreator().createSpan(invocation, newSpan);
}
String log = log(continueSpan);
boolean hasLog = StringUtils.hasText(log);
try {
if (newSpan != null) {
span = spanCreator().createSpan(invocation, newSpan);
}
try (Tracer.SpanInScope ws = tracing().tracer().withSpanInScope(span)) {
if (hasLog) {
logEvent(span, log + ".before");
}
@@ -213,7 +213,7 @@ class SleuthInterceptor implements IntroductionInterceptor, BeanFactoryAware {
if (hasLog) {
logEvent(span, log + ".afterFailure");
}
errorParser().parseErrorTags(tracer().getCurrentSpan(), e);
errorParser().parseErrorTags(span, e);
throw e;
} finally {
if (span != null) {
@@ -221,15 +221,15 @@ class SleuthInterceptor implements IntroductionInterceptor, BeanFactoryAware {
logEvent(span, log + ".after");
}
if (newSpan != null) {
tracer().close(span);
span.finish();
}
}
}
}
private void addTags(MethodInvocation invocation, Span span) {
tracer().addTag(CLASS_KEY, invocation.getThis().getClass().getSimpleName());
tracer().addTag(METHOD_KEY, invocation.getMethod().getName());
span.tag(CLASS_KEY, invocation.getThis().getClass().getSimpleName());
span.tag(METHOD_KEY, invocation.getMethod().getName());
}
private void logEvent(Span span, String name) {
@@ -239,7 +239,7 @@ class SleuthInterceptor implements IntroductionInterceptor, BeanFactoryAware {
+ "the same class then the aspect will not be properly resolved");
return;
}
span.logEvent(name);
span.annotate(name);
}
private String log(ContinueSpan continueSpan) {
@@ -249,11 +249,11 @@ class SleuthInterceptor implements IntroductionInterceptor, BeanFactoryAware {
return "";
}
private Tracer tracer() {
if (this.tracer == null) {
this.tracer = this.beanFactory.getBean(Tracer.class);
private Tracing tracing() {
if (this.tracing == null) {
this.tracing = this.beanFactory.getBean(Tracing.class);
}
return this.tracer;
return this.tracing;
}
private SpanCreator spanCreator() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,12 +15,12 @@
*/
package org.springframework.cloud.sleuth.annotation;
import brave.Tracing;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -36,32 +36,28 @@ import org.springframework.context.annotation.Configuration;
* @since 1.2.0
*/
@Configuration
@ConditionalOnBean(Tracer.class)
@ConditionalOnBean(Tracing.class)
@ConditionalOnProperty(name = "spring.sleuth.annotation.enabled", matchIfMissing = true)
@AutoConfigureAfter(TraceAutoConfiguration.class)
@EnableConfigurationProperties(SleuthAnnotationProperties.class)
public class SleuthAnnotationAutoConfiguration {
@Bean
@ConditionalOnMissingBean
SpanCreator spanCreator(Tracer tracer) {
return new DefaultSpanCreator(tracer);
@ConditionalOnMissingBean SpanCreator spanCreator(Tracing tracing) {
return new DefaultSpanCreator(tracing);
}
@Bean
@ConditionalOnMissingBean
TagValueExpressionResolver spelTagValueExpressionResolver() {
@ConditionalOnMissingBean TagValueExpressionResolver spelTagValueExpressionResolver() {
return new SpelTagValueExpressionResolver();
}
@Bean
@ConditionalOnMissingBean
TagValueResolver noOpTagValueResolver() {
@ConditionalOnMissingBean TagValueResolver noOpTagValueResolver() {
return new NoOpTagValueResolver();
}
@Bean
SleuthAdvisorConfig sleuthAdvisorConfig() {
@Bean SleuthAdvisorConfig sleuthAdvisorConfig() {
return new SleuthAdvisorConfig();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.springframework.cloud.sleuth.annotation;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
@@ -35,7 +34,7 @@ import org.springframework.core.annotation.AnnotationUtils;
*/
class SleuthAnnotationUtils {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private static final Log log = LogFactory.getLog(SleuthAnnotationUtils.class);
static boolean isMethodAnnotated(Method method) {
return findAnnotation(method, NewSpan.class) != null ||

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,8 +16,8 @@
package org.springframework.cloud.sleuth.annotation;
import brave.Span;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.cloud.sleuth.Span;
/**
* A contract for creating a new span for a given join point

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,17 +16,17 @@
package org.springframework.cloud.sleuth.annotation;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import brave.Span;
import brave.Tracing;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.util.StringUtils;
/**
@@ -43,10 +43,10 @@ import org.springframework.util.StringUtils;
*/
class SpanTagAnnotationHandler {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private static final Log log = LogFactory.getLog(SpanTagAnnotationHandler.class);
private final BeanFactory beanFactory;
private Tracer tracer;
private Tracing tracing;
SpanTagAnnotationHandler(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
@@ -122,11 +122,20 @@ class SpanTagAnnotationHandler {
for (SleuthAnnotatedParameter container : toBeAdded) {
String tagValue = resolveTagValue(container.annotation, container.argument);
String tagKey = resolveTagKey(container);
tracer().addTag(tagKey, tagValue);
span().tag(tagKey, tagValue);
}
}
private String resolveTagKey(SleuthAnnotatedParameter container) {
private Span span() {
Span span = tracing().tracer().currentSpan();
if (span != null) {
return span;
}
return tracing().tracer().nextSpan();
}
private String resolveTagKey(
SleuthAnnotatedParameter container) {
return StringUtils.hasText(container.annotation.value()) ?
container.annotation.value() : container.annotation.key();
}
@@ -145,11 +154,11 @@ class SpanTagAnnotationHandler {
return argument.toString();
}
private Tracer tracer() {
if (this.tracer == null) {
this.tracer = this.beanFactory.getBean(Tracer.class);
private Tracing tracing() {
if (this.tracing == null) {
this.tracing = this.beanFactory.getBean(Tracing.class);
}
return this.tracer;
return this.tracing;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,8 +16,6 @@
package org.springframework.cloud.sleuth.annotation;
import java.lang.invoke.MethodHandles;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.expression.Expression;
@@ -32,7 +30,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
* @since 1.2.0
*/
class SpelTagValueExpressionResolver implements TagValueExpressionResolver {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private static final Log log = LogFactory.getLog(SpelTagValueExpressionResolver.class);
@Override
public String resolve(String expression, Object parameter) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,9 @@
package org.springframework.cloud.sleuth.autoconfig;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
@@ -27,10 +30,26 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
public class SleuthProperties {
private boolean enabled = true;
/** When true, generate 128-bit trace IDs instead of 64-bit ones. */
private boolean traceId128 = false;
/** When true, your tracing system allows sharing a span ID between a client and server span */
private boolean supportsJoin = true;
/**
* List of baggage key names that should be propagated out of process.
* These keys will be prefixed with `baggage` before the actual key.
* This property is set in order to be backward compatible with previous
* Sleuth versions.
*
* @see brave.propagation.ExtraFieldPropagation.FactoryBuilder#addPrefixedFields(String, java.util.Collection)
*/
private List<String> baggageKeys = new ArrayList<>();
/**
* List of fields that are referenced the same in-process as it is on the wire. For example, the
* name "x-vcap-request-id" would be set as-is including the prefix.
*
* <p>Note: {@code fieldName} will be implicitly lower-cased.
*
* @see brave.propagation.ExtraFieldPropagation.FactoryBuilder#addField(String)
*/
private List<String> propagationKeys = new ArrayList<>();
public boolean isEnabled() {
return this.enabled;
@@ -40,19 +59,19 @@ public class SleuthProperties {
this.enabled = enabled;
}
public boolean isTraceId128() {
return this.traceId128;
public List<String> getBaggageKeys() {
return this.baggageKeys;
}
public void setTraceId128(boolean traceId128) {
this.traceId128 = traceId128;
public void setBaggageKeys(List<String> baggageKeys) {
this.baggageKeys = baggageKeys;
}
public boolean isSupportsJoin() {
return this.supportsJoin;
public List<String> getPropagationKeys() {
return this.propagationKeys;
}
public void setSupportsJoin(boolean supportsJoin) {
this.supportsJoin = supportsJoin;
public void setPropagationKeys(List<String> propagationKeys) {
this.propagationKeys = propagationKeys;
}
}

View File

@@ -1,43 +1,32 @@
/*
* Copyright 2013-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.autoconfig;
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
import brave.CurrentSpanCustomizer;
import brave.Tracer;
import brave.Tracing;
import brave.context.log4j2.ThreadContextCurrentTraceContext;
import brave.propagation.B3Propagation;
import brave.propagation.CurrentTraceContext;
import brave.propagation.ExtraFieldPropagation;
import brave.propagation.Propagation;
import brave.sampler.Sampler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.ErrorParser;
import org.springframework.cloud.sleuth.ExceptionMessageErrorParser;
import org.springframework.cloud.sleuth.NoOpSpanAdjuster;
import org.springframework.cloud.sleuth.NoOpSpanReporter;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.SpanAdjuster;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.log.SpanLogger;
import org.springframework.cloud.sleuth.sampler.NeverSampler;
import org.springframework.cloud.sleuth.trace.DefaultTracer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import zipkin2.Span;
import zipkin2.reporter.Reporter;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration}
@@ -45,58 +34,101 @@ import org.springframework.context.annotation.Configuration;
*
* @author Spencer Gibb
* @author Marcin Grzejszczak
* @since 1.0.0
* @since 2.0.0
*/
@Configuration
@ConditionalOnProperty(value="spring.sleuth.enabled", matchIfMissing=true)
@EnableConfigurationProperties({TraceKeys.class, SleuthProperties.class})
@EnableConfigurationProperties({ TraceKeys.class, SleuthProperties.class })
public class TraceAutoConfiguration {
@Autowired
SleuthProperties properties;
@Autowired(required = false) List<SpanAdjuster> spanAdjusters = new ArrayList<>();
@Bean
@ConditionalOnMissingBean
public Random randomForSpanIds() {
return new Random();
Tracing sleuthTracing(@Value("${spring.zipkin.service.name:${spring.application.name:default}}") String serviceName,
Propagation.Factory factory,
CurrentTraceContext currentTraceContext,
Reporter<zipkin2.Span> reporter,
Sampler sampler) {
return Tracing.newBuilder()
.sampler(sampler)
.localServiceName(serviceName)
.propagationFactory(factory)
.currentTraceContext(currentTraceContext)
.spanReporter(adjustedReporter(reporter)).build();
}
private Reporter<zipkin2.Span> adjustedReporter(Reporter<zipkin2.Span> delegate) {
return span -> {
Span spanToAdjust = span;
for (SpanAdjuster spanAdjuster : this.spanAdjusters) {
spanToAdjust = spanAdjuster.adjust(spanToAdjust);
}
delegate.report(spanToAdjust);
};
}
@Bean
@ConditionalOnMissingBean
public Sampler defaultTraceSampler() {
return NeverSampler.INSTANCE;
}
@Bean
@ConditionalOnMissingBean(Tracer.class)
public Tracer sleuthTracer(Sampler sampler, Random random,
SpanNamer spanNamer, SpanLogger spanLogger,
SpanReporter spanReporter, TraceKeys traceKeys) {
return new DefaultTracer(sampler, random, spanNamer, spanLogger,
spanReporter, this.properties.isTraceId128(), traceKeys);
Tracer sleuthTracer(Tracing tracing) {
return tracing.tracer();
}
@Bean
@ConditionalOnMissingBean
public SpanNamer spanNamer() {
Sampler sleuthTraceSampler() {
return Sampler.NEVER_SAMPLE;
}
@Bean
@ConditionalOnMissingBean SpanNamer sleuthSpanNamer() {
return new DefaultSpanNamer();
}
@Bean
@ConditionalOnMissingBean
public SpanReporter defaultSpanReporter() {
return new NoOpSpanReporter();
Propagation.Factory sleuthPropagation(SleuthProperties sleuthProperties) {
if (sleuthProperties.getBaggageKeys().isEmpty() && sleuthProperties.getPropagationKeys().isEmpty()) {
return B3Propagation.FACTORY;
}
ExtraFieldPropagation.FactoryBuilder factoryBuilder = ExtraFieldPropagation
.newFactoryBuilder(B3Propagation.FACTORY);
if (!sleuthProperties.getBaggageKeys().isEmpty()) {
factoryBuilder = factoryBuilder
// for HTTP
.addPrefixedFields("baggage-", sleuthProperties.getBaggageKeys())
// for messaging
.addPrefixedFields("baggage_", sleuthProperties.getBaggageKeys());
}
if (!sleuthProperties.getPropagationKeys().isEmpty()) {
for (String key : sleuthProperties.getPropagationKeys()) {
factoryBuilder = factoryBuilder.addField(key);
}
}
return factoryBuilder.build();
}
@Bean
@ConditionalOnMissingBean
public SpanAdjuster defaultSpanAdjuster() {
return new NoOpSpanAdjuster();
CurrentTraceContext sleuthCurrentTraceContext() {
return ThreadContextCurrentTraceContext.create();
}
@Bean
@ConditionalOnMissingBean
public ErrorParser defaultErrorParser() {
Reporter<zipkin2.Span> noOpSpanReporter() {
return Reporter.NOOP;
}
@Bean
@ConditionalOnMissingBean
ErrorParser sleuthErrorParser() {
return new ExceptionMessageErrorParser();
}
@Bean
@ConditionalOnMissingBean
CurrentSpanCustomizer sleuthCurrentSpanCustomizer(Tracing tracing) {
return CurrentSpanCustomizer.create(tracing);
}
}

View File

@@ -33,7 +33,8 @@ import org.springframework.core.env.PropertySource;
* </ul>
*
* @author Dave Syer
* @since 1.0.0
* @author Marcin Grzejszczak
* @since 2.0.0
*/
public class TraceEnvironmentPostProcessor implements EnvironmentPostProcessor {

View File

@@ -54,7 +54,7 @@ public class AsyncCustomAutoConfiguration implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof AsyncConfigurer) {
if (bean instanceof AsyncConfigurer && !(bean instanceof LazyTraceAsyncCustomizer)) {
AsyncConfigurer configurer = (AsyncConfigurer) bean;
return new LazyTraceAsyncCustomizer(this.beanFactory, configurer);
}

View File

@@ -18,15 +18,15 @@ package org.springframework.cloud.sleuth.instrument.async;
import java.util.concurrent.Executor;
import brave.Tracer;
import brave.Tracing;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
@@ -46,8 +46,8 @@ import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
*/
@Configuration
@ConditionalOnProperty(value = "spring.sleuth.async.enabled", matchIfMissing = true)
@ConditionalOnBean(Tracer.class)
@AutoConfigureAfter(AsyncCustomAutoConfiguration.class)
@ConditionalOnBean(Tracing.class)
//@AutoConfigureAfter(AsyncCustomAutoConfiguration.class)
public class AsyncDefaultAutoConfiguration {
@Autowired private BeanFactory beanFactory;
@@ -66,8 +66,8 @@ public class AsyncDefaultAutoConfiguration {
}
@Bean
public TraceAsyncAspect traceAsyncAspect(Tracer tracer, TraceKeys traceKeys, SpanNamer spanNamer) {
return new TraceAsyncAspect(tracer, traceKeys, spanNamer);
public TraceAsyncAspect traceAsyncAspect(Tracer tracer, SpanNamer spanNamer, TraceKeys traceKeys) {
return new TraceAsyncAspect(tracer, spanNamer, traceKeys);
}
@Bean

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,7 +42,8 @@ import org.springframework.util.ReflectionUtils;
*/
class ExecutorBeanPostProcessor implements BeanPostProcessor {
private static final Log log = LogFactory.getLog(ExecutorBeanPostProcessor.class);
private static final Log log = LogFactory.getLog(
ExecutorBeanPostProcessor.class);
private final BeanFactory beanFactory;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,6 +42,9 @@ public class LazyTraceAsyncCustomizer extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor() {
if (this.delegate.getAsyncExecutor() instanceof LazyTraceExecutor) {
return this.delegate.getAsyncExecutor();
}
return new LazyTraceExecutor(this.beanFactory, this.delegate.getAsyncExecutor());
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,35 +16,33 @@
package org.springframework.cloud.sleuth.instrument.async;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.Executor;
import brave.Tracer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.ErrorParser;
import org.springframework.cloud.sleuth.ExceptionMessageErrorParser;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
/**
* {@link Executor} that wraps {@link Runnable} in a
* {@link org.springframework.cloud.sleuth.TraceRunnable TraceRunnable} that sets a
* local component tag on the span.
* {@link Executor} that wraps {@link Runnable} in a trace representation
*
* @author Dave Syer
* @since 1.0.0
*/
public class LazyTraceExecutor implements Executor {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private static final Log log = LogFactory.getLog(LazyTraceExecutor.class);
private Tracer tracer;
private final BeanFactory beanFactory;
private final Executor delegate;
private TraceKeys traceKeys;
private SpanNamer spanNamer;
private ErrorParser errorParser;
public LazyTraceExecutor(BeanFactory beanFactory, Executor delegate) {
this.beanFactory = beanFactory;
@@ -62,21 +60,7 @@ public class LazyTraceExecutor implements Executor {
return;
}
}
this.delegate.execute(new SpanContinuingTraceRunnable(this.tracer, traceKeys(), spanNamer(), command));
}
// due to some race conditions trace keys might not be ready yet
private TraceKeys traceKeys() {
if (this.traceKeys == null) {
try {
this.traceKeys = this.beanFactory.getBean(TraceKeys.class);
}
catch (NoSuchBeanDefinitionException e) {
log.warn("TraceKeys bean not found - will provide a manually created instance");
return new TraceKeys();
}
}
return this.traceKeys;
this.delegate.execute(new TraceRunnable(this.tracer, spanNamer(), errorParser(), command));
}
// due to some race conditions trace keys might not be ready yet
@@ -93,4 +77,18 @@ public class LazyTraceExecutor implements Executor {
return this.spanNamer;
}
// due to some race conditions trace keys might not be ready yet
private ErrorParser errorParser() {
if (this.errorParser == null) {
try {
this.errorParser = this.beanFactory.getBean(ErrorParser.class);
}
catch (NoSuchBeanDefinitionException e) {
log.warn("ErrorParser bean not found - will provide a manually created instance");
return new ExceptionMessageErrorParser();
}
}
return this.errorParser;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,27 +16,27 @@
package org.springframework.cloud.sleuth.instrument.async;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import brave.Tracer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.ErrorParser;
import org.springframework.cloud.sleuth.ExceptionMessageErrorParser;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.concurrent.ListenableFuture;
/**
* {@link ThreadPoolTaskExecutor} that continues a span if one was passed or creates a new one
* Trace representation of {@link ThreadPoolTaskExecutor}
*
* @author Marcin Grzejszczak
* @since 1.0.10
@@ -44,13 +44,13 @@ import org.springframework.util.concurrent.ListenableFuture;
@SuppressWarnings("serial")
public class LazyTraceThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private static final Log log = LogFactory.getLog(LazyTraceThreadPoolTaskExecutor.class);
private Tracer tracer;
private final BeanFactory beanFactory;
private final ThreadPoolTaskExecutor delegate;
private TraceKeys traceKeys;
private SpanNamer spanNamer;
private ErrorParser errorParser;
public LazyTraceThreadPoolTaskExecutor(BeanFactory beanFactory,
ThreadPoolTaskExecutor delegate) {
@@ -60,32 +60,32 @@ public class LazyTraceThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
@Override
public void execute(Runnable task) {
this.delegate.execute(new SpanContinuingTraceRunnable(tracer(), traceKeys(), spanNamer(), task));
this.delegate.execute(new TraceRunnable(tracer(), spanNamer(), errorParser(), task));
}
@Override
public void execute(Runnable task, long startTimeout) {
this.delegate.execute(new SpanContinuingTraceRunnable(tracer(), traceKeys(), spanNamer(), task), startTimeout);
this.delegate.execute(new TraceRunnable(tracer(), spanNamer(), errorParser(), task), startTimeout);
}
@Override
public Future<?> submit(Runnable task) {
return this.delegate.submit(new SpanContinuingTraceRunnable(tracer(), traceKeys(), spanNamer(), task));
return this.delegate.submit(new TraceRunnable(tracer(), spanNamer(), errorParser(), task));
}
@Override
public <T> Future<T> submit(Callable<T> task) {
return this.delegate.submit(new SpanContinuingTraceCallable<>(tracer(), traceKeys(), spanNamer(), task));
return this.delegate.submit(new TraceCallable<>(tracer(), spanNamer(), errorParser(), task));
}
@Override
public ListenableFuture<?> submitListenable(Runnable task) {
return this.delegate.submitListenable(new SpanContinuingTraceRunnable(tracer(), traceKeys(), spanNamer(), task));
return this.delegate.submitListenable(new TraceRunnable(tracer(), spanNamer(), errorParser(), task));
}
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
return this.delegate.submitListenable(new SpanContinuingTraceCallable<>(tracer(), traceKeys(), spanNamer(), task));
return this.delegate.submitListenable(new TraceCallable<>(tracer(), spanNamer(), errorParser(), task));
}
@Override public boolean prefersShortLivedTasks() {
@@ -236,19 +236,6 @@ public class LazyTraceThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
return this.tracer;
}
private TraceKeys traceKeys() {
if (this.traceKeys == null) {
try {
this.traceKeys = this.beanFactory.getBean(TraceKeys.class);
}
catch (NoSuchBeanDefinitionException e) {
log.warn("TraceKeys bean not found - will provide a manually created instance");
return new TraceKeys();
}
}
return this.traceKeys;
}
private SpanNamer spanNamer() {
if (this.spanNamer == null) {
try {
@@ -261,4 +248,17 @@ public class LazyTraceThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
}
return this.spanNamer;
}
private ErrorParser errorParser() {
if (this.errorParser == null) {
try {
this.errorParser = this.beanFactory.getBean(ErrorParser.class);
}
catch (NoSuchBeanDefinitionException e) {
log.warn("ErrorParser bean not found - will provide a manually created instance");
return new ExceptionMessageErrorParser();
}
}
return this.errorParser;
}
}

View File

@@ -1,70 +0,0 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.async;
import java.util.concurrent.Callable;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.TraceCallable;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.TraceKeys;
/**
* Callable that starts a span that is a local component span.
*
* @author Marcin Grzejszczak
* @since 1.0.0
*/
public class LocalComponentTraceCallable<V> extends TraceCallable<V> {
protected static final String ASYNC_COMPONENT = "async";
private final TraceKeys traceKeys;
public LocalComponentTraceCallable(Tracer tracer, TraceKeys traceKeys,
SpanNamer spanNamer, Callable<V> delegate) {
super(tracer, spanNamer, delegate);
this.traceKeys = traceKeys;
}
public LocalComponentTraceCallable(Tracer tracer, TraceKeys traceKeys,
SpanNamer spanNamer, String name, Callable<V> delegate) {
super(tracer, spanNamer, delegate, name);
this.traceKeys = traceKeys;
}
@Override
public V call() throws Exception {
Span span = startSpan();
try {
return this.getDelegate().call();
}
finally {
close(span);
}
}
@Override
protected Span startSpan() {
Span span = getTracer().createSpan(getSpanName(), getParent());
getTracer().addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, ASYNC_COMPONENT);
getTracer().addTag(this.traceKeys.getAsync().getPrefix() +
this.traceKeys.getAsync().getThreadNameKey(), Thread.currentThread().getName());
return span;
}
}

View File

@@ -1,68 +0,0 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.async;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.TraceRunnable;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.TraceKeys;
/**
* Runnable that starts a span that is a local component span.
*
* @author Marcin Grzejszczak
* @since 1.0.0
*/
public class LocalComponentTraceRunnable extends TraceRunnable {
protected static final String ASYNC_COMPONENT = "async";
private final TraceKeys traceKeys;
public LocalComponentTraceRunnable(Tracer tracer, TraceKeys traceKeys,
SpanNamer spanNamer, Runnable delegate) {
super(tracer, spanNamer, delegate);
this.traceKeys = traceKeys;
}
public LocalComponentTraceRunnable(Tracer tracer, TraceKeys traceKeys,
SpanNamer spanNamer, Runnable delegate, String name) {
super(tracer, spanNamer, delegate, name);
this.traceKeys = traceKeys;
}
@Override
public void run() {
Span span = startSpan();
try {
this.getDelegate().run();
}
finally {
close(span);
}
}
@Override
protected Span startSpan() {
Span span = getTracer().createSpan(getSpanName(), getParent());
getTracer().addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, ASYNC_COMPONENT);
getTracer().addTag(this.traceKeys.getAsync().getPrefix() +
this.traceKeys.getAsync().getThreadNameKey(), Thread.currentThread().getName());
return span;
}
}

View File

@@ -1,77 +0,0 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.async;
import java.util.concurrent.Callable;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.TraceCallable;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
/**
* Runnable that continues a span if there is one and creates new that is a
* local component span if there was no tracing present.
*
* @author Marcin Grzejszczak
* @since 1.0.10
*/
public class SpanContinuingTraceCallable<V> extends TraceCallable<V> {
private final LocalComponentTraceCallable<V> traceCallable;
public SpanContinuingTraceCallable(Tracer tracer, TraceKeys traceKeys,
SpanNamer spanNamer, Callable<V> delegate) {
super(tracer, spanNamer, delegate);
this.traceCallable = new LocalComponentTraceCallable<>(tracer, traceKeys, spanNamer, delegate);
}
public SpanContinuingTraceCallable(Tracer tracer, TraceKeys traceKeys,
SpanNamer spanNamer, String name, Callable<V> delegate) {
super(tracer, spanNamer, delegate, name);
this.traceCallable = new LocalComponentTraceCallable<>(tracer, traceKeys, spanNamer, name, delegate);
}
@Override
public V call() throws Exception {
Span span = startSpan();
try {
return this.getDelegate().call();
}
finally {
close(span);
}
}
@Override
protected Span startSpan() {
Span span = this.getParent();
if (span == null) {
return this.traceCallable.startSpan();
}
return continueSpan(span);
}
@Override protected void close(Span span) {
if (this.getParent() == null) {
super.close(span);
} else {
super.detachSpan(span);
}
}
}

View File

@@ -1,75 +0,0 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.async;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.TraceRunnable;
import org.springframework.cloud.sleuth.Tracer;
/**
* Runnable that continues a span if there is one and creates new that is a
* local component span if there was no tracing present.
*
* @author Marcin Grzejszczak
* @since 1.0.10
*/
public class SpanContinuingTraceRunnable extends TraceRunnable {
private final LocalComponentTraceRunnable traceRunnable;
public SpanContinuingTraceRunnable(Tracer tracer, TraceKeys traceKeys,
SpanNamer spanNamer, Runnable delegate) {
super(tracer, spanNamer, delegate);
this.traceRunnable = new LocalComponentTraceRunnable(tracer, traceKeys, spanNamer, delegate);
}
public SpanContinuingTraceRunnable(Tracer tracer, TraceKeys traceKeys,
SpanNamer spanNamer, Runnable delegate, String name) {
super(tracer, spanNamer, delegate, name);
this.traceRunnable = new LocalComponentTraceRunnable(tracer, traceKeys, spanNamer, delegate, name);
}
@Override
public void run() {
Span span = startSpan();
try {
this.getDelegate().run();
}
finally {
close(span);
}
}
@Override
protected Span startSpan() {
Span span = this.getParent();
if (span == null) {
return this.traceRunnable.startSpan();
}
return continueSpan(span);
}
@Override protected void close(Span span) {
if (this.getParent() == null) {
super.close(span);
} else {
super.detachSpan(span);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,16 +18,14 @@ package org.springframework.cloud.sleuth.instrument.async;
import java.lang.reflect.Method;
import brave.Span;
import brave.Tracer;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.InternalApi;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.util.SpanNameUtil;
import org.springframework.util.ReflectionUtils;
@@ -43,61 +41,32 @@ import org.springframework.util.ReflectionUtils;
@Aspect
public class TraceAsyncAspect {
private static final String ASYNC_COMPONENT = "async";
private final Tracer tracer;
private final SpanNamer spanNamer;
private final TraceKeys traceKeys;
private final BeanFactory beanFactory;
private SpanNamer spanNamer;
@Deprecated
public TraceAsyncAspect(Tracer tracer, TraceKeys traceKeys, BeanFactory beanFactory) {
public TraceAsyncAspect(Tracer tracer, SpanNamer spanNamer, TraceKeys traceKeys) {
this.tracer = tracer;
this.traceKeys = traceKeys;
this.beanFactory = beanFactory;
}
public TraceAsyncAspect(Tracer tracer, TraceKeys traceKeys, SpanNamer spanNamer) {
this.tracer = tracer;
this.traceKeys = traceKeys;
this.spanNamer = spanNamer;
this.beanFactory = null;
this.traceKeys = traceKeys;
}
@Around("execution (@org.springframework.scheduling.annotation.Async * *.*(..))")
public Object traceBackgroundThread(final ProceedingJoinPoint pjp) throws Throwable {
String spanName = spanNamer().name(getMethod(pjp, pjp.getTarget()),
String spanName = this.spanNamer.name(getMethod(pjp, pjp.getTarget()),
SpanNameUtil.toLowerHyphen(pjp.getSignature().getName()));
Span span = span(spanName);
renameAsyncSpan(spanName, span);
this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, ASYNC_COMPONENT);
this.tracer.addTag(this.traceKeys.getAsync().getPrefix() +
this.traceKeys.getAsync().getClassNameKey(), pjp.getTarget().getClass().getSimpleName());
this.tracer.addTag(this.traceKeys.getAsync().getPrefix() +
this.traceKeys.getAsync().getMethodNameKey(), pjp.getSignature().getName());
try {
Span span = this.tracer.currentSpan().name(spanName);
try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) {
span.tag(this.traceKeys.getAsync().getPrefix() +
this.traceKeys.getAsync().getClassNameKey(), pjp.getTarget().getClass().getSimpleName());
span.tag(this.traceKeys.getAsync().getPrefix() +
this.traceKeys.getAsync().getMethodNameKey(), pjp.getSignature().getName());
return pjp.proceed();
} finally {
this.tracer.close(span);
span.finish();
}
}
private void renameAsyncSpan(String spanName, Span span) {
// if there's a tag "lc" -> "async", that means the span came from
// a LazyTraceExecutor component that creates a span that contains very few
// information. If that's the case we want to rename it to have a different name
if (ASYNC_COMPONENT.equals(span.tags().get(Span.SPAN_LOCAL_COMPONENT_TAG_NAME))) {
InternalApi.renameSpan(span, spanName);
}
}
private Span span(String spanName) {
if (this.tracer.isTracing()) {
return this.tracer.getCurrentSpan();
}
return this.tracer.createSpan(spanName);
}
private Method getMethod(ProceedingJoinPoint pjp, Object object) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
@@ -105,11 +74,4 @@ public class TraceAsyncAspect {
.findMethod(object.getClass(), method.getName(), method.getParameterTypes());
}
SpanNamer spanNamer() {
if (this.spanNamer == null && this.beanFactory != null) {
this.spanNamer = this.beanFactory.getBean(SpanNamer.class);
}
return this.spanNamer;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,12 @@
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.web.client;
package org.springframework.cloud.sleuth.instrument.async;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import org.springframework.cloud.sleuth.Tracer;
import brave.Tracing;
import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.util.concurrent.ListenableFuture;
@@ -29,48 +29,48 @@ import org.springframework.util.concurrent.ListenableFuture;
*
* @since 1.0.0
*
* @see Tracer#wrap(Runnable)
* @see Tracer#wrap(Callable)
* @see brave.propagation.CurrentTraceContext#wrap(Runnable)
* @see brave.propagation.CurrentTraceContext#wrap(Callable)
*/
public class TraceAsyncListenableTaskExecutor implements AsyncListenableTaskExecutor {
private final AsyncListenableTaskExecutor delegate;
private final Tracer tracer;
private final Tracing tracing;
TraceAsyncListenableTaskExecutor(AsyncListenableTaskExecutor delegate,
Tracer tracer) {
Tracing tracing) {
this.delegate = delegate;
this.tracer = tracer;
this.tracing = tracing;
}
@Override
public ListenableFuture<?> submitListenable(Runnable task) {
return this.delegate.submitListenable(this.tracer.wrap(task));
return this.delegate.submitListenable(this.tracing.currentTraceContext().wrap(task));
}
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
return this.delegate.submitListenable(this.tracer.wrap(task));
return this.delegate.submitListenable(this.tracing.currentTraceContext().wrap(task));
}
@Override
public void execute(Runnable task, long startTimeout) {
this.delegate.execute(this.tracer.wrap(task), startTimeout);
this.delegate.execute(this.tracing.currentTraceContext().wrap(task), startTimeout);
}
@Override
public Future<?> submit(Runnable task) {
return this.delegate.submit(this.tracer.wrap(task));
return this.delegate.submit(this.tracing.currentTraceContext().wrap(task));
}
@Override
public <T> Future<T> submit(Callable<T> task) {
return this.delegate.submit(this.tracer.wrap(task));
return this.delegate.submit(this.tracing.currentTraceContext().wrap(task));
}
@Override
public void execute(Runnable task) {
this.delegate.execute(this.tracer.wrap(task));
this.delegate.execute(this.tracing.currentTraceContext().wrap(task));
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.async;
import java.util.concurrent.Callable;
import brave.Span;
import brave.Tracer;
import org.springframework.cloud.sleuth.ErrorParser;
import org.springframework.cloud.sleuth.SpanNamer;
/**
* Callable that passes Span between threads. The Span name is
* taken either from the passed value or from the {@link SpanNamer}
* interface.
*
* @author Spencer Gibb
* @author Marcin Grzejszczak
* @since 1.0.0
*/
public class TraceCallable<V> implements Callable<V> {
/**
* Since we don't know the exact operation name we provide a default
* name for the Span
*/
private static final String DEFAULT_SPAN_NAME = "async";
private final Tracer tracer;
private final Callable<V> delegate;
private final Span span;
private final ErrorParser errorParser;
public TraceCallable(Tracer tracer, SpanNamer spanNamer, ErrorParser errorParser, Callable<V> delegate) {
this(tracer, spanNamer, errorParser, delegate, null);
}
public TraceCallable(Tracer tracer, SpanNamer spanNamer, ErrorParser errorParser, Callable<V> delegate, String name) {
this.tracer = tracer;
this.delegate = delegate;
String spanName = name != null ? name : spanNamer.name(delegate, DEFAULT_SPAN_NAME);
this.span = this.tracer.nextSpan().name(spanName);
this.errorParser = errorParser;
}
@Override public V call() throws Exception {
Throwable error = null;
try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(this.span.start())) {
return this.delegate.call();
} catch (Exception | Error e) {
error = e;
throw e;
} finally {
this.errorParser.parseErrorTags(this.span, error);
this.span.finish();
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.async;
import brave.Span;
import brave.Tracer;
import brave.Tracer.SpanInScope;
import org.springframework.cloud.sleuth.ErrorParser;
import org.springframework.cloud.sleuth.SpanNamer;
/**
* Runnable that passes Span between threads. The Span name is
* taken either from the passed value or from the {@link SpanNamer}
* interface.
*
* @author Spencer Gibb
* @author Marcin Grzejszczak
* @since 1.0.0
*/
public class TraceRunnable implements Runnable {
/**
* Since we don't know the exact operation name we provide a default
* name for the Span
*/
private static final String DEFAULT_SPAN_NAME = "async";
private final Tracer tracer;
private final Runnable delegate;
private final Span span;
private final ErrorParser errorParser;
public TraceRunnable(Tracer tracer, SpanNamer spanNamer, ErrorParser errorParser, Runnable delegate) {
this(tracer, spanNamer, errorParser, delegate, null);
}
public TraceRunnable(Tracer tracer, SpanNamer spanNamer, ErrorParser errorParser, Runnable delegate, String name) {
this.tracer = tracer;
this.delegate = delegate;
String spanName = name != null ? name : spanNamer.name(delegate, DEFAULT_SPAN_NAME);
this.span = this.tracer.nextSpan().name(spanName);
this.errorParser = errorParser;
}
@Override
public void run() {
Throwable error = null;
try (SpanInScope ws = this.tracer.withSpanInScope(this.span.start())) {
this.delegate.run();
} catch (RuntimeException | Error e) {
error = e;
throw e;
} finally {
this.errorParser.parseErrorTags(this.span, error);
this.span.finish();
}
}
}

View File

@@ -25,10 +25,10 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import brave.Tracer;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.ErrorParser;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
/**
* A decorator class for {@link ExecutorService} to support tracing in Executors
@@ -40,34 +40,23 @@ public class TraceableExecutorService implements ExecutorService {
final ExecutorService delegate;
Tracer tracer;
private final String spanName;
TraceKeys traceKeys;
SpanNamer spanNamer;
BeanFactory beanFactory;
public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer,
TraceKeys traceKeys, SpanNamer spanNamer) {
this(delegate, tracer, traceKeys, spanNamer, null);
}
ErrorParser errorParser;
public TraceableExecutorService(BeanFactory beanFactory, final ExecutorService delegate) {
this.delegate = delegate;
this.beanFactory = beanFactory;
this.spanName = null;
this(beanFactory, delegate, null);
}
public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer,
TraceKeys traceKeys, SpanNamer spanNamer, String spanName) {
public TraceableExecutorService(BeanFactory beanFactory, final ExecutorService delegate, String spanName) {
this.delegate = delegate;
this.tracer = tracer;
this.beanFactory = beanFactory;
this.spanName = spanName;
this.traceKeys = traceKeys;
this.spanNamer = spanNamer;
}
@Override
public void execute(Runnable command) {
final Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(),
spanNamer(), command, this.spanName);
final Runnable r = new TraceRunnable(tracer(), spanNamer(), errorParser(), command, this.spanName);
this.delegate.execute(r);
}
@@ -98,22 +87,19 @@ public class TraceableExecutorService implements ExecutorService {
@Override
public <T> Future<T> submit(Callable<T> task) {
Callable<T> c = new SpanContinuingTraceCallable<>(tracer(), traceKeys(),
spanNamer(), this.spanName, task);
Callable<T> c = new TraceCallable<>(tracer(), spanNamer(), errorParser(), task, this.spanName);
return this.delegate.submit(c);
}
@Override
public <T> Future<T> submit(Runnable task, T result) {
Runnable r = new SpanContinuingTraceRunnable(tracer(), traceKeys(),
spanNamer(), task, this.spanName);
Runnable r = new TraceRunnable(tracer(), spanNamer(), errorParser(), task, this.spanName);
return this.delegate.submit(r, result);
}
@Override
public Future<?> submit(Runnable task) {
Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(),
spanNamer(), task, this.spanName);
Runnable r = new TraceRunnable(tracer(), spanNamer(), errorParser(), task, this.spanName);
return this.delegate.submit(r);
}
@@ -142,9 +128,8 @@ public class TraceableExecutorService implements ExecutorService {
private <T> Collection<? extends Callable<T>> wrapCallableCollection(Collection<? extends Callable<T>> tasks) {
List<Callable<T>> ts = new ArrayList<>();
for (Callable<T> task : tasks) {
if (!(task instanceof SpanContinuingTraceCallable)) {
ts.add(new SpanContinuingTraceCallable<>(tracer(), traceKeys(),
spanNamer(), this.spanName, task));
if (!(task instanceof TraceCallable)) {
ts.add(new TraceCallable<>(tracer(), spanNamer(), errorParser(), task, this.spanName));
}
}
return ts;
@@ -157,18 +142,17 @@ public class TraceableExecutorService implements ExecutorService {
return this.tracer;
}
TraceKeys traceKeys() {
if (this.traceKeys == null && this.beanFactory != null) {
this.traceKeys = this.beanFactory.getBean(TraceKeys.class);
}
return this.traceKeys;
}
SpanNamer spanNamer() {
if (this.spanNamer == null && this.beanFactory != null) {
this.spanNamer = this.beanFactory.getBean(SpanNamer.class);
}
return this.spanNamer;
}
ErrorParser errorParser() {
if (this.errorParser == null) {
this.errorParser = this.beanFactory.getBean(ErrorParser.class);
}
return this.errorParser;
}
}

View File

@@ -17,13 +17,12 @@
package org.springframework.cloud.sleuth.instrument.async;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.beans.factory.BeanFactory;
/**
* A decorator class for {@link ScheduledExecutorService} to support tracing in Executors
@@ -33,9 +32,8 @@ import org.springframework.cloud.sleuth.TraceKeys;
*/
public class TraceableScheduledExecutorService extends TraceableExecutorService implements ScheduledExecutorService {
public TraceableScheduledExecutorService(ScheduledExecutorService delegate,
Tracer tracer, TraceKeys traceKeys, SpanNamer spanNamer) {
super(delegate, tracer, traceKeys, spanNamer);
public TraceableScheduledExecutorService(BeanFactory beanFactory, final ExecutorService delegate) {
super(beanFactory, delegate);
}
private ScheduledExecutorService getScheduledExecutorService() {
@@ -44,25 +42,25 @@ public class TraceableScheduledExecutorService extends TraceableExecutorService
@Override
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
Runnable r = new SpanContinuingTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command);
Runnable r = new TraceRunnable(tracer(), spanNamer(), errorParser(), command);
return getScheduledExecutorService().schedule(r, delay, unit);
}
@Override
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
Callable<V> c = new SpanContinuingTraceCallable<>(this.tracer, this.traceKeys, this.spanNamer, callable);
Callable<V> c = new TraceCallable<>(tracer(), spanNamer(), errorParser(), callable);
return getScheduledExecutorService().schedule(c, delay, unit);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
Runnable r = new SpanContinuingTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command);
Runnable r = new TraceRunnable(tracer(), spanNamer(), errorParser(), command);
return getScheduledExecutorService().scheduleAtFixedRate(r, initialDelay, period, unit);
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
Runnable r = new SpanContinuingTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command);
Runnable r = new TraceRunnable(tracer(), spanNamer(), errorParser(), command);
return getScheduledExecutorService().scheduleWithFixedDelay(r, initialDelay, delay, unit);
}

View File

@@ -1,11 +1,13 @@
package org.springframework.cloud.sleuth.instrument.hystrix;
import brave.Tracer;
import brave.Tracing;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.ErrorParser;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -24,13 +26,14 @@ import com.netflix.hystrix.HystrixCommand;
@Configuration
@AutoConfigureAfter(TraceAutoConfiguration.class)
@ConditionalOnClass(HystrixCommand.class)
@ConditionalOnBean(Tracer.class)
@ConditionalOnBean(Tracing.class)
@ConditionalOnProperty(value = "spring.sleuth.hystrix.strategy.enabled", matchIfMissing = true)
public class SleuthHystrixAutoConfiguration {
@Bean
SleuthHystrixConcurrencyStrategy sleuthHystrixConcurrencyStrategy(Tracer tracer, TraceKeys traceKeys) {
return new SleuthHystrixConcurrencyStrategy(tracer, traceKeys);
@Bean SleuthHystrixConcurrencyStrategy sleuthHystrixConcurrencyStrategy(Tracer tracer,
SpanNamer spanNamer, ErrorParser errorParser) {
return new SleuthHystrixConcurrencyStrategy(tracer, spanNamer,
errorParser);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,12 +16,12 @@
package org.springframework.cloud.sleuth.instrument.hystrix;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import brave.Tracer;
import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import com.netflix.hystrix.strategy.HystrixPlugins;
@@ -35,9 +35,9 @@ import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
import com.netflix.hystrix.strategy.properties.HystrixProperty;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.ErrorParser;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.instrument.async.TraceCallable;
/**
* A {@link HystrixConcurrencyStrategy} that wraps a {@link Callable} in a
@@ -54,12 +54,15 @@ public class SleuthHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy
.getLog(SleuthHystrixConcurrencyStrategy.class);
private final Tracer tracer;
private final TraceKeys traceKeys;
private final SpanNamer spanNamer;
private final ErrorParser errorParser;
private HystrixConcurrencyStrategy delegate;
public SleuthHystrixConcurrencyStrategy(Tracer tracer, TraceKeys traceKeys) {
public SleuthHystrixConcurrencyStrategy(Tracer tracer,
SpanNamer spanNamer, ErrorParser errorParser) {
this.tracer = tracer;
this.traceKeys = traceKeys;
this.spanNamer = spanNamer;
this.errorParser = errorParser;
try {
this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
if (this.delegate instanceof SleuthHystrixConcurrencyStrategy) {
@@ -103,15 +106,16 @@ public class SleuthHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy
@Override
public <T> Callable<T> wrapCallable(Callable<T> callable) {
if (callable instanceof HystrixTraceCallable) {
if (callable instanceof TraceCallable) {
return callable;
}
Callable<T> wrappedCallable = this.delegate != null
? this.delegate.wrapCallable(callable) : callable;
if (wrappedCallable instanceof HystrixTraceCallable) {
if (wrappedCallable instanceof TraceCallable) {
return wrappedCallable;
}
return new HystrixTraceCallable<>(this.tracer, this.traceKeys, wrappedCallable);
return new TraceCallable<>(this.tracer, this.spanNamer,
this.errorParser, wrappedCallable, HYSTRIX_COMPONENT);
}
@Override
@@ -140,68 +144,4 @@ public class SleuthHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy
HystrixRequestVariableLifecycle<T> rv) {
return this.delegate.getRequestVariable(rv);
}
// Visible for testing
static class HystrixTraceCallable<S> implements Callable<S> {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private final Tracer tracer;
private final TraceKeys traceKeys;
private final Callable<S> callable;
private final Span parent;
public HystrixTraceCallable(Tracer tracer, TraceKeys traceKeys,
Callable<S> callable) {
this.tracer = tracer;
this.traceKeys = traceKeys;
this.callable = callable;
this.parent = tracer.getCurrentSpan();
}
@Override
public S call() throws Exception {
Span span = this.parent;
boolean created = false;
if (span != null) {
span = this.tracer.continueSpan(span);
if (log.isDebugEnabled()) {
log.debug("Continuing span " + span);
}
}
else {
span = this.tracer.createSpan(HYSTRIX_COMPONENT);
created = true;
if (log.isDebugEnabled()) {
log.debug("Creating new span " + span);
}
}
if (!span.tags().containsKey(Span.SPAN_LOCAL_COMPONENT_TAG_NAME)) {
this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, HYSTRIX_COMPONENT);
}
String asyncKey = this.traceKeys.getAsync().getPrefix()
+ this.traceKeys.getAsync().getThreadNameKey();
if (!span.tags().containsKey(asyncKey)) {
this.tracer.addTag(asyncKey, Thread.currentThread().getName());
}
try {
return this.callable.call();
}
finally {
if (created) {
if (log.isDebugEnabled()) {
log.debug("Closing span since it was created" + span);
}
this.tracer.close(span);
}
else if(this.tracer.isTracing()) {
if (log.isDebugEnabled()) {
log.debug("Detaching span since it was continued " + span);
}
this.tracer.detach(span);
}
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,10 @@
package org.springframework.cloud.sleuth.instrument.hystrix;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.TraceKeys;
import brave.Span;
import brave.Tracer;
import com.netflix.hystrix.HystrixCommand;
import org.springframework.cloud.sleuth.TraceKeys;
/**
* Abstraction over {@code HystrixCommand} that wraps command execution with Trace setting
@@ -29,57 +28,38 @@ import com.netflix.hystrix.HystrixCommand;
* @see Tracer
*
* @author Tomasz Nurkiewicz, 4financeIT
* @author Marcin Grzejszczak, 4financeIT
* @author Marcin Grzejszczak
* @author Spencer Gibb
* @since 1.0.0
*/
public abstract class TraceCommand<R> extends HystrixCommand<R> {
private static final String HYSTRIX_COMPONENT = "hystrix";
private final Tracer tracer;
private final TraceKeys traceKeys;
private final Span parentSpan;
private final Span span;
protected TraceCommand(Tracer tracer, TraceKeys traceKeys, Setter setter) {
super(setter);
this.tracer = tracer;
this.traceKeys = traceKeys;
this.parentSpan = tracer.getCurrentSpan();
this.span = this.tracer.nextSpan();
}
@Override
protected R run() throws Exception {
String commandKeyName = getCommandKey().name();
Span span = startSpan(commandKeyName);
this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, HYSTRIX_COMPONENT);
this.tracer.addTag(this.traceKeys.getHystrix().getPrefix() +
Span span = this.span.name(commandKeyName);
span.tag(this.traceKeys.getHystrix().getPrefix() +
this.traceKeys.getHystrix().getCommandKey(), commandKeyName);
this.tracer.addTag(this.traceKeys.getHystrix().getPrefix() +
span.tag(this.traceKeys.getHystrix().getPrefix() +
this.traceKeys.getHystrix().getCommandGroup(), getCommandGroup().name());
this.tracer.addTag(this.traceKeys.getHystrix().getPrefix() +
span.tag(this.traceKeys.getHystrix().getPrefix() +
this.traceKeys.getHystrix().getThreadPoolKey(), getThreadPoolKey().name());
try {
try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) {
return doRun();
}
finally {
close(span);
}
}
private Span startSpan(String commandKeyName) {
Span span = this.parentSpan;
if (span == null) {
return this.tracer.createSpan(commandKeyName, this.parentSpan);
}
return this.tracer.continueSpan(span);
}
private void close(Span span) {
if (this.parentSpan == null) {
this.tracer.close(span);
} else {
this.tracer.detach(span);
span.finish();
}
}

View File

@@ -1,122 +0,0 @@
package org.springframework.cloud.sleuth.instrument.messaging;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.ErrorParser;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanTextMap;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.util.SpanNameUtil;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.messaging.support.ExecutorChannelInterceptor;
import org.springframework.util.ClassUtils;
import java.lang.invoke.MethodHandles;
/**
* Abstraction over classes related to channel intercepting
*
* @author Marcin Grzejszczak
*/
abstract class AbstractTraceChannelInterceptor extends ChannelInterceptorAdapter
implements ExecutorChannelInterceptor {
private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
/**
* If a span comes from messaging components then it will have this value as a prefix
* to its name.
* <p>
* Example of a Span name: {@code message:foo}
* <p>
* Where {@code message} is the prefix and {@code foo} is the channel name
*/
protected static final String MESSAGE_COMPONENT = "message";
private Tracer tracer;
private TraceKeys traceKeys;
private MessagingSpanTextMapExtractor spanExtractor;
private MessagingSpanTextMapInjector spanInjector;
private ErrorParser errorParser;
private final BeanFactory beanFactory;
protected AbstractTraceChannelInterceptor(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
protected Tracer getTracer() {
if (this.tracer == null) {
this.tracer = this.beanFactory.getBean(Tracer.class);
}
return this.tracer;
}
protected TraceKeys getTraceKeys() {
if (this.traceKeys == null) {
this.traceKeys = this.beanFactory.getBean(TraceKeys.class);
}
return this.traceKeys;
}
protected MessagingSpanTextMapExtractor getSpanExtractor() {
if (this.spanExtractor == null) {
this.spanExtractor = this.beanFactory.getBean(MessagingSpanTextMapExtractor.class);
}
return this.spanExtractor;
}
protected MessagingSpanTextMapInjector getSpanInjector() {
if (this.spanInjector == null) {
this.spanInjector = this.beanFactory.getBean(MessagingSpanTextMapInjector.class);
}
return this.spanInjector;
}
protected ErrorParser getErrorParser() {
if (this.errorParser == null) {
this.errorParser = this.beanFactory.getBean(ErrorParser.class);
}
return this.errorParser;
}
/**
* Returns a span given the message and a channel. Returns {@code null} if ids are
* missing.
*/
protected Span buildSpan(SpanTextMap carrier) {
try {
return getSpanExtractor().joinTrace(carrier);
} catch (Exception e) {
log.error("Exception occurred while trying to extract span from carrier", e);
return null;
}
}
String getChannelName(MessageChannel channel) {
String name = null;
if (ClassUtils.isPresent(
"org.springframework.integration.context.IntegrationObjectSupport",
null)) {
if (channel instanceof IntegrationObjectSupport) {
name = ((IntegrationObjectSupport) channel).getComponentName();
}
if (name == null && channel instanceof AbstractMessageChannel) {
name = ((AbstractMessageChannel) channel).getFullChannelName();
}
}
if (name == null) {
name = channel.toString();
}
return name;
}
String getMessageChannelName(MessageChannel channel) {
return SpanNameUtil.shorten(MESSAGE_COMPONENT + ":" + getChannelName(channel));
}
}

View File

@@ -1,96 +0,0 @@
package org.springframework.cloud.sleuth.instrument.messaging;
import java.util.Map;
import java.util.Random;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanTextMap;
import org.springframework.cloud.sleuth.util.TextMapUtil;
/**
* Default implementation for messaging
*
* @author Marcin Grzejszczak
* @since 1.2.0
*/
public class HeaderBasedMessagingExtractor implements MessagingSpanTextMapExtractor {
private final Random random = new Random();
@Override
public Span joinTrace(SpanTextMap textMap) {
Map<String, String> carrier = TextMapUtil.asMap(textMap);
boolean spanIdMissing = !hasHeader(carrier, TraceMessageHeaders.SPAN_ID_NAME);
boolean traceIdMissing = !hasHeader(carrier, TraceMessageHeaders.TRACE_ID_NAME);
if (Span.SPAN_SAMPLED.equals(carrier.get(TraceMessageHeaders.SPAN_FLAGS_NAME))) {
String traceId = generateTraceIdIfMissing(carrier, traceIdMissing);
if (spanIdMissing) {
carrier.put(TraceMessageHeaders.SPAN_ID_NAME, traceId);
}
} else if (spanIdMissing) {
return null;
// TODO: Consider throwing IllegalArgumentException;
}
boolean idMissing = spanIdMissing || traceIdMissing;
return extractSpanFromHeaders(carrier, Span.builder(), idMissing);
}
private String generateTraceIdIfMissing(Map<String, String> carrier,
boolean traceIdMissing) {
if (traceIdMissing) {
carrier.put(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(this.random.nextLong()));
}
return carrier.get(TraceMessageHeaders.TRACE_ID_NAME);
}
private Span extractSpanFromHeaders(Map<String, String> carrier,
Span.SpanBuilder spanBuilder, boolean idMissing) {
String traceId = carrier.get(TraceMessageHeaders.TRACE_ID_NAME);
spanBuilder = spanBuilder
.traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0)
.traceId(Span.hexToId(traceId))
.spanId(Span.hexToId(carrier.get(TraceMessageHeaders.SPAN_ID_NAME)));
String flags = carrier.get(TraceMessageHeaders.SPAN_FLAGS_NAME);
boolean debug = Span.SPAN_SAMPLED.equals(flags);
boolean spanSampled = Span.SPAN_SAMPLED.equals(carrier.get(TraceMessageHeaders.SAMPLED_NAME));
if (debug) {
spanBuilder.exportable(true);
} else {
spanBuilder.exportable(spanSampled);
}
String processId = carrier.get(TraceMessageHeaders.PROCESS_ID_NAME);
String spanName = carrier.get(TraceMessageHeaders.SPAN_NAME_NAME);
if (spanName != null) {
spanBuilder.name(spanName);
}
if (processId != null) {
spanBuilder.processId(processId);
}
setParentIdIfApplicable(carrier, spanBuilder, TraceMessageHeaders.PARENT_ID_NAME);
spanBuilder.remote(true);
spanBuilder.shared((debug || spanSampled) && !idMissing);
for (Map.Entry<String, String> entry : carrier.entrySet()) {
if (entry.getKey().toLowerCase().startsWith(Span.SPAN_BAGGAGE_HEADER_PREFIX + TraceMessageHeaders.HEADER_DELIMITER)) {
spanBuilder.baggage(unprefixedKey(entry.getKey()), entry.getValue());
}
}
return spanBuilder.build();
}
boolean hasHeader(Map<String, String> message, String name) {
return message.containsKey(name);
}
private void setParentIdIfApplicable(Map<String, String> carrier, Span.SpanBuilder spanBuilder,
String spanParentIdHeader) {
String parentId = carrier.get(spanParentIdHeader);
if (parentId != null) {
spanBuilder.parent(Span.hexToId(parentId));
}
}
private String unprefixedKey(String key) {
return key.substring(key.indexOf(TraceMessageHeaders.HEADER_DELIMITER) + 1).toLowerCase();
}
}

View File

@@ -1,112 +0,0 @@
package org.springframework.cloud.sleuth.instrument.messaging;
import java.util.List;
import java.util.Map;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanTextMap;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.util.TextMapUtil;
import org.springframework.util.StringUtils;
/**
* Default implementation for messaging
*
* @author Marcin Grzejszczak
* @since 1.2.0
*/
public class HeaderBasedMessagingInjector implements MessagingSpanTextMapInjector {
private final TraceKeys traceKeys;
public HeaderBasedMessagingInjector(TraceKeys traceKeys) {
this.traceKeys = traceKeys;
}
@Override
public void inject(Span span, SpanTextMap carrier) {
Map<String, String> map = TextMapUtil.asMap(carrier);
if (span == null) {
if (!isSampled(map, TraceMessageHeaders.SAMPLED_NAME)) {
carrier.put(TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED);
return;
}
return;
}
addHeaders(map, span, carrier);
}
private boolean isSampled(Map<String, String> initialMessage, String sampledHeaderName) {
return Span.SPAN_SAMPLED.equals(initialMessage.get(sampledHeaderName));
}
private void addHeaders(Map<String, String> map, Span span, SpanTextMap textMap) {
addHeader(map, textMap, TraceMessageHeaders.TRACE_ID_NAME, span.traceIdString());
addHeader(map, textMap, TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
if (span.isExportable()) {
addAnnotations(this.traceKeys, textMap, span);
Long parentId = getFirst(span.getParents());
if (parentId != null) {
addHeader(map, textMap, TraceMessageHeaders.PARENT_ID_NAME, Span.idToHex(parentId));
}
addHeader(map, textMap, TraceMessageHeaders.SPAN_NAME_NAME, span.getName());
addHeader(map, textMap, TraceMessageHeaders.PROCESS_ID_NAME, span.getProcessId());
addHeader(map, textMap, TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_SAMPLED);
}
else {
addHeader(map, 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) {
Map<String, String> map = TextMapUtil.asMap(spanTextMap);
for (String name : traceKeys.getMessage().getHeaders()) {
if (map.containsKey(name)) {
String key = traceKeys.getMessage().getPrefix() + name.toLowerCase();
Object value = map.get(name);
if (value == null) {
value = "null";
}
// TODO: better way to serialize?
tagIfEntryMissing(span, key, value.toString());
}
}
addPayloadAnnotations(traceKeys, map, span);
}
private void addPayloadAnnotations(TraceKeys traceKeys, Map<String, String> map, Span span) {
if (map.containsKey(traceKeys.getMessage().getPayload().getType())) {
tagIfEntryMissing(span, traceKeys.getMessage().getPayload().getType(),
map.get(traceKeys.getMessage().getPayload().getType()));
tagIfEntryMissing(span, traceKeys.getMessage().getPayload().getSize(),
map.get(traceKeys.getMessage().getPayload().getSize()));
}
}
private void tagIfEntryMissing(Span span, String key, String value) {
if (!span.tags().containsKey(key)) {
span.tag(key, value);
}
}
private void addHeader(Map<String, String> map, SpanTextMap textMap, String name, String value) {
if (StringUtils.hasText(value) && !map.containsKey(name)) {
textMap.put(name, value);
}
}
private Long getFirst(List<Long> parents) {
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

@@ -1,44 +0,0 @@
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.messaging;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.integration.channel.ChannelInterceptorAware;
import org.springframework.integration.channel.interceptor.VetoCapableInterceptor;
import org.springframework.messaging.support.ChannelInterceptor;
/**
* @author Dave Syer
*
*/
class IntegrationTraceChannelInterceptor extends TraceChannelInterceptor implements VetoCapableInterceptor {
IntegrationTraceChannelInterceptor(BeanFactory beanFactory) {
super(beanFactory);
}
@Override
public boolean shouldIntercept(String beanName, ChannelInterceptorAware channel) {
for (ChannelInterceptor interceptor : channel.getChannelInterceptors()) {
if (interceptor instanceof AbstractTraceChannelInterceptor) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,138 @@
package org.springframework.cloud.sleuth.instrument.messaging;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import brave.propagation.Propagation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.messaging.support.NativeMessageHeaderAccessor;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.StringUtils;
import static org.springframework.messaging.support.NativeMessageHeaderAccessor.NATIVE_HEADERS;
/**
* This always sets native headers in defence of STOMP issues discussed <a href="https://github.com/spring-cloud/spring-cloud-sleuth/issues/716#issuecomment-337523705">here</a>
*/
enum MessageHeaderPropagation
implements Propagation.Setter<MessageHeaderAccessor, String>,
Propagation.Getter<MessageHeaderAccessor, String> {
INSTANCE;
private static final Log log = LogFactory.getLog(MessageHeaderPropagation.class);
private static final Map<String, String> LEGACY_HEADER_MAPPING = new HashMap<>();
private static final String TRACE_ID_NAME = "X-B3-TraceId";
private static final String SPAN_ID_NAME = "X-B3-SpanId";
private static final String PARENT_SPAN_ID_NAME = "X-B3-ParentSpanId";
private static final String SAMPLED_NAME = "X-B3-Sampled";
private static final String FLAGS_NAME = "X-B3-Flags";
static {
LEGACY_HEADER_MAPPING.put(TRACE_ID_NAME, TraceMessageHeaders.TRACE_ID_NAME);
LEGACY_HEADER_MAPPING.put(SPAN_ID_NAME, TraceMessageHeaders.SPAN_ID_NAME);
LEGACY_HEADER_MAPPING.put(PARENT_SPAN_ID_NAME, TraceMessageHeaders.PARENT_ID_NAME);
LEGACY_HEADER_MAPPING.put(SAMPLED_NAME, TraceMessageHeaders.SAMPLED_NAME);
LEGACY_HEADER_MAPPING.put(FLAGS_NAME, TraceMessageHeaders.SPAN_FLAGS_NAME);
}
@Override public void put(MessageHeaderAccessor accessor, String key, String value) {
try {
doPut(accessor, key, value);
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("An exception happened when we tried to retrieve the [" + key + "] from message", e);
}
}
String legacyKey = LEGACY_HEADER_MAPPING.get(key);
if (legacyKey != null) {
doPut(accessor, legacyKey, value);
}
}
private void doPut(MessageHeaderAccessor accessor, String key, String value) {
accessor.setHeader(key, value);
if (accessor instanceof NativeMessageHeaderAccessor) {
NativeMessageHeaderAccessor nativeAccessor = (NativeMessageHeaderAccessor) accessor;
nativeAccessor.setNativeHeader(key, value);
}
else {
Map<String, List<String>> nativeHeaders = (Map) accessor
.getHeader(NATIVE_HEADERS);
if (nativeHeaders == null) {
accessor.setHeader(NATIVE_HEADERS,
nativeHeaders = new LinkedMultiValueMap<>());
}
nativeHeaders.put(key, Collections.singletonList(value));
}
}
@Override public String get(MessageHeaderAccessor accessor, String key) {
try {
String value = doGet(accessor, key);
if (StringUtils.hasText(value)) {
return value;
}
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("An exception happened when we tried to retrieve the [" + key + "] from message", e);
}
}
return legacyValue(accessor, key);
}
private String legacyValue(MessageHeaderAccessor accessor, String key) {
String legacyKey = LEGACY_HEADER_MAPPING.get(key);
if (legacyKey != null) {
return doGet(accessor, legacyKey);
}
return null;
}
private String doGet(MessageHeaderAccessor accessor, String key) {
if (accessor instanceof NativeMessageHeaderAccessor) {
NativeMessageHeaderAccessor nativeAccessor = (NativeMessageHeaderAccessor) accessor;
String result = nativeAccessor.getFirstNativeHeader(key);
if (result != null)
return result;
}
else {
Map<String, List<String>> nativeHeaders = (Map) accessor
.getHeader(NATIVE_HEADERS);
if (nativeHeaders != null) {
List<String> result = nativeHeaders.get(key);
if (result != null && !result.isEmpty())
return result.get(0);
}
}
Object result = accessor.getHeader(key);
return result != null ? result.toString() : null;
}
static void removeAnyTraceHeaders(MessageHeaderAccessor accessor,
List<String> keysToRemove) {
for (String keyToRemove : keysToRemove) {
accessor.removeHeader(keyToRemove);
if (accessor instanceof NativeMessageHeaderAccessor) {
NativeMessageHeaderAccessor nativeAccessor = (NativeMessageHeaderAccessor) accessor;
nativeAccessor.removeNativeHeader(keyToRemove);
}
else {
Map<String, List<String>> nativeHeaders = (Map) accessor
.getHeader(NATIVE_HEADERS);
if (nativeHeaders == null)
continue;
nativeHeaders.remove(keyToRemove);
}
}
}
@Override public String toString() {
return "MessageHeaderPropagation{}";
}
}

View File

@@ -1,14 +0,0 @@
package org.springframework.cloud.sleuth.instrument.messaging;
import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.cloud.sleuth.SpanTextMap;
/**
* Contract for extracting tracing headers from a {@link SpanTextMap}
* via message headers
*
* @author Marcin Grzejszczak
* @since 1.2.0
*/
public interface MessagingSpanTextMapExtractor extends SpanExtractor<SpanTextMap> {
}

View File

@@ -1,14 +0,0 @@
package org.springframework.cloud.sleuth.instrument.messaging;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.SpanTextMap;
/**
* Contract for injecting tracing headers from a {@link SpanTextMap}
* via message headers
*
* @author Marcin Grzejszczak
* @since 1.2.0
*/
public interface MessagingSpanTextMapInjector extends SpanInjector<SpanTextMap> {
}

View File

@@ -1,88 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.messaging;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.springframework.cloud.sleuth.SpanTextMap;
import org.springframework.messaging.Message;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.messaging.support.NativeMessageHeaderAccessor;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
* A {@link SpanTextMap} abstraction over {@link MessageBuilder}
*
* @author Marcin Grzejszczak
* @since 1.2.0
*/
class MessagingTextMap implements SpanTextMap {
private final MessageBuilder<?> delegate;
public MessagingTextMap(MessageBuilder<?> delegate) {
this.delegate = delegate;
}
@Override
public Iterator<Map.Entry<String, String>> iterator() {
Map<String, String> map = new HashMap<>();
for (Map.Entry<String, Object> entry : this.delegate.build().getHeaders()
.entrySet()) {
if (!NativeMessageHeaderAccessor.NATIVE_HEADERS.equals(entry.getKey())) {
map.put(entry.getKey(), String.valueOf(entry.getValue()));
}
}
return map.entrySet().iterator();
}
@Override
@SuppressWarnings("unchecked")
public void put(String key, String value) {
if (!StringUtils.hasText(value)) {
return;
}
Message<?> initialMessage = this.delegate.build();
MessageHeaderAccessor accessor = MessageHeaderAccessor
.getMutableAccessor(initialMessage);
accessor.setHeader(key, value);
if (accessor instanceof SimpMessageHeaderAccessor) {
SimpMessageHeaderAccessor nativeAccessor = (SimpMessageHeaderAccessor) accessor;
nativeAccessor.setNativeHeader(key, value);
}
else if (accessor.getHeader(NativeMessageHeaderAccessor.NATIVE_HEADERS) != null) {
if (accessor.getHeader(
NativeMessageHeaderAccessor.NATIVE_HEADERS) instanceof MultiValueMap) {
MultiValueMap<String, String> map = (MultiValueMap<String, String>) accessor
.getHeader(NativeMessageHeaderAccessor.NATIVE_HEADERS);
map.add(key, value);
}
}
else {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
accessor.setHeader(NativeMessageHeaderAccessor.NATIVE_HEADERS, map);
map.add(key, value);
}
this.delegate.copyHeaders(accessor.toMessageHeaders());
}
}

View File

@@ -1,223 +0,0 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.messaging;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.Log;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.sampler.NeverSampler;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.messaging.support.MessageHeaderAccessor;
/**
* A channel interceptor that automatically starts / continues / closes and detaches
* spans.
*
* @author Dave Syer
* @since 1.0.0
*/
public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor {
private static final org.apache.commons.logging.Log log = LogFactory
.getLog(TraceChannelInterceptor.class);
public TraceChannelInterceptor(BeanFactory beanFactory) {
super(beanFactory);
}
@Override
public void afterSendCompletion(Message<?> message, MessageChannel channel,
boolean sent, Exception ex) {
Message<?> retrievedMessage = getMessage(message);
MessageBuilder<?> messageBuilder = MessageBuilder.fromMessage(retrievedMessage);
Span currentSpan = getTracer().isTracing() ? getTracer().getCurrentSpan()
: buildSpan(new MessagingTextMap(messageBuilder));
if (log.isDebugEnabled()) {
log.debug("Completed sending and current span is " + currentSpan);
}
getTracer().continueSpan(currentSpan);
if (containsServerReceived(currentSpan)) {
if (log.isDebugEnabled()) {
log.debug("Marking span with server send");
}
currentSpan.logEvent(Span.SERVER_SEND);
}
else if (currentSpan != null) {
if (log.isDebugEnabled()) {
log.debug("Marking span with client received");
}
currentSpan.logEvent(Span.CLIENT_RECV);
}
addErrorTag(ex);
if (log.isDebugEnabled()) {
log.debug("Closing messaging span " + currentSpan);
}
getTracer().close(currentSpan);
if (log.isDebugEnabled()) {
log.debug("Messaging span " + currentSpan + " successfully closed");
}
}
private boolean containsServerReceived(Span span) {
if (span == null) {
return false;
}
for (Log log : span.logs()) {
if (Span.SERVER_RECV.equals(log.getEvent())) {
return true;
}
}
return false;
}
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
if (log.isDebugEnabled()) {
log.debug("Processing message before sending it to the channel");
}
Message<?> retrievedMessage = getMessage(message);
MessageBuilder<?> messageBuilder = MessageBuilder.fromMessage(retrievedMessage);
Span parentSpan = getTracer().isTracing() ? getTracer().getCurrentSpan()
: buildSpan(new MessagingTextMap(messageBuilder));
// Do not continue the parent (assume that this is handled by caller)
// getTracer().continueSpan(parentSpan);
if (log.isDebugEnabled()) {
log.debug("Parent span is " + parentSpan);
}
String name = getMessageChannelName(channel);
if (log.isDebugEnabled()) {
log.debug("Name of the span will be [" + name + "]");
}
Span span = startSpan(parentSpan, name, message);
if (message.getHeaders()
.containsKey(TraceMessageHeaders.MESSAGE_SENT_FROM_CLIENT)) {
if (log.isDebugEnabled()) {
log.debug("Marking span with server received");
}
span.logEvent(Span.SERVER_RECV);
}
else {
if (log.isDebugEnabled()) {
log.debug("Marking span with client send");
}
span.logEvent(Span.CLIENT_SEND);
messageBuilder.setHeader(TraceMessageHeaders.MESSAGE_SENT_FROM_CLIENT, true);
}
getSpanInjector().inject(span, new MessagingTextMap(messageBuilder));
MessageHeaderAccessor headers = MessageHeaderAccessor.getMutableAccessor(message);
if (message instanceof ErrorMessage) {
headers.copyHeaders(sleuthHeaders(messageBuilder.build().getHeaders()));
return new ErrorMessage((Throwable) message.getPayload(), headers.getMessageHeaders());
}
headers.copyHeaders(messageBuilder.build().getHeaders());
return new GenericMessage<>(message.getPayload(), headers.getMessageHeaders());
}
private Map<String, ?> sleuthHeaders(Map<String, ?> headers) {
Map<String, Object> headersToCopy = new HashMap<>();
for (Map.Entry<String, ?> entry : headers.entrySet()) {
if (TraceMessageHeaders.ALL_HEADERS.contains(entry.getKey())) {
headersToCopy.put(entry.getKey(), entry.getValue());
}
}
return headersToCopy;
}
private Message<?> getMessage(Message<?> message) {
Object payload = message.getPayload();
if (payload instanceof MessagingException) {
MessagingException e = (MessagingException) payload;
return e.getFailedMessage();
}
return message;
}
private Span startSpan(Span span, String name, Message<?> message) {
if (span != null) {
return getTracer().createSpan(name, span);
}
if (Span.SPAN_NOT_SAMPLED
.equals(message.getHeaders().get(TraceMessageHeaders.SAMPLED_NAME))) {
return getTracer().createSpan(name, NeverSampler.INSTANCE);
}
return getTracer().createSpan(name);
}
@Override
public Message<?> beforeHandle(Message<?> message, MessageChannel channel,
MessageHandler handler) {
Message<?> retrievedMessage = getMessage(message);
MessageBuilder<?> messageBuilder = MessageBuilder.fromMessage(retrievedMessage);
Span spanFromHeader = getTracer().isTracing() ? getTracer().getCurrentSpan()
: buildSpan(new MessagingTextMap(messageBuilder));
if (log.isDebugEnabled()) {
log.debug("Continuing span " + spanFromHeader + " before handling message");
}
if (spanFromHeader != null) {
if (log.isDebugEnabled()) {
log.debug("Marking span with server received");
}
spanFromHeader.logEvent(Span.SERVER_RECV);
}
getTracer().continueSpan(spanFromHeader);
if (log.isDebugEnabled()) {
log.debug("Span " + spanFromHeader + " successfully continued");
}
return message;
}
@Override
public void afterMessageHandled(Message<?> message, MessageChannel channel,
MessageHandler handler, Exception ex) {
Span spanFromHeader = getTracer().getCurrentSpan();
if (log.isDebugEnabled()) {
log.debug("Continuing span " + spanFromHeader + " after message handled");
}
if (spanFromHeader != null) {
if (log.isDebugEnabled()) {
log.debug("Marking span with server send");
}
spanFromHeader.logEvent(Span.SERVER_SEND);
addErrorTag(ex);
}
// related to #447
if (getTracer().isTracing()) {
getTracer().detach(spanFromHeader);
if (log.isDebugEnabled()) {
log.debug("Detached " + spanFromHeader + " from current thread");
}
}
}
private void addErrorTag(Exception ex) {
if (ex != null) {
getErrorParser().parseErrorTags(getTracer().getCurrentSpan(), ex);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,6 @@
package org.springframework.cloud.sleuth.instrument.messaging;
import java.util.Arrays;
import java.util.List;
/**
* Contains trace related messaging headers. The deprecated headers contained `-` which
* for example in the JMS specs is invalid. That's why the public constants in this class
@@ -31,16 +28,10 @@ public class TraceMessageHeaders {
public static final String SPAN_ID_NAME = "spanId";
public static final String SAMPLED_NAME = "spanSampled";
public static final String PROCESS_ID_NAME = "spanProcessId";
public static final String PARENT_ID_NAME = "spanParentSpanId";
public static final String TRACE_ID_NAME = "spanTraceId";
public static final String SPAN_NAME_NAME = "spanName";
public static final String SPAN_FLAGS_NAME = "spanFlags";
static final List<String> ALL_HEADERS = Arrays.asList(SPAN_ID_NAME, SAMPLED_NAME,
PROCESS_ID_NAME, PARENT_ID_NAME, TRACE_ID_NAME, SPAN_NAME_NAME, SPAN_FLAGS_NAME);
static final String MESSAGE_SENT_FROM_CLIENT = "messageSent";
static final String HEADER_DELIMITER = "_";
private TraceMessageHeaders() {}
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.messaging;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
/**
* AutoConfiguration containing Span extractor and injector for messaging. Will be reused
* by Messaging and WebSockets
*
* @author Marcin Grzejszczak
* @since 1.0.0
*/
@Configuration
@ConditionalOnClass(Message.class)
@ConditionalOnBean(Tracer.class)
public class TraceSpanMessagingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MessagingSpanTextMapExtractor messagingSpanExtractor() {
return new HeaderBasedMessagingExtractor();
}
@Bean
@ConditionalOnMissingBean
public MessagingSpanTextMapInjector messagingSpanInjector(TraceKeys traceKeys) {
return new HeaderBasedMessagingInjector(traceKeys);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,14 +16,13 @@
package org.springframework.cloud.sleuth.instrument.messaging;
import org.springframework.beans.factory.BeanFactory;
import brave.Tracing;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -37,21 +36,20 @@ import org.springframework.integration.config.GlobalChannelInterceptor;
* @author Spencer Gibb
* @since 1.0.0
*
* @see TraceChannelInterceptor
* @see TracingChannelInterceptor
*/
@Configuration
@ConditionalOnClass(GlobalChannelInterceptor.class)
@ConditionalOnBean(Tracer.class)
@AutoConfigureAfter({ TraceAutoConfiguration.class,
TraceSpanMessagingAutoConfiguration.class })
@ConditionalOnBean(Tracing.class)
@AutoConfigureAfter({ TraceAutoConfiguration.class })
@ConditionalOnProperty(value = "spring.sleuth.integration.enabled", matchIfMissing = true)
@EnableConfigurationProperties(TraceKeys.class)
public class TraceSpringIntegrationAutoConfiguration {
@Bean
@GlobalChannelInterceptor(patterns = "${spring.sleuth.integration.patterns:*}")
public TraceChannelInterceptor traceChannelInterceptor(BeanFactory beanFactory) {
return new IntegrationTraceChannelInterceptor(beanFactory);
public TracingChannelInterceptor traceChannelInterceptor(Tracing tracing) {
return new TracingChannelInterceptor(tracing);
}
}

View File

@@ -0,0 +1,245 @@
package org.springframework.cloud.sleuth.instrument.messaging;
import brave.Span;
import brave.SpanCustomizer;
import brave.Tracer;
import brave.Tracing;
import brave.propagation.ThreadLocalSpan;
import brave.propagation.TraceContext;
import brave.propagation.TraceContextOrSamplingFlags;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.sleuth.util.SpanNameUtil;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.support.ExecutorChannelInterceptor;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.util.ClassUtils;
/**
* This starts and propagates {@link Span.Kind#PRODUCER} span for each message sent (via native
* headers. It also extracts or creates a {@link Span.Kind#CONSUMER} span for each message
* received. This span is injected onto each message so it becomes the parent when a handler later
* calls {@link MessageHandler#handleMessage(Message)}, or a another processing library calls {@link #nextSpan(Message)}.
* <p>
* <p>This implementation uses {@link ThreadLocalSpan} to propagate context between callbacks. This
* is an alternative to {@code ThreadStatePropagationChannelInterceptor} which is less sensitive
* to message manipulation by other interceptors.
*/
public final class TracingChannelInterceptor extends ChannelInterceptorAdapter
implements ExecutorChannelInterceptor {
private static final Log log = LogFactory.getLog(TracingChannelInterceptor.class);
public static TracingChannelInterceptor create(Tracing tracing) {
return new TracingChannelInterceptor(tracing);
}
final Tracing tracing;
final Tracer tracer;
final ThreadLocalSpan threadLocalSpan;
final TraceContext.Injector<MessageHeaderAccessor> injector;
final TraceContext.Extractor<MessageHeaderAccessor> extractor;
TracingChannelInterceptor(Tracing tracing) {
this.tracing = tracing;
this.tracer = tracing.tracer();
this.threadLocalSpan = ThreadLocalSpan.create(this.tracer);
this.injector = tracing.propagation().injector(MessageHeaderPropagation.INSTANCE);
this.extractor = tracing.propagation()
.extractor(MessageHeaderPropagation.INSTANCE);
}
/**
* Use this to create a span for processing the given message. Note: the result has no name and is
* not started.
* <p>
* <p>This creates a child from identifiers extracted from the message headers, or a new span if
* one couldn't be extracted.
*/
public Span nextSpan(Message<?> message) {
MessageHeaderAccessor headers = mutableHeaderAccessor(message);
TraceContextOrSamplingFlags extracted = this.extractor.extract(headers);
headers.setImmutable();
Span result = this.tracer.nextSpan(extracted);
if (extracted.context() == null && !result.isNoop()) {
addTags(message, result, null);
}
if (log.isDebugEnabled()) {
log.debug("Created a new span " + result);
}
return result;
}
/**
* Starts and propagates {@link Span.Kind#PRODUCER} span for each message sent.
*/
@Override public Message<?> preSend(Message<?> message, MessageChannel channel) {
MessageHeaderAccessor headers = mutableHeaderAccessor(message);
TraceContextOrSamplingFlags extracted = this.extractor.extract(headers);
Span span = this.threadLocalSpan.next(extracted);
MessageHeaderPropagation
.removeAnyTraceHeaders(headers, this.tracing.propagation().keys());
this.injector.inject(span.context(), headers);
if (!span.isNoop()) {
span.kind(Span.Kind.PRODUCER).name("send").start();
addTags(message, span, channel);
}
if (log.isDebugEnabled()) {
log.debug("Created a new span in pre send" + span);
}
headers.setImmutable();
return new GenericMessage<>(message.getPayload(), headers.getMessageHeaders());
}
@Override public void afterSendCompletion(Message<?> message, MessageChannel channel,
boolean sent, Exception ex) {
if (log.isDebugEnabled()) {
log.debug("Will finish the current span after completion " + this.tracer.currentSpan());
}
finishSpan(ex);
}
/**
* This starts a consumer span as a child of the incoming message or the current trace context,
* placing it in scope until the receive completes.
*/
@Override public Message<?> postReceive(Message<?> message, MessageChannel channel) {
MessageHeaderAccessor headers = mutableHeaderAccessor(message);
TraceContextOrSamplingFlags extracted = this.extractor.extract(headers);
Span span = this.threadLocalSpan.next(extracted);
MessageHeaderPropagation
.removeAnyTraceHeaders(headers, this.tracing.propagation().keys());
this.injector.inject(span.context(), headers);
if (!span.isNoop()) {
span.kind(Span.Kind.CONSUMER).name("receive").start();
addTags(message, span, channel);
}
if (log.isDebugEnabled()) {
log.debug("Created a new span in post receive " + span);
}
headers.setImmutable();
return new GenericMessage<>(message.getPayload(), headers.getMessageHeaders());
}
@Override
public void afterReceiveCompletion(Message<?> message, MessageChannel channel,
Exception ex) {
if (log.isDebugEnabled()) {
log.debug("Will finish the current span after receive completion " + this.tracer.currentSpan());
}
finishSpan(ex);
}
/**
* This starts a consumer span as a child of the incoming message or the current trace context.
* It then creates a span for the handler, placing it in scope.
*/
@Override public Message<?> beforeHandle(Message<?> message, MessageChannel channel,
MessageHandler handler) {
MessageHeaderAccessor headers = mutableHeaderAccessor(message);
TraceContextOrSamplingFlags extracted = this.extractor.extract(headers);
// Start and finish a consumer span as we will immediately process it.
Span consumerSpan = this.tracer.nextSpan(extracted);
if (!consumerSpan.isNoop()) {
consumerSpan.kind(Span.Kind.CONSUMER).start();
addTags(message, consumerSpan, channel);
consumerSpan.finish();
}
// create and scope a span for the message processor
this.threadLocalSpan.next(TraceContextOrSamplingFlags.create(consumerSpan.context()))
.name("handle").start();
// remove any trace headers, but don't re-inject as we are synchronously processing the
// message and can rely on scoping to access this span later.
MessageHeaderPropagation
.removeAnyTraceHeaders(headers, this.tracing.propagation().keys());
if (log.isDebugEnabled()) {
log.debug("Created a new span in before handle" + consumerSpan);
}
if (message instanceof ErrorMessage) {
return new ErrorMessage((Throwable) message.getPayload(), headers.getMessageHeaders());
}
headers.setImmutable();
return new GenericMessage<>(message.getPayload(), headers.getMessageHeaders());
}
@Override public void afterMessageHandled(Message<?> message, MessageChannel channel,
MessageHandler handler, Exception ex) {
if (log.isDebugEnabled()) {
log.debug("Will finish the current span after message handled " + this.tracer.currentSpan());
}
finishSpan(ex);
}
/**
* When an upstream context was not present, lookup keys are unlikely added
*/
static void addTags(Message<?> message, SpanCustomizer result, MessageChannel channel) {
// TODO topic etc
if (channel != null) {
result.tag("channel", messageChannelName(channel));
}
}
private static String channelName(MessageChannel channel) {
String name = null;
if (ClassUtils.isPresent(
"org.springframework.integration.context.IntegrationObjectSupport",
null)) {
if (channel instanceof IntegrationObjectSupport) {
name = ((IntegrationObjectSupport) channel).getComponentName();
}
if (name == null && channel instanceof AbstractMessageChannel) {
name = ((AbstractMessageChannel) channel).getFullChannelName();
}
}
if (name == null) {
name = channel.toString();
}
return name;
}
private static String messageChannelName(MessageChannel channel) {
return SpanNameUtil.shorten("send:" + channelName(channel));
}
void finishSpan(Exception error) {
Span span = this.threadLocalSpan.remove();
if (span == null || span.isNoop())
return;
if (error != null) { // an error occurred, adding error to span
String message = error.getMessage();
if (message == null)
message = error.getClass().getSimpleName();
span.tag("error", message);
}
span.finish();
}
private MessageHeaderAccessor mutableHeaderAccessor(Message<?> message) {
MessageHeaderAccessor headers = MessageHeaderAccessor.getMutableAccessor(getMessage(message));
headers.setLeaveMutable(true);
return headers;
}
private Message<?> getMessage(Message<?> message) {
Object payload = message.getPayload();
if (payload instanceof MessagingException) {
MessagingException e = (MessagingException) payload;
return e.getFailedMessage();
}
return message;
}
}

View File

@@ -1,21 +1,14 @@
package org.springframework.cloud.sleuth.instrument.messaging.websocket;
import org.springframework.beans.factory.BeanFactory;
import brave.Tracing;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.messaging.MessagingSpanTextMapExtractor;
import org.springframework.cloud.sleuth.instrument.messaging.MessagingSpanTextMapInjector;
import org.springframework.cloud.sleuth.instrument.messaging.TraceChannelInterceptor;
import org.springframework.cloud.sleuth.instrument.messaging.TraceSpanMessagingAutoConfiguration;
import org.springframework.cloud.sleuth.instrument.messaging.TracingChannelInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@@ -29,25 +22,15 @@ import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
*
* @see AbstractWebSocketMessageBrokerConfigurer
*/
@Component
@Configuration
@AutoConfigureAfter(TraceSpanMessagingAutoConfiguration.class)
@ConditionalOnClass(DelegatingWebSocketMessageBrokerConfiguration.class)
@ConditionalOnBean(Tracer.class)
@ConditionalOnBean(Tracing.class)
@ConditionalOnProperty(value = "spring.sleuth.integration.websockets.enabled", matchIfMissing = true)
public class TraceWebSocketAutoConfiguration
extends AbstractWebSocketMessageBrokerConfigurer {
@Autowired
BeanFactory beanFactory;
@Autowired
Tracer tracer;
@Autowired
TraceKeys traceKeys;
@Autowired
MessagingSpanTextMapExtractor spanExtractor;
@Autowired
MessagingSpanTextMapInjector spanInjector;
Tracing tracing;
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
@@ -56,16 +39,16 @@ public class TraceWebSocketAutoConfiguration
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.configureBrokerChannel().setInterceptors(new TraceChannelInterceptor(this.beanFactory));
registry.configureBrokerChannel().setInterceptors(TracingChannelInterceptor.create(this.tracing));
}
@Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new TraceChannelInterceptor(this.beanFactory));
registration.setInterceptors(TracingChannelInterceptor.create(this.tracing));
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new TraceChannelInterceptor(this.beanFactory));
registration.setInterceptors(TracingChannelInterceptor.create(this.tracing));
}
}

View File

@@ -3,12 +3,11 @@ package org.springframework.cloud.sleuth.instrument.reactor;
import java.util.function.Function;
import java.util.function.Predicate;
import org.reactivestreams.Publisher;
import org.springframework.cloud.sleuth.Tracer;
import brave.Tracing;
import reactor.core.Fuseable;
import reactor.core.Scannable;
import reactor.core.publisher.Operators;
import org.reactivestreams.Publisher;
/**
* Reactive Span pointcuts factories
@@ -19,18 +18,19 @@ import reactor.core.publisher.Operators;
public abstract class ReactorSleuth {
/**
* Return a span operator pointcut given a {@link Tracer}. This can be used in reactor
* Return a span operator pointcut given a {@link Tracing}. This can be used in reactor
* via {@link reactor.core.publisher.Flux#transform(Function)}, {@link
* reactor.core.publisher.Mono#transform(Function)}, {@link
* reactor.core.publisher.Hooks#onEachOperator(Function)} or {@link
* reactor.core.publisher.Hooks#onLastOperator(Function)}.
*
* @param tracer the {@link Tracer} instance to use in this span operator
* @param tracing the {@link Tracing} instance to use in this span operator
* @param <T> an arbitrary type that is left unchanged by the span operator
*
* @return a new Span operator pointcut
*/
public static <T> Function<? super Publisher<T>, ? extends Publisher<T>> spanOperator(Tracer tracer) {
public static <T> Function<? super Publisher<T>, ? extends Publisher<T>> spanOperator(
Tracing tracing) {
return Operators.lift(POINTCUT_FILTER, ((scannable, sub) -> {
//do not trace fused flows
if(scannable instanceof Fuseable && sub instanceof Fuseable.QueueSubscription){
@@ -39,7 +39,7 @@ public abstract class ReactorSleuth {
return new SpanSubscriber<>(
sub,
sub.currentContext(),
tracer,
tracing,
scannable.name());
}));
}

View File

@@ -2,14 +2,16 @@ package org.springframework.cloud.sleuth.instrument.reactor;
import java.util.concurrent.atomic.AtomicBoolean;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import brave.Span;
import brave.Tracer;
import brave.Tracing;
import brave.propagation.TraceContextOrSamplingFlags;
import reactor.core.CoreSubscriber;
import reactor.util.Logger;
import reactor.util.Loggers;
import reactor.util.context.Context;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
/**
* A trace representation of the {@link Subscriber}
@@ -21,7 +23,8 @@ import reactor.util.context.Context;
final class SpanSubscriber<T> extends AtomicBoolean implements Subscription,
CoreSubscriber<T> {
private static final Logger log = Loggers.getLogger(SpanSubscriber.class);
private static final Logger log = Loggers.getLogger(
SpanSubscriber.class);
private final Span span;
private final Span rootSpan;
@@ -30,11 +33,11 @@ final class SpanSubscriber<T> extends AtomicBoolean implements Subscription,
private final Tracer tracer;
private Subscription s;
SpanSubscriber(Subscriber<? super T> subscriber, Context ctx, Tracer tracer,
SpanSubscriber(Subscriber<? super T> subscriber, Context ctx, Tracing tracing,
String name) {
this.subscriber = subscriber;
this.tracer = tracer;
Span root = ctx.getOrDefault(Span.class, tracer.getCurrentSpan());
this.tracer = tracing.tracer();
Span root = ctx.getOrDefault(Span.class, this.tracer.currentSpan());
if (log.isTraceEnabled()) {
log.trace("Span from context [{}]", root);
}
@@ -42,7 +45,9 @@ final class SpanSubscriber<T> extends AtomicBoolean implements Subscription,
if (log.isTraceEnabled()) {
log.trace("Stored context root span [{}]", this.rootSpan);
}
this.span = tracer.createSpan(name, root);
this.span = root != null ?
this.tracer.nextSpan(TraceContextOrSamplingFlags.create(root.context()))
.name(name) : this.tracer.nextSpan().name(name);
if (log.isTraceEnabled()) {
log.trace("Created span [{}], with name [{}]", this.span, name);
}
@@ -54,55 +59,29 @@ final class SpanSubscriber<T> extends AtomicBoolean implements Subscription,
log.trace("On subscribe");
}
this.s = subscription;
this.tracer.continueSpan(this.span);
if (log.isTraceEnabled()) {
log.trace("On subscribe - span continued");
try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(this.span)) {
if (log.isTraceEnabled()) {
log.trace("On subscribe - span continued");
}
this.subscriber.onSubscribe(this);
}
this.subscriber.onSubscribe(this);
}
@Override public void request(long n) {
if (log.isTraceEnabled()) {
log.trace("Request");
}
this.tracer.continueSpan(this.span);
if (log.isTraceEnabled()) {
log.trace("Request - continued");
}
this.s.request(n);
// We're in the main thread so we don't want to pollute it with wrong spans
// that's why we need to detach the current one and continue with its parent
Span localRootSpan = this.span;
while (localRootSpan != null) {
if (this.rootSpan != null) {
if (localRootSpan.getSpanId() != this.rootSpan.getSpanId() &&
!isRootParentSpan(localRootSpan)) {
localRootSpan = continueDetachedSpan(localRootSpan);
} else {
localRootSpan = null;
}
} else if (!isRootParentSpan(localRootSpan)) {
localRootSpan = continueDetachedSpan(localRootSpan);
} else {
localRootSpan = null;
try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(this.span)) {
if (log.isTraceEnabled()) {
log.trace("Request - continued");
}
this.s.request(n);
// no additional cleaning is required cause we operate on scopes
if (log.isTraceEnabled()) {
log.trace("Request after cleaning. Current span [{}]",
this.tracer.currentSpan());
}
}
if (log.isTraceEnabled()) {
log.trace("Request after cleaning. Current span [{}]",
this.tracer.getCurrentSpan());
}
}
private boolean isRootParentSpan(Span localRootSpan) {
return localRootSpan.getSpanId() == localRootSpan.getTraceId();
}
private Span continueDetachedSpan(Span localRootSpan) {
if (log.isTraceEnabled()) {
log.trace("Will detach span {}", localRootSpan);
}
Span detachedSpan = this.tracer.detach(localRootSpan);
return this.tracer.continueSpan(detachedSpan);
}
@Override public void cancel() {
@@ -144,12 +123,12 @@ final class SpanSubscriber<T> extends AtomicBoolean implements Subscription,
if (log.isTraceEnabled()) {
log.trace("Cleaning up");
}
if (this.tracer.getCurrentSpan() != this.span) {
Tracer.SpanInScope ws = null;
if (this.tracer.currentSpan() != this.span) {
if (log.isTraceEnabled()) {
log.trace("Detaching span");
}
this.tracer.detach(this.tracer.getCurrentSpan());
this.tracer.continueSpan(this.span);
ws = this.tracer.withSpanInScope(this.span);
if (log.isTraceEnabled()) {
log.trace("Continuing span");
}
@@ -157,13 +136,15 @@ final class SpanSubscriber<T> extends AtomicBoolean implements Subscription,
if (log.isTraceEnabled()) {
log.trace("Closing span");
}
this.tracer.close(this.span);
this.span.finish();
if (ws != null) {
ws.close();
}
if (log.isTraceEnabled()) {
log.trace("Span closed");
}
if (this.rootSpan != null) {
this.tracer.continueSpan(this.rootSpan);
this.tracer.close(this.rootSpan);
this.rootSpan.finish();
if (log.isTraceEnabled()) {
log.trace("Closed root span");
}

View File

@@ -1,10 +1,15 @@
package org.springframework.cloud.sleuth.instrument.reactor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Supplier;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Supplier;
import brave.Tracing;
import reactor.core.publisher.Hooks;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -12,16 +17,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.async.TraceableScheduledExecutorService;
import org.springframework.cloud.sleuth.instrument.web.TraceWebFluxAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Hooks;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration}
@@ -38,42 +37,31 @@ import reactor.core.scheduler.Schedulers;
public class TraceReactorAutoConfiguration {
@Configuration
@ConditionalOnBean(Tracer.class)
@ConditionalOnBean(Tracing.class)
static class TraceReactorConfiguration {
@Autowired Tracer tracer;
@Autowired TraceKeys traceKeys;
@Autowired SpanNamer spanNamer;
@Autowired Tracing tracing;
@Autowired BeanFactory beanFactory;
@Autowired LastOperatorWrapper lastOperatorWrapper;
@Bean
@ConditionalOnNotWebApplication
LastOperatorWrapper spanOperator() {
return new LastOperatorWrapper() {
@Override public void wrapLastOperator(Tracer tracer) {
Hooks.onLastOperator(ReactorSleuth.spanOperator(tracer));
}
};
@ConditionalOnNotWebApplication LastOperatorWrapper spanOperator() {
return tracer -> Hooks.onLastOperator(ReactorSleuth.spanOperator(tracer));
}
@Bean
@ConditionalOnWebApplication
LastOperatorWrapper noOpLastOperatorWrapper() {
return new LastOperatorWrapper() {
@Override public void wrapLastOperator(Tracer tracer) {
}
};
@ConditionalOnWebApplication LastOperatorWrapper noOpLastOperatorWrapper() {
return tracer -> { };
}
@PostConstruct
public void setupHooks() {
this.lastOperatorWrapper.wrapLastOperator(this.tracer);
this.lastOperatorWrapper.wrapLastOperator(this.tracing);
Schedulers.setFactory(new Schedulers.Factory() {
@Override public ScheduledExecutorService decorateExecutorService(String schedulerType,
Supplier<? extends ScheduledExecutorService> actual) {
return new TraceableScheduledExecutorService(actual.get(),
TraceReactorConfiguration.this.tracer,
TraceReactorConfiguration.this.traceKeys,
TraceReactorConfiguration.this.spanNamer);
return new TraceableScheduledExecutorService(
TraceReactorConfiguration.this.beanFactory,
actual.get());
}
});
}
@@ -87,5 +75,5 @@ public class TraceReactorAutoConfiguration {
}
interface LastOperatorWrapper {
void wrapLastOperator(Tracer tracer);
void wrapLastOperator(Tracing tracer);
}

View File

@@ -2,19 +2,19 @@ package org.springframework.cloud.sleuth.instrument.rxjava;
import java.util.Arrays;
import brave.Tracer;
import brave.Tracing;
import rx.plugins.RxJavaSchedulersHook;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import rx.plugins.RxJavaSchedulersHook;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} that
* enables support for RxJava via {@link RxJavaSchedulersHook}.
@@ -24,7 +24,7 @@ import rx.plugins.RxJavaSchedulersHook;
*/
@Configuration
@AutoConfigureAfter(TraceAutoConfiguration.class)
@ConditionalOnBean(Tracer.class)
@ConditionalOnBean(Tracing.class)
@ConditionalOnClass(RxJavaSchedulersHook.class)
@ConditionalOnProperty(value = "spring.sleuth.rxjava.schedulers.hook.enabled", matchIfMissing = true)
@EnableConfigurationProperties(SleuthRxJavaSchedulersProperties.class)

View File

@@ -2,12 +2,11 @@ package org.springframework.cloud.sleuth.instrument.rxjava;
import java.util.List;
import brave.Span;
import brave.Tracer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import rx.functions.Action0;
import rx.plugins.RxJavaErrorHandler;
import rx.plugins.RxJavaObservableExecutionHook;
@@ -23,7 +22,8 @@ import rx.plugins.RxJavaSchedulersHook;
*/
class SleuthRxJavaSchedulersHook extends RxJavaSchedulersHook {
private static final Log log = LogFactory.getLog(SleuthRxJavaSchedulersHook.class);
private static final Log log = LogFactory.getLog(
SleuthRxJavaSchedulersHook.class);
private static final String RXJAVA_COMPONENT = "rxjava";
private final Tracer tracer;
@@ -93,7 +93,7 @@ class SleuthRxJavaSchedulersHook extends RxJavaSchedulersHook {
this.tracer = tracer;
this.traceKeys = traceKeys;
this.threadsToIgnore = threadsToIgnore;
this.parent = tracer.getCurrentSpan();
this.parent = this.tracer.currentSpan();
this.actual = actual;
}
@@ -116,21 +116,19 @@ class SleuthRxJavaSchedulersHook extends RxJavaSchedulersHook {
Span span = this.parent;
boolean created = false;
if (span != null) {
span = this.tracer.continueSpan(span);
span = this.tracer.joinSpan(this.parent.context());
} else {
span = this.tracer.createSpan(RXJAVA_COMPONENT);
this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, RXJAVA_COMPONENT);
this.tracer.addTag(this.traceKeys.getAsync().getPrefix()
+ this.traceKeys.getAsync().getThreadNameKey(), Thread.currentThread().getName());
span = this.tracer.nextSpan().name(RXJAVA_COMPONENT).start();
span.tag(this.traceKeys.getAsync().getPrefix()
+ this.traceKeys.getAsync().getThreadNameKey(),
Thread.currentThread().getName());
created = true;
}
try {
try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) {
this.actual.call();
} finally {
if (created) {
this.tracer.close(span);
} else if (this.tracer.isTracing()) {
this.tracer.detach(span);
span.finish();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,12 +18,13 @@ package org.springframework.cloud.sleuth.instrument.scheduling;
import java.util.regex.Pattern;
import brave.Span;
import brave.Tracer;
import brave.Tracing;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.util.SpanNameUtil;
/**
@@ -39,21 +40,20 @@ import org.springframework.cloud.sleuth.util.SpanNameUtil;
* @author Spencer Gibb
* @since 1.0.0
*
* @see Tracer
* @see Tracing
*/
@Aspect
public class TraceSchedulingAspect {
private static final String SCHEDULED_COMPONENT = "scheduled";
private final Tracer tracer;
private final TraceKeys traceKeys;
private final Pattern skipPattern;
private final TraceKeys traceKeys;
public TraceSchedulingAspect(Tracer tracer, TraceKeys traceKeys, Pattern skipPattern) {
public TraceSchedulingAspect(Tracer tracer, Pattern skipPattern,
TraceKeys traceKeys) {
this.tracer = tracer;
this.traceKeys = traceKeys;
this.skipPattern = skipPattern;
this.traceKeys = traceKeys;
}
@Around("execution (@org.springframework.scheduling.annotation.Scheduled * *.*(..))")
@@ -62,18 +62,24 @@ public class TraceSchedulingAspect {
return pjp.proceed();
}
String spanName = SpanNameUtil.toLowerHyphen(pjp.getSignature().getName());
Span span = this.tracer.createSpan(spanName);
this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, SCHEDULED_COMPONENT);
this.tracer.addTag(this.traceKeys.getAsync().getPrefix() +
this.traceKeys.getAsync().getClassNameKey(), pjp.getTarget().getClass().getSimpleName());
this.tracer.addTag(this.traceKeys.getAsync().getPrefix() +
this.traceKeys.getAsync().getMethodNameKey(), pjp.getSignature().getName());
try {
Span span = startOrContinueRenamedSpan(spanName);
try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) {
span.tag(this.traceKeys.getAsync().getPrefix() +
this.traceKeys.getAsync().getClassNameKey(), pjp.getTarget().getClass().getSimpleName());
span.tag(this.traceKeys.getAsync().getPrefix() +
this.traceKeys.getAsync().getMethodNameKey(), pjp.getSignature().getName());
return pjp.proceed();
}
finally {
this.tracer.close(span);
} finally {
span.finish();
}
}
private Span startOrContinueRenamedSpan(String spanName) {
Span currentSpan = this.tracer.currentSpan();
if (currentSpan != null) {
return currentSpan.name(spanName);
}
return this.tracer.nextSpan().name(spanName);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,20 +16,21 @@
package org.springframework.cloud.sleuth.instrument.scheduling;
import java.util.regex.Pattern;
import brave.Tracer;
import brave.Tracing;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import java.util.regex.Pattern;
/**
* Registers beans related to task scheduling.
*
@@ -42,15 +43,16 @@ import java.util.regex.Pattern;
@Configuration
@EnableAspectJAutoProxy
@ConditionalOnProperty(value = "spring.sleuth.scheduled.enabled", matchIfMissing = true)
@ConditionalOnBean(Tracer.class)
@ConditionalOnBean(Tracing.class)
@AutoConfigureAfter(TraceAutoConfiguration.class)
@EnableConfigurationProperties(SleuthSchedulingProperties.class)
public class TraceSchedulingAutoConfiguration {
@ConditionalOnClass(name = "org.aspectj.lang.ProceedingJoinPoint")
@Bean
public TraceSchedulingAspect traceSchedulingAspect(Tracer tracer, TraceKeys traceKeys,
SleuthSchedulingProperties sleuthSchedulingProperties) {
return new TraceSchedulingAspect(tracer, traceKeys, Pattern.compile(sleuthSchedulingProperties.getSkipPattern()));
@ConditionalOnClass(name = "org.aspectj.lang.ProceedingJoinPoint")
public TraceSchedulingAspect traceSchedulingAspect(Tracer tracer,
SleuthSchedulingProperties sleuthSchedulingProperties, TraceKeys traceKeys) {
return new TraceSchedulingAspect(tracer,
Pattern.compile(sleuthSchedulingProperties.getSkipPattern()), traceKeys);
}
}

View File

@@ -1,81 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.web;
import java.util.AbstractMap;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.cloud.sleuth.SpanTextMap;
import org.springframework.web.util.UrlPathHelper;
/**
* A {@link SpanTextMap} abstraction over {@link HttpServletRequest}
*
* @author Marcin Grzejszczak
* @since 1.2.0
*/
class HttpServletRequestTextMap implements SpanTextMap {
private final HttpServletRequest delegate;
private final UrlPathHelper urlPathHelper;
HttpServletRequestTextMap(HttpServletRequest delegate) {
this.delegate = delegate;
this.urlPathHelper = new UrlPathHelper();
}
@Override
public Iterator<Map.Entry<String, String>> iterator() {
final Enumeration<String> headerNames = this.delegate.getHeaderNames();
return new Iterator<Map.Entry<String, String>>() {
private boolean useAdditionalHeader = true;
@Override
public boolean hasNext() {
return useAdditionalHeader
|| (headerNames != null && headerNames.hasMoreElements());
}
@Override
public Map.Entry<String, String> next() {
if (useAdditionalHeader) {
useAdditionalHeader = false;
return new AbstractMap.SimpleImmutableEntry<>(
ZipkinHttpSpanMapper.URI_HEADER,
HttpServletRequestTextMap.this.urlPathHelper
.getPathWithinApplication(
HttpServletRequestTextMap.this.delegate));
}
String name = headerNames.nextElement();
String value = HttpServletRequestTextMap.this.delegate.getHeader(name);
return new AbstractMap.SimpleEntry<>(name, value);
}
};
}
@Override
public void put(String key, String value) {
throw new UnsupportedOperationException("change servlet request isn't supported");
}
}

View File

@@ -1,14 +0,0 @@
package org.springframework.cloud.sleuth.instrument.web;
import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.cloud.sleuth.SpanTextMap;
/**
* Contract for extracting tracing headers from a {@link SpanTextMap}
* via HTTP headers
*
* @author Marcin Grzejszczak
* @since 1.2.0
*/
public interface HttpSpanExtractor extends SpanExtractor<SpanTextMap> {
}

View File

@@ -1,14 +0,0 @@
package org.springframework.cloud.sleuth.instrument.web;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.SpanTextMap;
/**
* Contract for injecting tracing headers from a {@link SpanTextMap}
* via HTTP headers
*
* @author Marcin Grzejszczak
* @since 1.2.0
*/
public interface HttpSpanInjector extends SpanInjector<SpanTextMap> {
}

View File

@@ -1,90 +0,0 @@
package org.springframework.cloud.sleuth.instrument.web;
import java.net.URI;
import java.util.Collection;
import java.util.Map;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.util.StringUtils;
/**
* Injects HTTP related keys to the current span.
*
* @author Marcin Grzejszczak
*
* @since 1.0.1
*/
public class HttpTraceKeysInjector {
private final Tracer tracer;
private final TraceKeys traceKeys;
public HttpTraceKeysInjector(Tracer tracer, TraceKeys traceKeys) {
this.tracer = tracer;
this.traceKeys = traceKeys;
}
/**
* Adds tags from the HTTP request to the current Span
*/
public void addRequestTags(String url, String host, String path, String method) {
this.tracer.addTag(this.traceKeys.getHttp().getUrl(), url);
this.tracer.addTag(this.traceKeys.getHttp().getHost(), host);
this.tracer.addTag(this.traceKeys.getHttp().getPath(), path);
this.tracer.addTag(this.traceKeys.getHttp().getMethod(), method);
}
/**
* Adds tags from the HTTP request to the given Span
*/
public void addRequestTags(Span span, String url, String host, String path, String method) {
tagSpan(span, this.traceKeys.getHttp().getUrl(), url);
tagSpan(span, this.traceKeys.getHttp().getHost(), host);
tagSpan(span, this.traceKeys.getHttp().getPath(), path);
tagSpan(span, this.traceKeys.getHttp().getMethod(), method);
}
/**
* Adds tags from the HTTP request to the given Span
*/
public void addRequestTags(Span span, URI uri, String method) {
addRequestTags(span, uri.toString(), uri.getHost(), uri.getPath(), method);
}
/**
* Adds tags from the HTTP request together with headers to the current Span
*/
public void addRequestTags(String url, String host, String path, String method,
Map<String, ? extends Collection<String>> headers) {
addRequestTags(url, host, path, method);
addRequestTagsFromHeaders(headers);
}
/**
* Add a tag to the given, exportable Span
*/
public void tagSpan(Span span, String key, String value) {
if (span != null && span.isExportable()) {
span.tag(key, value);
}
}
private void addRequestTagsFromHeaders(Map<String, ? extends Collection<String>> headers) {
for (String name : this.traceKeys.getHttp().getHeaders()) {
Collection<String> values = headers.get(name);
if (values != null) {
addTagForEntry(name, values);
}
}
}
private void addTagForEntry(String name, Collection<String> list) {
String key = this.traceKeys.getHttp().getPrefix() + name.toLowerCase();
String value = list.size() == 1 ? list.iterator().next()
: StringUtils.collectionToDelimitedString(list, ",", "'", "'");
this.tracer.addTag(key, value);
}
}

View File

@@ -1,41 +0,0 @@
package org.springframework.cloud.sleuth.instrument.web;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.springframework.cloud.sleuth.SpanTextMap;
import org.springframework.http.server.reactive.ServerHttpRequest;
/**
* Created by mgrzejszczak.
*/
class ServerHttpRequestTextMap implements SpanTextMap {
private final ServerHttpRequest delegate;
private final Map<String, String> additionalHeaders = new HashMap<>();
ServerHttpRequestTextMap(ServerHttpRequest delegate) {
this.delegate = delegate;
this.additionalHeaders.put(ZipkinHttpSpanMapper.URI_HEADER,
delegate.getPath().pathWithinApplication().value());
}
@Override
public Iterator<Map.Entry<String, String>> iterator() {
Map<String, String> map = new HashMap<>();
for (Map.Entry<String, List<String>> entry : this.delegate.getHeaders()
.entrySet()) {
map.put(entry.getKey(), entry.getValue() != null ?
entry.getValue().isEmpty() ? "" : entry.getValue().get(0) : "");
}
map.putAll(this.additionalHeaders);
return map.entrySet().iterator();
}
@Override
public void put(String key, String value) {
this.additionalHeaders.put(key, value);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.web;
import java.net.URI;
import brave.SpanCustomizer;
import brave.http.HttpAdapter;
import brave.http.HttpClientParser;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.util.SpanNameUtil;
/**
* An {@link HttpClientParser} that behaves like Sleuth in versions 1.x
*
* @author Marcin Grzejszczak
* @since 2.0.0
*/
class SleuthHttpClientParser extends HttpClientParser {
private final TraceKeys traceKeys;
public SleuthHttpClientParser(TraceKeys traceKeys) {
this.traceKeys = traceKeys;
}
@Override protected <Req> String spanName(HttpAdapter<Req, ?> adapter,
Req req) {
return getName(URI.create(adapter.url(req)));
}
@Override public <Req> void request(HttpAdapter<Req, ?> adapter, Req req,
SpanCustomizer customizer) {
super.request(adapter, req, customizer);
String url = adapter.url(req);
URI uri = URI.create(url);
addRequestTags(customizer, url, uri.getHost(), uri.getPath(), adapter.method(req));
this.traceKeys.getHttp().getHeaders()
.forEach(s -> {
String headerValue = adapter.requestHeader(req, s);
if (headerValue != null) {
customizer.tag(key(s), headerValue);
}
});
}
private String key(String key) {
return this.traceKeys.getHttp().getPrefix() + key.toLowerCase();
}
private String getName(URI uri) {
// The returned name should comply with RFC 882 - Section 3.1.2.
// i.e Header values must composed of printable ASCII values.
return SpanNameUtil.shorten(uriScheme(uri) + ":" + uri.getRawPath());
}
private String uriScheme(URI uri) {
return uri.getScheme() == null ? "http" : uri.getScheme();
}
private void addRequestTags(SpanCustomizer customizer, String url, String host,
String path, String method) {
customizer.tag(this.traceKeys.getHttp().getUrl(), url);
if (host != null) {
customizer.tag(this.traceKeys.getHttp().getHost(), host);
}
customizer.tag(this.traceKeys.getHttp().getPath(), path);
customizer.tag(this.traceKeys.getHttp().getMethod(), method);
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.web;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Sleuth HTTP settings
*
* @since 2.0.0
*/
@ConfigurationProperties("spring.sleuth.http")
public class SleuthHttpProperties {
private boolean enabled = true;
private Legacy legacy = new Legacy();
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Legacy getLegacy() {
return this.legacy;
}
public void setLegacy(Legacy legacy) {
this.legacy = legacy;
}
/**
* Legacy Sleuth support. Related to the way headers are parsed and tags are set
*/
public static class Legacy {
private boolean enabled = false;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
}

Some files were not shown because too many files have changed in this diff Show More