2160 lines
126 KiB
XML
2160 lines
126 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
||
<?asciidoc-toc maxdepth="8"?>
|
||
<?asciidoc-numbered?>
|
||
<book xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0" xml:lang="en">
|
||
<info>
|
||
<title>Spring Cloud Sleuth</title>
|
||
<date>2019-03-05</date>
|
||
<author>
|
||
<personname>
|
||
<firstname>Adrian Cole, Spencer Gibb, Marcin Grzejszczak, Dave Syer, Jay Bryant</firstname>
|
||
</personname>
|
||
</author>
|
||
<authorinitials>A</authorinitials>
|
||
</info>
|
||
<preface>
|
||
<title></title>
|
||
<simpara><emphasis role="strong">2.1.1.RELEASE</emphasis></simpara>
|
||
</preface>
|
||
<chapter xml:id="_introduction">
|
||
<title>Introduction</title>
|
||
<simpara>Spring Cloud Sleuth implements a distributed tracing solution for <link xl:href="http://cloud.spring.io">Spring Cloud</link>.</simpara>
|
||
<section xml:id="_terminology">
|
||
<title>Terminology</title>
|
||
<simpara>Spring Cloud Sleuth borrows <link xl:href="http://research.google.com/pubs/pub36356.html">Dapper’s</link> terminology.</simpara>
|
||
<simpara><emphasis role="strong">Span</emphasis>: The basic unit of work. For example, sending an RPC is a new span, as is sending a response to an RPC.
|
||
Spans are identified by a unique 64-bit ID for the span and another 64-bit ID for the trace the span is a part of.
|
||
Spans also have other data, such as descriptions, timestamped events, key-value annotations (tags), the ID of the span that caused them, and process IDs (normally IP addresses).</simpara>
|
||
<simpara>Spans can be started and stopped, and they keep track of their timing information.
|
||
Once you create a span, you must stop it at some point in the future.</simpara>
|
||
<tip>
|
||
<simpara>The initial span that starts a trace is called a <literal>root span</literal>. The value of the ID
|
||
of that span is equal to the trace ID.</simpara>
|
||
</tip>
|
||
<simpara><emphasis role="strong">Trace:</emphasis> A set of spans forming a tree-like structure.
|
||
For example, if you run a distributed big-data store, a trace might be formed by a <literal>PUT</literal> request.</simpara>
|
||
<simpara><emphasis role="strong">Annotation:</emphasis> Used to record the existence of an event in time. With
|
||
<link xl:href="https://github.com/openzipkin/brave">Brave</link> instrumentation, we no longer need to set special events
|
||
for <link xl:href="https://zipkin.io/">Zipkin</link> to understand who the client and server are, where
|
||
the request started, and where it ended. For learning purposes,
|
||
however, we mark these events to highlight what kind
|
||
of an action took place.</simpara>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara><emphasis role="strong">cs</emphasis>: Client Sent. The client has made a request. This annotation indicates the start of the span.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara><emphasis role="strong">sr</emphasis>: Server Received: The server side got the request and started processing it.
|
||
Subtracting the <literal>cs</literal> timestamp from this timestamp reveals the network latency.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara><emphasis role="strong">ss</emphasis>: Server Sent. Annotated upon completion of request processing (when the response got sent back to the client).
|
||
Subtracting the <literal>sr</literal> timestamp from this timestamp reveals the time needed by the server side to process the request.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara><emphasis role="strong">cr</emphasis>: Client Received. Signifies the end of the span.
|
||
The client has successfully received the response from the server side.
|
||
Subtracting the <literal>cs</literal> timestamp from this timestamp reveals the whole time needed by the client to receive the response from the server.</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
<simpara>The following image shows how <emphasis role="strong">Span</emphasis> and <emphasis role="strong">Trace</emphasis> look in a system, together with the Zipkin annotations:</simpara>
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/trace-id.png"/>
|
||
</imageobject>
|
||
<textobject><phrase>Trace Info propagation</phrase></textobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
<simpara>Each color of a note signifies a span (there are seven spans - from <emphasis role="strong">A</emphasis> to <emphasis role="strong">G</emphasis>).
|
||
Consider the following note:</simpara>
|
||
<screen>Trace Id = X
|
||
Span Id = D
|
||
Client Sent</screen>
|
||
<simpara>This note indicates that the current span has <emphasis role="strong">Trace Id</emphasis> set to <emphasis role="strong">X</emphasis> and <emphasis role="strong">Span Id</emphasis> set to <emphasis role="strong">D</emphasis>.
|
||
Also, the <literal>Client Sent</literal> event took place.</simpara>
|
||
<simpara>The following image shows how parent-child relationships of spans look:</simpara>
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/parents.png"/>
|
||
</imageobject>
|
||
<textobject><phrase>Parent child relationship</phrase></textobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
</section>
|
||
<section xml:id="_purpose">
|
||
<title>Purpose</title>
|
||
<simpara>The following sections refer to the example shown in the preceding image.</simpara>
|
||
<section xml:id="_distributed_tracing_with_zipkin">
|
||
<title>Distributed Tracing with Zipkin</title>
|
||
<simpara>This example has seven spans.
|
||
If you go to traces in Zipkin, you can see this number in the second trace, as shown in the following image:</simpara>
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/zipkin-traces.png"/>
|
||
</imageobject>
|
||
<textobject><phrase>Traces</phrase></textobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
<simpara>However, if you pick a particular trace, you can see four spans, as shown in the following image:</simpara>
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/zipkin-ui.png"/>
|
||
</imageobject>
|
||
<textobject><phrase>Traces Info propagation</phrase></textobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
<note>
|
||
<simpara>When you pick a particular trace, you see merged spans.
|
||
That means that, if there were two spans sent to Zipkin with Server Received and Server Sent or Client Received and Client Sent annotations, they are presented as a single span.</simpara>
|
||
</note>
|
||
<simpara>Why is there a difference between the seven and four spans in this case?</simpara>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara>One span comes from the <literal>http:/start</literal> span. It has the Server Received (<literal>sr</literal>) and Server Sent (<literal>ss</literal>) annotations.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Two spans come from the RPC call from <literal>service1</literal> to <literal>service2</literal> to the <literal>http:/foo</literal> endpoint.
|
||
The Client Sent (<literal>cs</literal>) and Client Received (<literal>cr</literal>) events took place on the <literal>service1</literal> side.
|
||
Server Received (<literal>sr</literal>) and Server Sent (<literal>ss</literal>) events took place on the <literal>service2</literal> side.
|
||
These two spans form one logical span related to an RPC call.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Two spans come from the RPC call from <literal>service2</literal> to <literal>service3</literal> to the <literal>http:/bar</literal> endpoint.
|
||
The Client Sent (<literal>cs</literal>) and Client Received (<literal>cr</literal>) events took place on the <literal>service2</literal> side.
|
||
The Server Received (<literal>sr</literal>) and Server Sent (<literal>ss</literal>) events took place on the <literal>service3</literal> side.
|
||
These two spans form one logical span related to an RPC call.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Two spans come from the RPC call from <literal>service2</literal> to <literal>service4</literal> to the <literal>http:/baz</literal> endpoint.
|
||
The Client Sent (<literal>cs</literal>) and Client Received (<literal>cr</literal>) events took place on the <literal>service2</literal> side.
|
||
Server Received (<literal>sr</literal>) and Server Sent (<literal>ss</literal>) events took place on the <literal>service4</literal> side.
|
||
These two spans form one logical span related to an RPC call.</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
<simpara>So, if we count the physical spans, we have one from <literal>http:/start</literal>, two from <literal>service1</literal> calling <literal>service2</literal>, two from <literal>service2</literal>
|
||
calling <literal>service3</literal>, and two from <literal>service2</literal> calling <literal>service4</literal>. In sum, we have a total of seven spans.</simpara>
|
||
<simpara>Logically, we see the information of four total Spans because we have one span related to the incoming request
|
||
to <literal>service1</literal> and three spans related to RPC calls.</simpara>
|
||
</section>
|
||
<section xml:id="_visualizing_errors">
|
||
<title>Visualizing errors</title>
|
||
<simpara>Zipkin lets you visualize errors in your trace.
|
||
When an exception was thrown and was not caught, we set proper tags on the span, which Zipkin can then properly colorize.
|
||
You could see in the list of traces one trace that is red. That appears because an exception was thrown.</simpara>
|
||
<simpara>If you click that trace, you see a similar picture, as follows:</simpara>
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/zipkin-error-traces.png"/>
|
||
</imageobject>
|
||
<textobject><phrase>Error Traces</phrase></textobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
<simpara>If you then click on one of the spans, you see the following</simpara>
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/zipkin-error-trace-screenshot.png"/>
|
||
</imageobject>
|
||
<textobject><phrase>Error Traces Info propagation</phrase></textobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
<simpara>The span shows the reason for the error and the whole stack trace related to it.</simpara>
|
||
</section>
|
||
<section xml:id="_distributed_tracing_with_brave">
|
||
<title>Distributed Tracing with Brave</title>
|
||
<simpara>Starting with version <literal>2.0.0</literal>, Spring Cloud Sleuth uses <link xl:href="https://github.com/openzipkin/brave">Brave</link> as the tracing library.
|
||
Consequently, Sleuth no longer takes care of storing the context but delegates that work to Brave.</simpara>
|
||
<simpara>Due to the fact that Sleuth had different naming and tagging conventions than Brave, we decided to follow Brave’s conventions from now on.
|
||
However, if you want to use the legacy Sleuth approaches, you can set the <literal>spring.sleuth.http.legacy.enabled</literal> property to <literal>true</literal>.</simpara>
|
||
</section>
|
||
<section xml:id="_live_examples">
|
||
<title>Live examples</title>
|
||
<figure>
|
||
<title>Click the Pivotal Web Services icon to see it live!</title>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/pws.png" contentwidth="150" contentdepth="74"/>
|
||
</imageobject>
|
||
<textobject><phrase>Zipkin deployed on Pivotal Web Services</phrase></textobject>
|
||
</mediaobject>
|
||
</figure>
|
||
<simpara><link xl:href="http://docssleuth-zipkin-server.cfapps.io/">Click here to see it live!</link></simpara>
|
||
<simpara>The dependency graph in Zipkin should resemble the following image:</simpara>
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/dependencies.png"/>
|
||
</imageobject>
|
||
<textobject><phrase>Dependencies</phrase></textobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
<figure>
|
||
<title>Click the Pivotal Web Services icon to see it live!</title>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/pws.png" contentwidth="150" contentdepth="74"/>
|
||
</imageobject>
|
||
<textobject><phrase>Zipkin deployed on Pivotal Web Services</phrase></textobject>
|
||
</mediaobject>
|
||
</figure>
|
||
<simpara><link xl:href="http://docssleuth-zipkin-server.cfapps.io/dependency">Click here to see it live!</link></simpara>
|
||
</section>
|
||
<section xml:id="_log_correlation">
|
||
<title>Log correlation</title>
|
||
<simpara>When using grep to read the logs of those four applications by scanning for a trace ID equal to (for example) <literal>2485ec27856c56f4</literal>, you get output resembling the following:</simpara>
|
||
<screen>service1.log:2016-02-26 11:15:47.561 INFO [service1,2485ec27856c56f4,2485ec27856c56f4,true] 68058 --- [nio-8081-exec-1] i.s.c.sleuth.docs.service1.Application : Hello from service1. Calling service2
|
||
service2.log:2016-02-26 11:15:47.710 INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application : Hello from service2. Calling service3 and then service4
|
||
service3.log:2016-02-26 11:15:47.895 INFO [service3,2485ec27856c56f4,1210be13194bfe5,true] 68060 --- [nio-8083-exec-1] i.s.c.sleuth.docs.service3.Application : Hello from service3
|
||
service2.log:2016-02-26 11:15:47.924 INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application : Got response from service3 [Hello from service3]
|
||
service4.log:2016-02-26 11:15:48.134 INFO [service4,2485ec27856c56f4,1b1845262ffba49d,true] 68061 --- [nio-8084-exec-1] i.s.c.sleuth.docs.service4.Application : Hello from service4
|
||
service2.log:2016-02-26 11:15:48.156 INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application : Got response from service4 [Hello from service4]
|
||
service1.log:2016-02-26 11:15:48.182 INFO [service1,2485ec27856c56f4,2485ec27856c56f4,true] 68058 --- [nio-8081-exec-1] i.s.c.sleuth.docs.service1.Application : Got response from service2 [Hello from service2, response from service3 [Hello from service3] and from service4 [Hello from service4]]</screen>
|
||
<simpara>If you use a log aggregating tool (such as <link xl:href="https://www.elastic.co/products/kibana">Kibana</link>, <link xl:href="http://www.splunk.com/">Splunk</link>, and others), you can order the events that took place.
|
||
An example from Kibana would resemble the following image:</simpara>
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/docs/src/main/asciidoc/images/kibana.png"/>
|
||
</imageobject>
|
||
<textobject><phrase>Log correlation with Kibana</phrase></textobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
<simpara>If you want to use <link xl:href="https://www.elastic.co/guide/en/logstash/current/index.html">Logstash</link>, the following listing shows the Grok pattern for Logstash:</simpara>
|
||
<screen>filter {
|
||
# pattern matching logback pattern
|
||
grok {
|
||
match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
|
||
}
|
||
}</screen>
|
||
<note>
|
||
<simpara>If you want to use Grok together with the logs from Cloud Foundry, you have to use the following pattern:</simpara>
|
||
</note>
|
||
<screen>filter {
|
||
# pattern matching logback pattern
|
||
grok {
|
||
match => { "message" => "(?m)OUT\s+%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
|
||
}
|
||
}</screen>
|
||
<section xml:id="_json_logback_with_logstash">
|
||
<title>JSON Logback with Logstash</title>
|
||
<simpara>Often, you do not want to store your logs in a text file but in a JSON file that Logstash can immediately pick.
|
||
To do so, you have to do the following (for readability, we pass the dependencies in the <literal>groupId:artifactId:version</literal> notation).</simpara>
|
||
<simpara><emphasis role="strong">Dependencies Setup</emphasis></simpara>
|
||
<orderedlist numeration="arabic">
|
||
<listitem>
|
||
<simpara>Ensure that Logback is on the classpath (<literal>ch.qos.logback:logback-core</literal>).</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Add Logstash Logback encode. For example, to use version <literal>4.6</literal>, add <literal>net.logstash.logback:logstash-logback-encoder:4.6</literal>.</simpara>
|
||
</listitem>
|
||
</orderedlist>
|
||
<simpara><emphasis role="strong">Logback Setup</emphasis></simpara>
|
||
<simpara>Consider the following example of a Logback configuration file (named <link xl:href="https://github.com/spring-cloud-samples/sleuth-documentation-apps/blob/master/service1/src/main/resources/logback-spring.xml">logback-spring.xml</link>).</simpara>
|
||
<programlisting language="xml" linenumbering="unnumbered"><?xml version="1.0" encoding="UTF-8"?>
|
||
<configuration>
|
||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||
|
||
<springProperty scope="context" name="springAppName" source="spring.application.name"/>
|
||
<!-- Example for logging into the build folder of your project -->
|
||
<property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}"/>
|
||
|
||
<!-- You can override this to have a custom pattern -->
|
||
<property name="CONSOLE_LOG_PATTERN"
|
||
value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
|
||
|
||
<!-- Appender to log to console -->
|
||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||
<!-- Minimum logging level to be presented in the console logs-->
|
||
<level>DEBUG</level>
|
||
</filter>
|
||
<encoder>
|
||
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||
<charset>utf8</charset>
|
||
</encoder>
|
||
</appender>
|
||
|
||
<!-- Appender to log to file -->
|
||
<appender name="flatfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||
<file>${LOG_FILE}</file>
|
||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
|
||
<maxHistory>7</maxHistory>
|
||
</rollingPolicy>
|
||
<encoder>
|
||
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||
<charset>utf8</charset>
|
||
</encoder>
|
||
</appender>
|
||
|
||
<!-- Appender to log to file in a JSON format -->
|
||
<appender name="logstash" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||
<file>${LOG_FILE}.json</file>
|
||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||
<fileNamePattern>${LOG_FILE}.json.%d{yyyy-MM-dd}.gz</fileNamePattern>
|
||
<maxHistory>7</maxHistory>
|
||
</rollingPolicy>
|
||
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
|
||
<providers>
|
||
<timestamp>
|
||
<timeZone>UTC</timeZone>
|
||
</timestamp>
|
||
<pattern>
|
||
<pattern>
|
||
{
|
||
"severity": "%level",
|
||
"service": "${springAppName:-}",
|
||
"trace": "%X{X-B3-TraceId:-}",
|
||
"span": "%X{X-B3-SpanId:-}",
|
||
"parent": "%X{X-B3-ParentSpanId:-}",
|
||
"exportable": "%X{X-Span-Export:-}",
|
||
"pid": "${PID:-}",
|
||
"thread": "%thread",
|
||
"class": "%logger{40}",
|
||
"rest": "%message"
|
||
}
|
||
</pattern>
|
||
</pattern>
|
||
</providers>
|
||
</encoder>
|
||
</appender>
|
||
|
||
<root level="INFO">
|
||
<appender-ref ref="console"/>
|
||
<!-- uncomment this to have also JSON logs -->
|
||
<!--<appender-ref ref="logstash"/>-->
|
||
<!--<appender-ref ref="flatfile"/>-->
|
||
</root>
|
||
</configuration></programlisting>
|
||
<simpara>That Logback configuration file:</simpara>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara>Logs information from the application in a JSON format to a <literal>build/${spring.application.name}.json</literal> file.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Has commented out two additional appenders: console and standard log file.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Has the same logging pattern as the one presented in the previous section.</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
<note>
|
||
<simpara>If you use a custom <literal>logback-spring.xml</literal>, you must pass the <literal>spring.application.name</literal> in the <literal>bootstrap</literal> rather than the <literal>application</literal> property file.
|
||
Otherwise, your custom logback file does not properly read the property.</simpara>
|
||
</note>
|
||
</section>
|
||
</section>
|
||
<section xml:id="_propagating_span_context">
|
||
<title>Propagating Span Context</title>
|
||
<simpara>The span context is the state that must get propagated to any child spans across process boundaries.
|
||
Part of the Span Context is the Baggage. The trace and span IDs are a required part of the span context.
|
||
Baggage is an optional part.</simpara>
|
||
<simpara>Baggage is a set of key:value pairs stored in the span context.
|
||
Baggage travels together with the trace and is attached to every span.
|
||
Spring Cloud Sleuth understands that a header is baggage-related if the HTTP header is prefixed with <literal>baggage-</literal> and, for messaging, it starts with <literal>baggage_</literal>.</simpara>
|
||
<important>
|
||
<simpara>There is currently no limitation of the count or size of baggage items.
|
||
However, keep in mind that too many can decrease system throughput or increase RPC latency.
|
||
In extreme cases, too much baggage can crash the application, due to exceeding transport-level message or header capacity.</simpara>
|
||
</important>
|
||
<simpara>The following example shows setting baggage on a span:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">Span initialSpan = this.tracer.nextSpan().name("span").start();
|
||
ExtraFieldPropagation.set(initialSpan.context(), "foo", "bar");
|
||
ExtraFieldPropagation.set(initialSpan.context(), "UPPER_CASE", "someValue");</programlisting>
|
||
<section xml:id="_baggage_versus_span_tags">
|
||
<title>Baggage versus Span Tags</title>
|
||
<simpara>Baggage travels with the trace (every child span contains the baggage of its parent).
|
||
Zipkin has no knowledge of baggage and does not receive that information.</simpara>
|
||
<important>
|
||
<simpara>Starting from Sleuth 2.0.0 you have to pass the baggage key names explicitly
|
||
in your project configuration. Read more about that setup <link linkend="prefixed-fields">here</link></simpara>
|
||
</important>
|
||
<simpara>Tags are attached to a specific span. In other words, they are presented only for that particular span.
|
||
However, you can search by tag to find the trace, assuming a span having the searched tag value exists.</simpara>
|
||
<simpara>If you want to be able to lookup a span based on baggage, you should add a corresponding entry as a tag in the root span.</simpara>
|
||
<important>
|
||
<simpara>The span must be in scope.</simpara>
|
||
</important>
|
||
<simpara>The following listing shows integration tests that use baggage:</simpara>
|
||
<formalpara>
|
||
<title>The setup</title>
|
||
<para>
|
||
<programlisting language="yml" linenumbering="unnumbered">spring.sleuth:
|
||
baggage-keys:
|
||
- baz
|
||
- bizarrecase
|
||
propagation-keys:
|
||
- foo
|
||
- upper_case</programlisting>
|
||
</para>
|
||
</formalpara>
|
||
<formalpara>
|
||
<title>The code</title>
|
||
<para>
|
||
<programlisting language="java" linenumbering="unnumbered">initialSpan.tag("foo",
|
||
ExtraFieldPropagation.get(initialSpan.context(), "foo"));
|
||
initialSpan.tag("UPPER_CASE",
|
||
ExtraFieldPropagation.get(initialSpan.context(), "UPPER_CASE"));</programlisting>
|
||
</para>
|
||
</formalpara>
|
||
</section>
|
||
</section>
|
||
</section>
|
||
<section xml:id="sleuth-adding-project">
|
||
<title>Adding Sleuth to the Project</title>
|
||
<simpara>This section addresses how to add Sleuth to your project with either Maven or Gradle.</simpara>
|
||
<important>
|
||
<simpara>To ensure that your application name is properly displayed in Zipkin, set the <literal>spring.application.name</literal> property in <literal>bootstrap.yml</literal>.</simpara>
|
||
</important>
|
||
<section xml:id="_only_sleuth_log_correlation">
|
||
<title>Only Sleuth (log correlation)</title>
|
||
<simpara>If you want to use only Spring Cloud Sleuth without the Zipkin integration, add the <literal>spring-cloud-starter-sleuth</literal> module to your project.</simpara>
|
||
<simpara>The following example shows how to add Sleuth with Maven:</simpara>
|
||
<formalpara role="primary">
|
||
<title>Maven</title>
|
||
<para>
|
||
<programlisting language="xml" linenumbering="unnumbered"><dependencyManagement> <co xml:id="CO1-1"/>
|
||
<dependencies>
|
||
<dependency>
|
||
<groupId>org.springframework.cloud</groupId>
|
||
<artifactId>spring-cloud-dependencies</artifactId>
|
||
<version>${release.train.version}</version>
|
||
<type>pom</type>
|
||
<scope>import</scope>
|
||
</dependency>
|
||
</dependencies>
|
||
</dependencyManagement>
|
||
|
||
<dependency> <co xml:id="CO1-2"/>
|
||
<groupId>org.springframework.cloud</groupId>
|
||
<artifactId>spring-cloud-starter-sleuth</artifactId>
|
||
</dependency></programlisting>
|
||
</para>
|
||
</formalpara>
|
||
<calloutlist>
|
||
<callout arearefs="CO1-1">
|
||
<para>We recommend that you add the dependency management through the Spring BOM so that you need not manage versions yourself.</para>
|
||
</callout>
|
||
<callout arearefs="CO1-2">
|
||
<para>Add the dependency to <literal>spring-cloud-starter-sleuth</literal>.</para>
|
||
</callout>
|
||
</calloutlist>
|
||
<simpara>The following example shows how to add Sleuth with Gradle:</simpara>
|
||
<formalpara role="secondary">
|
||
<title>Gradle</title>
|
||
<para>
|
||
<programlisting language="groovy" linenumbering="unnumbered">dependencyManagement { <co xml:id="CO2-1"/>
|
||
imports {
|
||
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
|
||
}
|
||
}
|
||
|
||
dependencies { <co xml:id="CO2-2"/>
|
||
compile "org.springframework.cloud:spring-cloud-starter-sleuth"
|
||
}</programlisting>
|
||
</para>
|
||
</formalpara>
|
||
<calloutlist>
|
||
<callout arearefs="CO2-1">
|
||
<para>We recommend that you add the dependency management through the Spring BOM so that you need not manage versions yourself.</para>
|
||
</callout>
|
||
<callout arearefs="CO2-2">
|
||
<para>Add the dependency to <literal>spring-cloud-starter-sleuth</literal>.</para>
|
||
</callout>
|
||
</calloutlist>
|
||
</section>
|
||
<section xml:id="_sleuth_with_zipkin_via_http">
|
||
<title>Sleuth with Zipkin via HTTP</title>
|
||
<simpara>If you want both Sleuth and Zipkin, add the <literal>spring-cloud-starter-zipkin</literal> dependency.</simpara>
|
||
<simpara>The following example shows how to do so for Maven:</simpara>
|
||
<formalpara role="primary">
|
||
<title>Maven</title>
|
||
<para>
|
||
<programlisting language="xml" linenumbering="unnumbered"><dependencyManagement> <co xml:id="CO3-1"/>
|
||
<dependencies>
|
||
<dependency>
|
||
<groupId>org.springframework.cloud</groupId>
|
||
<artifactId>spring-cloud-dependencies</artifactId>
|
||
<version>${release.train.version}</version>
|
||
<type>pom</type>
|
||
<scope>import</scope>
|
||
</dependency>
|
||
</dependencies>
|
||
</dependencyManagement>
|
||
|
||
<dependency> <co xml:id="CO3-2"/>
|
||
<groupId>org.springframework.cloud</groupId>
|
||
<artifactId>spring-cloud-starter-zipkin</artifactId>
|
||
</dependency></programlisting>
|
||
</para>
|
||
</formalpara>
|
||
<calloutlist>
|
||
<callout arearefs="CO3-1">
|
||
<para>We recommend that you add the dependency management through the Spring BOM so that you need not manage versions yourself.</para>
|
||
</callout>
|
||
<callout arearefs="CO3-2">
|
||
<para>Add the dependency to <literal>spring-cloud-starter-zipkin</literal>.</para>
|
||
</callout>
|
||
</calloutlist>
|
||
<simpara>The following example shows how to do so for Gradle:</simpara>
|
||
<formalpara role="secondary">
|
||
<title>Gradle</title>
|
||
<para>
|
||
<programlisting language="groovy" linenumbering="unnumbered">dependencyManagement { <co xml:id="CO4-1"/>
|
||
imports {
|
||
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
|
||
}
|
||
}
|
||
|
||
dependencies { <co xml:id="CO4-2"/>
|
||
compile "org.springframework.cloud:spring-cloud-starter-zipkin"
|
||
}</programlisting>
|
||
</para>
|
||
</formalpara>
|
||
<calloutlist>
|
||
<callout arearefs="CO4-1">
|
||
<para>We recommend that you add the dependency management through the Spring BOM so that you need not manage versions yourself.</para>
|
||
</callout>
|
||
<callout arearefs="CO4-2">
|
||
<para>Add the dependency to <literal>spring-cloud-starter-zipkin</literal>.</para>
|
||
</callout>
|
||
</calloutlist>
|
||
</section>
|
||
<section xml:id="_sleuth_with_zipkin_over_rabbitmq_or_kafka">
|
||
<title>Sleuth with Zipkin over RabbitMQ or Kafka</title>
|
||
<simpara>If you want to use RabbitMQ or Kafka instead of HTTP, add the <literal>spring-rabbit</literal> or <literal>spring-kafka</literal> dependency.
|
||
The default destination name is <literal>zipkin</literal>.</simpara>
|
||
<simpara>If using Kafka, you must set the property <literal>spring.zipkin.sender.type</literal> property accordingly:</simpara>
|
||
<programlisting language="yaml" linenumbering="unnumbered">spring.zipkin.sender.type: kafka</programlisting>
|
||
<caution>
|
||
<simpara><literal>spring-cloud-sleuth-stream</literal> is deprecated and incompatible with these destinations.</simpara>
|
||
</caution>
|
||
<simpara>If you want Sleuth over RabbitMQ, add the <literal>spring-cloud-starter-zipkin</literal> and <literal>spring-rabbit</literal>
|
||
dependencies.</simpara>
|
||
<simpara>The following example shows how to do so for Gradle:</simpara>
|
||
<formalpara role="primary">
|
||
<title>Maven</title>
|
||
<para>
|
||
<programlisting language="xml" linenumbering="unnumbered"><dependencyManagement> <co xml:id="CO5-1"/>
|
||
<dependencies>
|
||
<dependency>
|
||
<groupId>org.springframework.cloud</groupId>
|
||
<artifactId>spring-cloud-dependencies</artifactId>
|
||
<version>${release.train.version}</version>
|
||
<type>pom</type>
|
||
<scope>import</scope>
|
||
</dependency>
|
||
</dependencies>
|
||
</dependencyManagement>
|
||
|
||
<dependency> <co xml:id="CO5-2"/>
|
||
<groupId>org.springframework.cloud</groupId>
|
||
<artifactId>spring-cloud-starter-zipkin</artifactId>
|
||
</dependency>
|
||
<dependency> <co xml:id="CO5-3"/>
|
||
<groupId>org.springframework.amqp</groupId>
|
||
<artifactId>spring-rabbit</artifactId>
|
||
</dependency></programlisting>
|
||
</para>
|
||
</formalpara>
|
||
<calloutlist>
|
||
<callout arearefs="CO5-1">
|
||
<para>We recommend that you add the dependency management through the Spring BOM so that you need not manage versions yourself.</para>
|
||
</callout>
|
||
<callout arearefs="CO5-2">
|
||
<para>Add the dependency to <literal>spring-cloud-starter-zipkin</literal>. That way, all nested dependencies get downloaded.</para>
|
||
</callout>
|
||
<callout arearefs="CO5-3">
|
||
<para>To automatically configure RabbitMQ, add the <literal>spring-rabbit</literal> dependency.</para>
|
||
</callout>
|
||
</calloutlist>
|
||
<formalpara role="secondary">
|
||
<title>Gradle</title>
|
||
<para>
|
||
<programlisting language="groovy" linenumbering="unnumbered">dependencyManagement { <co xml:id="CO6-1"/>
|
||
imports {
|
||
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
|
||
}
|
||
}
|
||
|
||
dependencies {
|
||
compile "org.springframework.cloud:spring-cloud-starter-zipkin" <co xml:id="CO6-2"/>
|
||
compile "org.springframework.amqp:spring-rabbit" <co xml:id="CO6-3"/>
|
||
}</programlisting>
|
||
</para>
|
||
</formalpara>
|
||
<calloutlist>
|
||
<callout arearefs="CO6-1">
|
||
<para>We recommend that you add the dependency management through the Spring BOM so that you need not manage versions yourself.</para>
|
||
</callout>
|
||
<callout arearefs="CO6-2">
|
||
<para>Add the dependency to <literal>spring-cloud-starter-zipkin</literal>. That way, all nested dependencies get downloaded.</para>
|
||
</callout>
|
||
<callout arearefs="CO6-3">
|
||
<para>To automatically configure RabbitMQ, add the <literal>spring-rabbit</literal> dependency.</para>
|
||
</callout>
|
||
</calloutlist>
|
||
</section>
|
||
</section>
|
||
<section xml:id="_overriding_the_auto_configuration_of_zipkin">
|
||
<title>Overriding the auto-configuration of Zipkin</title>
|
||
<simpara>Spring Cloud Sleuth supports sending traces to multiple tracing systems as of version 2.1.0.
|
||
In order to get this to work, every tracing system needs to have a <literal>Reporter<Span></literal> and <literal>Sender</literal>.
|
||
If you want to override the provided beans you need to give them a specific name.
|
||
To do this you can use respectively <literal>ZipkinAutoConfiguration.REPORTER_BEAN_NAME</literal> and <literal>ZipkinAutoConfiguration.SENDER_BEAN_NAME</literal>.</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Configuration
|
||
protected static class MyConfig {
|
||
|
||
@Bean(ZipkinAutoConfiguration.REPORTER_BEAN_NAME)
|
||
Reporter<zipkin2.Span> myReporter() {
|
||
return AsyncReporter.create(mySender());
|
||
}
|
||
|
||
@Bean(ZipkinAutoConfiguration.SENDER_BEAN_NAME)
|
||
MySender mySender() {
|
||
return new MySender();
|
||
}
|
||
|
||
static class MySender extends Sender {
|
||
|
||
private boolean spanSent = false;
|
||
|
||
boolean isSpanSent() {
|
||
return this.spanSent;
|
||
}
|
||
|
||
@Override
|
||
public Encoding encoding() {
|
||
return Encoding.JSON;
|
||
}
|
||
|
||
@Override
|
||
public int messageMaxBytes() {
|
||
return Integer.MAX_VALUE;
|
||
}
|
||
|
||
@Override
|
||
public int messageSizeInBytes(List<byte[]> encodedSpans) {
|
||
return encoding().listSizeInBytes(encodedSpans);
|
||
}
|
||
|
||
@Override
|
||
public Call<Void> sendSpans(List<byte[]> encodedSpans) {
|
||
this.spanSent = true;
|
||
return Call.create(null);
|
||
}
|
||
|
||
}
|
||
|
||
}</programlisting>
|
||
</section>
|
||
</chapter>
|
||
<chapter xml:id="_additional_resources">
|
||
<title>Additional Resources</title>
|
||
<simpara>You can watch a video of <link xl:href="https://twitter.com/reshmi9k">Reshmi Krishna</link> and <link xl:href="https://twitter.com/mgrzejszczak">Marcin Grzejszczak</link> talking about Spring Cloud
|
||
Sleuth and Zipkin <link xl:href="https://content.pivotal.io/springone-platform-2017/distributed-tracing-latency-analysis-for-your-microservices-grzejszczak-krishna">by clicking here</link>.</simpara>
|
||
<simpara>You can check different setups of Sleuth and Brave <link xl:href="https://github.com/openzipkin/sleuth-webmvc-example">in the openzipkin/sleuth-webmvc-example repository</link>.</simpara>
|
||
</chapter>
|
||
<chapter xml:id="_features">
|
||
<title>Features</title>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara>Adds trace and span IDs to the Slf4J MDC, so you can extract all the logs from a given trace or span in a log aggregator, as shown in the following example logs:</simpara>
|
||
<screen>2016-02-02 15:30:57.902 INFO [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...
|
||
2016-02-02 15:30:58.372 ERROR [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...
|
||
2016-02-02 15:31:01.936 INFO [bar,46ab0d418373cbc9,46ab0d418373cbc9,false] 23030 --- [nio-8081-exec-4] ...</screen>
|
||
<simpara>Notice the <literal>[appname,traceId,spanId,exportable]</literal> entries from the MDC:</simpara>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara><emphasis role="strong"><literal>spanId</literal></emphasis>: The ID of a specific operation that took place.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara><emphasis role="strong"><literal>appname</literal></emphasis>: The name of the application that logged the span.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara><emphasis role="strong"><literal>traceId</literal></emphasis>: The ID of the latency graph that contains the span.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara><emphasis role="strong"><literal>exportable</literal></emphasis>: Whether the log should be exported to Zipkin.
|
||
When would you like the span not to be exportable?
|
||
When you want to wrap some operation in a Span and have it written to the logs only.</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Provides an abstraction over common distributed tracing data models: traces, spans (forming a DAG), annotations, and key-value annotations.
|
||
Spring Cloud Sleuth is loosely based on HTrace but is compatible with Zipkin (Dapper).</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Sleuth records timing information to aid in latency analysis.
|
||
By using sleuth, you can pinpoint causes of latency in your applications.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Sleuth is written to not log too much and to not cause your production application to crash.
|
||
To that end, Sleuth:</simpara>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara>Propagates structural data about your call graph in-band and the rest out-of-band.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Includes opinionated instrumentation of layers such as HTTP.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Includes a sampling policy to manage volume.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Can report to a Zipkin system for query and visualization.</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Instruments common ingress and egress points from Spring applications (servlet filter, async endpoints, rest template, scheduled actions, message channels, Zuul filters, and Feign client).</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Sleuth includes default logic to join a trace across HTTP or messaging boundaries.
|
||
For example, HTTP propagation works over Zipkin-compatible request headers.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Sleuth can propagate context (also known as baggage) between processes.
|
||
Consequently, if you set a baggage element on a Span, it is sent downstream to other processes over either HTTP or messaging.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Provides a way to create or continue spans and add tags and logs through annotations.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>If <literal>spring-cloud-sleuth-zipkin</literal> is on the classpath, the app generates and collects Zipkin-compatible traces.
|
||
By default, it sends them over HTTP to a Zipkin server on localhost (port 9411).
|
||
You can configure the location of the service by setting <literal>spring.zipkin.baseUrl</literal>.</simpara>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara>If you depend on <literal>spring-rabbit</literal>, your app sends traces to a RabbitMQ broker instead of HTTP.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>If you depend on <literal>spring-kafka</literal>, and set <literal>spring.zipkin.sender.type: kafka</literal>, your app sends traces to a Kafka broker instead of HTTP.</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</listitem>
|
||
</itemizedlist>
|
||
<caution>
|
||
<simpara><literal>spring-cloud-sleuth-stream</literal> is deprecated and should no longer be used.</simpara>
|
||
</caution>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara>Spring Cloud Sleuth is <link xl:href="http://opentracing.io/">OpenTracing</link> compatible.</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
<important>
|
||
<simpara>If you use Zipkin, configure the probability of spans exported by setting <literal>spring.sleuth.sampler.probability</literal>
|
||
(default: 0.1, which is 10 percent). Otherwise, you might think that Sleuth is not working be cause it omits some spans.</simpara>
|
||
</important>
|
||
<note>
|
||
<simpara>The SLF4J MDC is always set and logback users immediately see the trace and span IDs in logs per the example
|
||
shown earlier.
|
||
Other logging systems have to configure their own formatter to get the same result.
|
||
The default is as follows:
|
||
<literal>logging.pattern.level</literal> set to <literal>%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]</literal>
|
||
(this is a Spring Boot feature for logback users).
|
||
If you do not use SLF4J, this pattern is NOT automatically applied.</simpara>
|
||
</note>
|
||
<section xml:id="_introduction_to_brave">
|
||
<title>Introduction to Brave</title>
|
||
<important>
|
||
<simpara>Starting with version <literal>2.0.0</literal>, Spring Cloud Sleuth uses
|
||
<link xl:href="https://github.com/openzipkin/brave">Brave</link> as the tracing library.
|
||
For your convenience, we embed part of the Brave’s docs here.</simpara>
|
||
</important>
|
||
<important>
|
||
<simpara>In the vast majority of cases you need to just use the <literal>Tracer</literal>
|
||
or <literal>SpanCustomizer</literal> beans from Brave that Sleuth provides. The documentation below contains
|
||
a high overview of what Brave is and how it works.</simpara>
|
||
</important>
|
||
<simpara>Brave is a library used to capture and report latency information about distributed operations to Zipkin.
|
||
Most users do not use Brave directly. They use libraries or frameworks rather than employ Brave on their behalf.</simpara>
|
||
<simpara>This module includes a tracer that 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, with HTTP headers).</simpara>
|
||
<section xml:id="_tracing">
|
||
<title>Tracing</title>
|
||
<simpara>Most importantly, you need a <literal>brave.Tracer</literal>, configured to <link xl:href="https://github.com/openzipkin/zipkin-reporter-java">report to Zipkin</link>.</simpara>
|
||
<simpara>The following example setup sends trace data (spans) to Zipkin over HTTP (as opposed to Kafka):</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">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();
|
||
// ...
|
||
}
|
||
}</programlisting>
|
||
<important>
|
||
<simpara>If your span contains a name longer than 50 chars, then that name is truncated to 50 chars.
|
||
Your names have to be explicit and concrete.
|
||
Big names lead to latency issues and sometimes even thrown exceptions.</simpara>
|
||
</important>
|
||
<simpara>The tracer creates and joins spans that model the latency of potentially distributed work.
|
||
It can employ sampling to reduce overhead during the process, to reduce the amount of data sent to Zipkin, or both.</simpara>
|
||
<simpara>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.</simpara>
|
||
<simpara>Spans have a context that includes trace identifiers that place the span at the correct spot in the tree representing the distributed operation.</simpara>
|
||
</section>
|
||
<section xml:id="_local_tracing">
|
||
<title>Local Tracing</title>
|
||
<simpara>When tracing code that never leaves your process, run it inside a scoped span.</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Autowired Tracer tracer;
|
||
|
||
// Start a new trace or a span within an existing trace representing an operation
|
||
ScopedSpan span = tracer.startScopedSpan("encode");
|
||
try {
|
||
// The span is in "scope" meaning downstream code such as loggers can see trace IDs
|
||
return encoder.encode();
|
||
} catch (RuntimeException | Error e) {
|
||
span.error(e); // Unless you handle exceptions, you might not know the operation failed!
|
||
throw e;
|
||
} finally {
|
||
span.finish(); // always finish the span
|
||
}</programlisting>
|
||
<simpara>When you need more features, or finer control, use the <literal>Span</literal> type:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Autowired Tracer tracer;
|
||
|
||
// Start a new trace or a span within an existing trace representing an operation
|
||
Span span = tracer.nextSpan().name("encode").start();
|
||
// Put the span in "scope" so that downstream code such as loggers can see trace IDs
|
||
try (SpanInScope ws = tracer.withSpanInScope(span)) {
|
||
return encoder.encode();
|
||
} catch (RuntimeException | Error e) {
|
||
span.error(e); // Unless you handle exceptions, you might not know the operation failed!
|
||
throw e;
|
||
} finally {
|
||
span.finish(); // note the scope is independent of the span. Always finish a span.
|
||
}</programlisting>
|
||
<simpara>Both of the above examples report the exact same span on finish!</simpara>
|
||
<simpara>In the above example, the span will be either a new root span or the
|
||
next child in an existing trace.</simpara>
|
||
</section>
|
||
<section xml:id="_customizing_spans">
|
||
<title>Customizing Spans</title>
|
||
<simpara>Once you have a span, you can add tags to it.
|
||
The tags can be used as lookup keys or details.
|
||
For example, you might add a tag with your runtime version, as shown in the following example:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">span.tag("clnt/finagle.version", "6.36.0");</programlisting>
|
||
<simpara>When exposing the ability to customize spans to third parties, prefer <literal>brave.SpanCustomizer</literal> as opposed to <literal>brave.Span</literal>.
|
||
The former is simpler to understand and test and does not tempt users with span lifecycle hooks.</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">interface MyTraceCallback {
|
||
void request(Request request, SpanCustomizer customizer);
|
||
}</programlisting>
|
||
<simpara>Since <literal>brave.Span</literal> implements <literal>brave.SpanCustomizer</literal>, you can pass it to users, as shown in the following example:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">for (MyTraceCallback callback : userCallbacks) {
|
||
callback.request(request, span);
|
||
}</programlisting>
|
||
</section>
|
||
<section xml:id="_implicitly_looking_up_the_current_span">
|
||
<title>Implicitly Looking up the Current Span</title>
|
||
<simpara>Sometimes, you do not know if a trace is in progress or not, and you do not want users to do null checks.
|
||
<literal>brave.CurrentSpanCustomizer</literal> handles this problem by adding data to any span that’s in progress or drops, as shown in the following example:</simpara>
|
||
<simpara>Ex.</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">// The user code can then inject this without a chance of it being null.
|
||
@Autowired SpanCustomizer span;
|
||
|
||
void userCode() {
|
||
span.annotate("tx.started");
|
||
...
|
||
}</programlisting>
|
||
</section>
|
||
<section xml:id="_rpc_tracing">
|
||
<title>RPC tracing</title>
|
||
<tip>
|
||
<simpara>Check for <link xl:href="https://github.com/openzipkin/brave/tree/master/instrumentation">instrumentation written here</link> and <link xl:href="http://zipkin.io/pages/existing_instrumentations.html">Zipkin’s list</link> before rolling your own RPC instrumentation.</simpara>
|
||
</tip>
|
||
<simpara>RPC tracing is often done automatically by interceptors. Behind the scenes, they add tags and events that relate to their role in an RPC operation.</simpara>
|
||
<simpara>The following example shows how to add a client span:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Autowired Tracing tracing;
|
||
@Autowired Tracer tracer;
|
||
|
||
// before you send a request, add metadata that describes the operation
|
||
span = tracer.nextSpan().name(service + "/" + method).kind(CLIENT);
|
||
span.tag("myrpc.version", "1.0.0");
|
||
span.remoteServiceName("backend");
|
||
span.remoteIpAndPort("172.3.4.1", 8108);
|
||
|
||
// Add the trace context to the request, so it can be propagated in-band
|
||
tracing.propagation().injector(Request::addHeader)
|
||
.inject(span.context(), request);
|
||
|
||
// when the request is scheduled, start the span
|
||
span.start();
|
||
|
||
// if there is an error, tag the span
|
||
span.tag("error", error.getCode());
|
||
// or if there is an exception
|
||
span.error(exception);
|
||
|
||
// when the response is complete, finish the span
|
||
span.finish();</programlisting>
|
||
<section xml:id="_one_way_tracing">
|
||
<title>One-Way tracing</title>
|
||
<simpara>Sometimes, you need to model an asynchronous operation where there is a
|
||
request but no response. In normal RPC tracing, you use <literal>span.finish()</literal>
|
||
to indicate that the response was received. In one-way tracing, you use
|
||
<literal>span.flush()</literal> instead, as you do not expect a response.</simpara>
|
||
<simpara>The following example shows how a client might model a one-way operation:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Autowired Tracing tracing;
|
||
@Autowired Tracer tracer;
|
||
|
||
// start a new span representing a client request
|
||
oneWaySend = tracer.nextSpan().name(service + "/" + method).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();</programlisting>
|
||
<simpara>The following example shows how a server might handle a one-way operation:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Autowired Tracing tracing;
|
||
@Autowired Tracer tracer;
|
||
|
||
// 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();</programlisting>
|
||
</section>
|
||
</section>
|
||
</section>
|
||
</chapter>
|
||
<chapter xml:id="_sampling">
|
||
<title>Sampling</title>
|
||
<simpara>Sampling may be employed to reduce the data collected and reported out of process.
|
||
When a span is not sampled, it adds no overhead (a noop).</simpara>
|
||
<simpara>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.</simpara>
|
||
<simpara>By default, a global sampler applies a single rate to all traced operations.
|
||
<literal>Tracer.Builder.sampler</literal> controls this setting, and it defaults to tracing every request.</simpara>
|
||
<section xml:id="_declarative_sampling">
|
||
<title>Declarative sampling</title>
|
||
<simpara>Some applications need to sample based on the type or annotations of a java method.</simpara>
|
||
<simpara>Most users use a framework interceptor to automate this sort of policy.
|
||
The following example shows how that might work internally:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Autowired Tracer tracer;
|
||
|
||
// 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 {
|
||
// When there is no trace in progress, this decides using an annotation
|
||
Sampler decideUsingAnnotation = declarativeSampler.toSampler(traced);
|
||
Tracer tracer = tracer.withSampler(decideUsingAnnotation);
|
||
|
||
// This code looks the same as if there was no declarative override
|
||
ScopedSpan span = tracer.startScopedSpan(spanName(pjp));
|
||
try {
|
||
return pjp.proceed();
|
||
} catch (RuntimeException | Error e) {
|
||
span.error(e);
|
||
throw e;
|
||
} finally {
|
||
span.finish();
|
||
}
|
||
}</programlisting>
|
||
</section>
|
||
<section xml:id="_custom_sampling">
|
||
<title>Custom sampling</title>
|
||
<simpara>Depending on what the operation is, you may want to apply different policies.
|
||
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.</simpara>
|
||
<simpara>Most users use a framework interceptor to automate this sort of policy.
|
||
The following example shows how that might work internally:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Autowired Tracer tracer;
|
||
@Autowired Sampler fallback;
|
||
|
||
Span nextSpan(final Request input) {
|
||
Sampler requestBased = Sampler() {
|
||
@Override public boolean isSampled(long traceId) {
|
||
if (input.url().startsWith("/experimental")) {
|
||
return true;
|
||
} else if (input.url().startsWith("/static")) {
|
||
return false;
|
||
}
|
||
return fallback.isSampled(traceId);
|
||
}
|
||
};
|
||
return tracer.withSampler(requestBased).nextSpan();
|
||
}</programlisting>
|
||
</section>
|
||
<section xml:id="_sampling_in_spring_cloud_sleuth">
|
||
<title>Sampling in Spring Cloud Sleuth</title>
|
||
<simpara>By default Spring Cloud Sleuth sets all spans to non-exportable.
|
||
That means that traces appear in logs but not in any remote store.
|
||
For testing the default is often enough, and it probably is all you need if you use only the logs (for example, with an ELK aggregator).
|
||
If you export span data to Zipkin, there is also an <literal>Sampler.ALWAYS_SAMPLE</literal> setting that exports everything and a <literal>ProbabilityBasedSampler</literal> setting that samples a fixed fraction of spans.</simpara>
|
||
<note>
|
||
<simpara>The <literal>ProbabilityBasedSampler</literal> is the default if you use <literal>spring-cloud-sleuth-zipkin</literal>.
|
||
You can configure the exports by setting <literal>spring.sleuth.sampler.probability</literal>.
|
||
The passed value needs to be a double from <literal>0.0</literal> to <literal>1.0</literal>.</simpara>
|
||
</note>
|
||
<simpara>A sampler can be installed by creating a bean definition, as shown in the following example:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Bean
|
||
public Sampler defaultSampler() {
|
||
return Sampler.ALWAYS_SAMPLE;
|
||
}</programlisting>
|
||
<tip>
|
||
<simpara>You can set the HTTP header <literal>X-B3-Flags</literal> to <literal>1</literal>, or, when doing messaging, you can set the <literal>spanFlags</literal> header to <literal>1</literal>.
|
||
Doing so forces the current span to be exportable regardless of the sampling decision.</simpara>
|
||
</tip>
|
||
<simpara>In order to use the rate-limited sampler set the <literal>spring.sleuth.sampler.rate</literal> property to choose an amount of traces to accept on a per-second interval. The minimum number is 0 and the max is 2,147,483,647 (max int).</simpara>
|
||
</section>
|
||
</chapter>
|
||
<chapter xml:id="_propagation">
|
||
<title>Propagation</title>
|
||
<simpara>Propagation is needed to ensure activities 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 by sending an RPC request to a server receiving it.</simpara>
|
||
<simpara>For example, when a downstream HTTP call is made, its trace context is encoded as request headers and sent along with it, as shown in the following image:</simpara>
|
||
<screen> 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 │ │
|
||
│ └──────────────┘ │ └───────────────────┘ │ └──────────────┘ │
|
||
│ │ │ │
|
||
└──────────────────┘ └──────────────────┘</screen>
|
||
<simpara>The names above are from <link xl:href="https://github.com/openzipkin/b3-propagation">B3 Propagation</link>, which is built-in to Brave and has implementations in many languages and frameworks.</simpara>
|
||
<simpara>Most users use a framework interceptor to automate propagation.
|
||
The next two examples show how that might work for a client and a server.</simpara>
|
||
<simpara>The following example shows how client-side propagation might work:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Autowired Tracing tracing;
|
||
|
||
// 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);</programlisting>
|
||
<simpara>The following example shows how server-side propagation might work:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Autowired Tracing tracing;
|
||
@Autowired Tracer tracer;
|
||
|
||
// configure a function that extracts the trace context from a request
|
||
extractor = tracing.propagation().extractor(Request::getHeader);
|
||
|
||
// when a server receives a request, it joins or starts a new trace
|
||
span = tracer.nextSpan(extractor.extract(request));</programlisting>
|
||
<section xml:id="_propagating_extra_fields">
|
||
<title>Propagating extra fields</title>
|
||
<simpara>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, as shown in the following example:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">// when you initialize the builder, define the extra field you want to propagate
|
||
Tracing.newBuilder().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");</programlisting>
|
||
<simpara>You may also need to propagate a trace context that you are not using.
|
||
For example, you may be in an Amazon Web Services environment but not be reporting data to X-Ray.
|
||
To ensure X-Ray can co-exist correctly, pass-through its tracing header, as shown in the following example:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">tracingBuilder.propagationFactory(
|
||
ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "x-amzn-trace-id")
|
||
);</programlisting>
|
||
<tip>
|
||
<simpara>In Spring Cloud Sleuth all elements of the tracing builder <literal>Tracing.newBuilder()</literal>
|
||
are defined as beans. So if you want to pass a custom <literal>PropagationFactory</literal>, it’s enough
|
||
for you to create a bean of that type and we will set it in the <literal>Tracing</literal> bean.</simpara>
|
||
</tip>
|
||
<section xml:id="prefixed-fields">
|
||
<title>Prefixed fields</title>
|
||
<simpara>If they follow a common pattern, you can also prefix fields.
|
||
The following example shows how to propagate <literal>x-vcap-request-id</literal> the field as-is but send the <literal>country-code</literal> and <literal>user-id</literal> fields on the wire as <literal>x-baggage-country-code</literal> and <literal>x-baggage-user-id</literal>, respectively:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">Tracing.newBuilder().propagationFactory(
|
||
ExtraFieldPropagation.newFactoryBuilder(B3Propagation.FACTORY)
|
||
.addField("x-vcap-request-id")
|
||
.addPrefixedFields("x-baggage-", Arrays.asList("country-code", "user-id"))
|
||
.build()
|
||
);</programlisting>
|
||
<simpara>Later, you can call the following code to affect the country code of the current trace context:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">ExtraFieldPropagation.set("x-country-code", "FO");
|
||
String countryCode = ExtraFieldPropagation.get("x-country-code");</programlisting>
|
||
<simpara>Alternatively, if you have a reference to a trace context, you can use it explicitly, as shown in the following example:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">ExtraFieldPropagation.set(span.context(), "x-country-code", "FO");
|
||
String countryCode = ExtraFieldPropagation.get(span.context(), "x-country-code");</programlisting>
|
||
<important>
|
||
<simpara>A difference from previous versions of Sleuth is that, with Brave, you must pass the list of baggage keys.
|
||
There are two properties to achieve this.
|
||
With the <literal>spring.sleuth.baggage-keys</literal>, you set keys that get prefixed with <literal>baggage-</literal> for HTTP calls and <literal>baggage_</literal> for messaging.
|
||
You can also use the <literal>spring.sleuth.propagation-keys</literal> property to pass a list of prefixed keys that are whitelisted without any prefix.
|
||
Notice that there’s no <literal>x-</literal> in front of the header keys.</simpara>
|
||
</important>
|
||
<simpara>In order to automatically set the baggage values to Slf4j’s MDC, you have to set
|
||
the <literal>spring.sleuth.log.slf4j.whitelisted-mdc-keys</literal> property with a list of whitelisted
|
||
baggage and propagation keys. E.g. <literal>spring.sleuth.log.slf4j.whitelisted-mdc-keys=foo</literal> will set the value of the <literal>foo</literal> baggage into MDC.</simpara>
|
||
<important>
|
||
<simpara>Remember that adding entries to MDC can drastically decrease the performance of your application!</simpara>
|
||
</important>
|
||
<simpara>If you want to add the baggage entries as tags, to make it possible to search for spans via the baggage entries, you can set the value of
|
||
<literal>spring.sleuth.propagation.tag.whitelisted-keys</literal> with a list of whitelisted baggage keys. To disable the feature you have to pass the <literal>spring.sleuth.propagation.tag.enabled=false</literal> property.</simpara>
|
||
</section>
|
||
<section xml:id="_extracting_a_propagated_context">
|
||
<title>Extracting a Propagated Context</title>
|
||
<simpara>The <literal>TraceContext.Extractor<C></literal> reads trace identifiers and sampling status from an incoming request or message.
|
||
The carrier is usually a request object or headers.</simpara>
|
||
<simpara>This utility is used in standard instrumentation (such as <literal>HttpServerHandler</literal>) but can also be used for custom RPC or messaging code.</simpara>
|
||
<simpara><literal>TraceContextOrSamplingFlags</literal> is usually used only with <literal>Tracer.nextSpan(extracted)</literal>, unless you are
|
||
sharing span IDs between a client and a server.</simpara>
|
||
</section>
|
||
<section xml:id="_sharing_span_ids_between_client_and_server">
|
||
<title>Sharing span IDs between Client and Server</title>
|
||
<simpara>A normal instrumentation pattern is to create a span representing the server side of an RPC.
|
||
<literal>Extractor.extract</literal> might return a complete trace context when applied to an incoming client request.
|
||
<literal>Tracer.joinSpan</literal> attempts to continue this trace, using the same span ID if supported or creating a child span
|
||
if not. When the span ID is shared, the reported data includes a flag saying so.</simpara>
|
||
<simpara>The following image shows an example of B3 propagation:</simpara>
|
||
<screen> ┌───────────────────┐ ┌───────────────────┐
|
||
Incoming Headers │ TraceContext │ │ TraceContext │
|
||
┌───────────────────┐(extract)│ ┌───────────────┐ │(join)│ ┌───────────────┐ │
|
||
│ X─B3-TraceId │─────────┼─┼> TraceId │ │──────┼─┼> TraceId │ │
|
||
│ │ │ │ │ │ │ │ │ │
|
||
│ X─B3-ParentSpanId │─────────┼─┼> ParentSpanId │ │──────┼─┼> ParentSpanId │ │
|
||
│ │ │ │ │ │ │ │ │ │
|
||
│ X─B3-SpanId │─────────┼─┼> SpanId │ │──────┼─┼> SpanId │ │
|
||
└───────────────────┘ │ │ │ │ │ │ │ │
|
||
│ │ │ │ │ │ Shared: true │ │
|
||
│ └───────────────┘ │ │ └───────────────┘ │
|
||
└───────────────────┘ └───────────────────┘</screen>
|
||
<simpara>Some propagation systems forward only the parent span ID, detected when <literal>Propagation.Factory.supportsJoin() == false</literal>.
|
||
In this case, a new span ID is always provisioned, and the incoming context determines the parent ID.</simpara>
|
||
<simpara>The following image shows an example of AWS propagation:</simpara>
|
||
<screen> ┌───────────────────┐ ┌───────────────────┐
|
||
x-amzn-trace-id │ TraceContext │ │ TraceContext │
|
||
┌───────────────────┐(extract)│ ┌───────────────┐ │(join)│ ┌───────────────┐ │
|
||
│ Root │─────────┼─┼> TraceId │ │──────┼─┼> TraceId │ │
|
||
│ │ │ │ │ │ │ │ │ │
|
||
│ Parent │─────────┼─┼> SpanId │ │──────┼─┼> ParentSpanId │ │
|
||
└───────────────────┘ │ └───────────────┘ │ │ │ │ │
|
||
└───────────────────┘ │ │ SpanId: New │ │
|
||
│ └───────────────┘ │
|
||
└───────────────────┘</screen>
|
||
<simpara>Note: Some span reporters do not support sharing span IDs.
|
||
For example, if you set <literal>Tracing.Builder.spanReporter(amazonXrayOrGoogleStackdrive)</literal>, you should disable join by setting <literal>Tracing.Builder.supportsJoin(false)</literal>.
|
||
Doing so forces a new child span on <literal>Tracer.joinSpan()</literal>.</simpara>
|
||
</section>
|
||
<section xml:id="_implementing_propagation">
|
||
<title>Implementing Propagation</title>
|
||
<simpara><literal>TraceContext.Extractor<C></literal> is implemented by a <literal>Propagation.Factory</literal> plugin.
|
||
Internally, this code creates the union type, <literal>TraceContextOrSamplingFlags</literal>, with one of the following:
|
||
* <literal>TraceContext</literal> if trace and span IDs were present.
|
||
* <literal>TraceIdContext</literal> if a trace ID was present but span IDs were not present.
|
||
* <literal>SamplingFlags</literal> if no identifiers were present.</simpara>
|
||
<simpara>Some <literal>Propagation</literal> implementations carry extra data from the point of extraction (for example, reading incoming headers) to injection (for example, writing outgoing headers).
|
||
For example, it might carry a request ID.
|
||
When implementations have extra data, they handle it as follows:
|
||
* If a <literal>TraceContext</literal> were extracted, add the extra data as <literal>TraceContext.extra()</literal>.
|
||
* Otherwise, add it as <literal>TraceContextOrSamplingFlags.extra()</literal>, which <literal>Tracer.nextSpan</literal> handles.</simpara>
|
||
</section>
|
||
</section>
|
||
</chapter>
|
||
<chapter xml:id="_current_tracing_component">
|
||
<title>Current Tracing Component</title>
|
||
<simpara>Brave supports a "<literal>current tracing component</literal>" concept, which should only be used when you have no other way to get a reference.
|
||
This was made for JDBC connections, as they often initialize prior to the tracing component.</simpara>
|
||
<simpara>The most recent tracing component instantiated is available through <literal>Tracing.current()</literal>.
|
||
You can also use <literal>Tracing.currentTracer()</literal> to get only the tracer.
|
||
If you use either of these methods, do not cache the result.
|
||
Instead, look them up each time you need them.</simpara>
|
||
</chapter>
|
||
<chapter xml:id="_current_span">
|
||
<title>Current Span</title>
|
||
<simpara>Brave supports a "<literal>current span</literal>" concept which represents the in-flight operation.
|
||
You can use <literal>Tracer.currentSpan()</literal> to add custom tags to a span and <literal>Tracer.nextSpan()</literal> to create a child of whatever is in-flight.</simpara>
|
||
<important>
|
||
<simpara>In Sleuth, you can autowire the <literal>Tracer</literal> bean to retrieve the current span via
|
||
<literal>tracer.currentSpan()</literal> method. To retrieve the current context just call
|
||
<literal>tracer.currentSpan().context()</literal>. To get the current trace id as String
|
||
you can use the <literal>traceIdString()</literal> method like this: <literal>tracer.currentSpan().context().traceIdString()</literal>.</simpara>
|
||
</important>
|
||
<section xml:id="_setting_a_span_in_scope_manually">
|
||
<title>Setting a span in scope manually</title>
|
||
<simpara>When writing new instrumentation, it is important to place a span you created in scope as the current span.
|
||
Not only does doing so let users access it with <literal>Tracer.currentSpan()</literal>, but it also allows customizations such as SLF4J MDC to see the current trace IDs.</simpara>
|
||
<simpara><literal>Tracer.withSpanInScope(Span)</literal> facilitates this and is most conveniently employed by using the try-with-resources idiom.
|
||
Whenever external code might be invoked (such as proceeding an interceptor or otherwise), place the span in scope, as shown in the following example:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Autowired Tracer tracer;
|
||
|
||
try (SpanInScope ws = tracer.withSpanInScope(span)) {
|
||
return inboundRequest.invoke();
|
||
} finally { // note the scope is independent of the span
|
||
span.finish();
|
||
}</programlisting>
|
||
<simpara>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 tso, pass null to <literal>withSpanInScope</literal>, as shown in the following example:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Autowired Tracer tracer;
|
||
|
||
try (SpanInScope cleared = tracer.withSpanInScope(null)) {
|
||
startBackgroundThread();
|
||
}</programlisting>
|
||
</section>
|
||
</chapter>
|
||
<chapter xml:id="_instrumentation">
|
||
<title>Instrumentation</title>
|
||
<simpara>Spring Cloud Sleuth automatically instruments all your Spring applications, so you should not have to do anything to activate it.
|
||
The instrumentation is added by using a variety of technologies according to the stack that is available. For example, for a servlet web application, we use a <literal>Filter</literal>, and, for Spring Integration, we use <literal>ChannelInterceptors</literal>.</simpara>
|
||
<simpara>You can customize the keys used in span tags.
|
||
To limit the volume of span data, an HTTP request is, by default, tagged only with a handful of metadata, such as the status code, the host, and the URL.
|
||
You can add request headers by configuring <literal>spring.sleuth.keys.http.headers</literal> (a list of header names).</simpara>
|
||
<note>
|
||
<simpara>Tags are collected and exported only if there is a <literal>Sampler</literal> that allows it. By default, there is no such <literal>Sampler</literal>, to ensure that there is no danger of accidentally collecting too much data without configuring something).</simpara>
|
||
</note>
|
||
</chapter>
|
||
<chapter xml:id="_span_lifecycle">
|
||
<title>Span lifecycle</title>
|
||
<simpara>You can do the following operations on the Span by means of <literal>brave.Tracer</literal>:</simpara>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara><link linkend="creating-and-finishing-spans">start</link>: When you start a span, its name is assigned and the start timestamp is recorded.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara><link linkend="creating-and-finishing-spans">close</link>: The span gets finished (the end time of the span is recorded) and, if the span is sampled, it is eligible for collection (for example, to Zipkin).</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara><link linkend="continuing-spans">continue</link>: A new instance of span is created.
|
||
It is a copy of the one that it continues.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara><link linkend="continuing-spans">detach</link>: The span does not get stopped or closed.
|
||
It only gets removed from the current thread.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara><link linkend="creating-spans-with-explicit-parent">create with explicit parent</link>: You can create a new span and set an explicit parent for it.</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
<tip>
|
||
<simpara>Spring Cloud Sleuth creates an instance of <literal>Tracer</literal> for you. In order to use it, you can autowire it.</simpara>
|
||
</tip>
|
||
<section xml:id="creating-and-finishing-spans">
|
||
<title>Creating and finishing spans</title>
|
||
<simpara>You can manually create spans by using the <literal>Tracer</literal>, as shown in the following example:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">// Start a span. If there was a span present in this thread it will become
|
||
// the `newSpan`'s parent.
|
||
Span newSpan = this.tracer.nextSpan().name("calculateTax");
|
||
try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(newSpan.start())) {
|
||
// ...
|
||
// You can tag a span
|
||
newSpan.tag("taxValue", taxValue);
|
||
// ...
|
||
// You can log an event on a span
|
||
newSpan.annotate("taxCalculated");
|
||
}
|
||
finally {
|
||
// Once done remember to finish the span. This will allow collecting
|
||
// the span to send it to Zipkin
|
||
newSpan.finish();
|
||
}</programlisting>
|
||
<simpara>In the preceding example, we could see how to create a new instance of the span.
|
||
If there is already a span in this thread, it becomes the parent of the new span.</simpara>
|
||
<important>
|
||
<simpara>Always clean after you create a span. Also, always finish any span that you want to send to Zipkin.</simpara>
|
||
</important>
|
||
<important>
|
||
<simpara>If your span contains a name greater than 50 chars, that name is truncated to 50 chars.
|
||
Your names have to be explicit and concrete. Big names lead to latency issues and sometimes even exceptions.</simpara>
|
||
</important>
|
||
</section>
|
||
<section xml:id="continuing-spans">
|
||
<title>Continuing Spans</title>
|
||
<simpara>Sometimes, you do not want to create a new span but you want to continue one. An example of such a
|
||
situation might be as follows:</simpara>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara><emphasis role="strong">AOP</emphasis>: If there was already a span created before an aspect was reached, you might not want to create a new span.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara><emphasis role="strong">Hystrix</emphasis>: Executing a Hystrix command is most likely a logical part of the current processing.
|
||
It is in fact merely a technical implementation detail that you would not necessarily want to reflect in tracing as a separate being.</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
<simpara>To continue a span, you can use <literal>brave.Tracer</literal>, as shown in the following example:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">// let's assume that we're in a thread Y and we've received
|
||
// the `initialSpan` from thread X
|
||
Span continuedSpan = this.tracer.toSpan(newSpan.context());
|
||
try {
|
||
// ...
|
||
// You can tag a span
|
||
continuedSpan.tag("taxValue", taxValue);
|
||
// ...
|
||
// You can log an event on a span
|
||
continuedSpan.annotate("taxCalculated");
|
||
}
|
||
finally {
|
||
// Once done remember to flush the span. That means that
|
||
// it will get reported but the span itself is not yet finished
|
||
continuedSpan.flush();
|
||
}</programlisting>
|
||
</section>
|
||
<section xml:id="creating-spans-with-explicit-parent">
|
||
<title>Creating a Span with an explicit Parent</title>
|
||
<simpara>You might want to start a new span and provide an explicit parent of that span.
|
||
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 <literal>nextSpan()</literal>, it creates a span in reference to the span that is currently in scope.
|
||
You can put the span in scope and then call <literal>nextSpan()</literal>, as shown in the following example:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">// let's assume that we're in a thread Y and we've received
|
||
// the `initialSpan` from thread X. `initialSpan` will be the parent
|
||
// of the `newSpan`
|
||
Span newSpan = null;
|
||
try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(initialSpan)) {
|
||
newSpan = this.tracer.nextSpan().name("calculateCommission");
|
||
// ...
|
||
// You can tag a span
|
||
newSpan.tag("commissionValue", commissionValue);
|
||
// ...
|
||
// You can log an event on a span
|
||
newSpan.annotate("commissionCalculated");
|
||
}
|
||
finally {
|
||
// Once done remember to finish the span. This will allow collecting
|
||
// the span to send it to Zipkin. The tags and events set on the
|
||
// newSpan will not be present on the parent
|
||
if (newSpan != null) {
|
||
newSpan.finish();
|
||
}
|
||
}</programlisting>
|
||
<important>
|
||
<simpara>After creating such a span, you must finish it. Otherwise it is not reported (for example, to Zipkin).</simpara>
|
||
</important>
|
||
</section>
|
||
</chapter>
|
||
<chapter xml:id="_naming_spans">
|
||
<title>Naming spans</title>
|
||
<simpara>Picking a span name is not a trivial task. A span name should depict an operation name.
|
||
The name should be low cardinality, so it should not include identifiers.</simpara>
|
||
<simpara>Since there is a lot of instrumentation going on, some span names are artificial:</simpara>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara><literal>controller-method-name</literal> when received by a Controller with a method name of <literal>controllerMethodName</literal></simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara><literal>async</literal> for asynchronous operations done with wrapped <literal>Callable</literal> and <literal>Runnable</literal> interfaces.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Methods annotated with <literal>@Scheduled</literal> return the simple name of the class.</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
<simpara>Fortunately, for asynchronous processing, you can provide explicit naming.</simpara>
|
||
<section xml:id="_spanname_annotation">
|
||
<title><literal>@SpanName</literal> Annotation</title>
|
||
<simpara>You can name the span explicitly by using the <literal>@SpanName</literal> annotation, as shown in the following example:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered"> @SpanName("calculateTax")
|
||
class TaxCountingRunnable implements Runnable {
|
||
|
||
@Override
|
||
public void run() {
|
||
// perform logic
|
||
}
|
||
|
||
}
|
||
|
||
}</programlisting>
|
||
<simpara>In this case, when processed in the following manner, the span is named <literal>calculateTax</literal>:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">Runnable runnable = new TraceRunnable(this.tracing, spanNamer,
|
||
new TaxCountingRunnable());
|
||
Future<?> future = executorService.submit(runnable);
|
||
// ... some additional logic ...
|
||
future.get();</programlisting>
|
||
</section>
|
||
<section xml:id="_tostring_method">
|
||
<title><literal>toString()</literal> method</title>
|
||
<simpara>It is pretty rare to create separate classes for <literal>Runnable</literal> or <literal>Callable</literal>.
|
||
Typically, one creates an anonymous instance of those classes.
|
||
You cannot annotate such classes.
|
||
To overcome that limitation, if there is no <literal>@SpanName</literal> annotation present, we check whether the class has a custom implementation of the <literal>toString()</literal> method.</simpara>
|
||
<simpara>Running such code leads to creating a span named <literal>calculateTax</literal>, as shown in the following example:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">Runnable runnable = new TraceRunnable(this.tracing, spanNamer, new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
// perform logic
|
||
}
|
||
|
||
@Override
|
||
public String toString() {
|
||
return "calculateTax";
|
||
}
|
||
});
|
||
Future<?> future = executorService.submit(runnable);
|
||
// ... some additional logic ...
|
||
future.get();</programlisting>
|
||
</section>
|
||
</chapter>
|
||
<chapter xml:id="_managing_spans_with_annotations">
|
||
<title>Managing Spans with Annotations</title>
|
||
<simpara>You can manage spans with a variety of annotations.</simpara>
|
||
<section xml:id="_rationale">
|
||
<title>Rationale</title>
|
||
<simpara>There are a number of good reasons to manage spans with annotations, including:</simpara>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara>API-agnostic means to collaborate with a span. Use of annotations lets users add to a span with no library dependency on a span api.
|
||
Doing so lets Sleuth change its core API to create less impact to user code.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Reduced surface area for basic span operations. Without this feature, you must use the span api, which has lifecycle commands that could be used incorrectly.
|
||
By only exposing scope, tag, and log functionality, you can collaborate without accidentally breaking span lifecycle.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>Collaboration with runtime generated code. With libraries such as Spring Data and Feign, the implementations of interfaces are generated at runtime.
|
||
Consequently, span wrapping of objects was tedious.
|
||
Now you can provide annotations over interfaces and the arguments of those interfaces.</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</section>
|
||
<section xml:id="_creating_new_spans">
|
||
<title>Creating New Spans</title>
|
||
<simpara>If you do not want to create local spans manually, you can use the <literal>@NewSpan</literal> annotation.
|
||
Also, we provide the <literal>@SpanTag</literal> annotation to add tags in an automated fashion.</simpara>
|
||
<simpara>Now we can consider some examples of usage.</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@NewSpan
|
||
void testMethod();</programlisting>
|
||
<simpara>Annotating the method without any parameter leads to creating a new span whose name equals the annotated method name.</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@NewSpan("customNameOnTestMethod4")
|
||
void testMethod4();</programlisting>
|
||
<simpara>If you provide the value in the annotation (either directly or by setting the <literal>name</literal> parameter), the created span has the provided value as the name.</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">// method declaration
|
||
@NewSpan(name = "customNameOnTestMethod5")
|
||
void testMethod5(@SpanTag("testTag") String param);
|
||
|
||
// and method execution
|
||
this.testBean.testMethod5("test");</programlisting>
|
||
<simpara>You can combine both the name and a tag. Let’s focus on the latter.
|
||
In this case, the value of the annotated method’s parameter runtime value becomes the value of the tag.
|
||
In our sample, the tag key is <literal>testTag</literal>, and the tag value is <literal>test</literal>.</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@NewSpan(name = "customNameOnTestMethod3")
|
||
@Override
|
||
public void testMethod3() {
|
||
}</programlisting>
|
||
<simpara>You can place the <literal>@NewSpan</literal> annotation on both the class and an interface.
|
||
If you override the interface’s method and provide a different value for the <literal>@NewSpan</literal> annotation, the most
|
||
concrete one wins (in this case <literal>customNameOnTestMethod3</literal> is set).</simpara>
|
||
</section>
|
||
<section xml:id="_continuing_spans">
|
||
<title>Continuing Spans</title>
|
||
<simpara>If you want to add tags and annotations to an existing span, you can use the <literal>@ContinueSpan</literal> annotation, as shown in the following example:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">// method declaration
|
||
@ContinueSpan(log = "testMethod11")
|
||
void testMethod11(@SpanTag("testTag11") String param);
|
||
|
||
// method execution
|
||
this.testBean.testMethod11("test");
|
||
this.testBean.testMethod13();</programlisting>
|
||
<simpara>(Note that, in contrast with the <literal>@NewSpan</literal> annotation ,you can also add logs with the <literal>log</literal> parameter.)</simpara>
|
||
<simpara>That way, the span gets continued and:</simpara>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara>Log entries named <literal>testMethod11.before</literal> and <literal>testMethod11.after</literal> are created.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>If an exception is thrown, a log entry named <literal>testMethod11.afterFailure</literal> is also created.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>A tag with a key of <literal>testTag11</literal> and a value of <literal>test</literal> is created.</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</section>
|
||
<section xml:id="_advanced_tag_setting">
|
||
<title>Advanced Tag Setting</title>
|
||
<simpara>There are 3 different ways to add tags to a span. All of them are controlled by the <literal>SpanTag</literal> annotation.
|
||
The precedence is as follows:</simpara>
|
||
<orderedlist numeration="arabic">
|
||
<listitem>
|
||
<simpara>Try with a bean of <literal>TagValueResolver</literal> type and a provided name.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>If the bean name has not been provided, try to evaluate an expression.
|
||
We search for a <literal>TagValueExpressionResolver</literal> bean.
|
||
The default implementation uses SPEL expression resolution.
|
||
<emphasis role="strong">IMPORTANT</emphasis> You can only reference properties from the SPEL expression. Method execution is not allowed due to security constraints.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>If we do not find any expression to evaluate, return the <literal>toString()</literal> value of the parameter.</simpara>
|
||
</listitem>
|
||
</orderedlist>
|
||
<section xml:id="_custom_extractor">
|
||
<title>Custom extractor</title>
|
||
<simpara>The value of the tag for the following method is computed by an implementation of <literal>TagValueResolver</literal> interface.
|
||
Its class name has to be passed as the value of the <literal>resolver</literal> attribute.</simpara>
|
||
<simpara>Consider the following annotated method:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@NewSpan
|
||
public void getAnnotationForTagValueResolver(
|
||
@SpanTag(key = "test", resolver = TagValueResolver.class) String test) {
|
||
}</programlisting>
|
||
<simpara>Now further consider the following <literal>TagValueResolver</literal> bean implementation:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Bean(name = "myCustomTagValueResolver")
|
||
public TagValueResolver tagValueResolver() {
|
||
return parameter -> "Value from myCustomTagValueResolver";
|
||
}</programlisting>
|
||
<simpara>The two preceding examples lead to setting a tag value equal to <literal>Value from myCustomTagValueResolver</literal>.</simpara>
|
||
</section>
|
||
<section xml:id="_resolving_expressions_for_a_value">
|
||
<title>Resolving Expressions for a Value</title>
|
||
<simpara>Consider the following annotated method:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@NewSpan
|
||
public void getAnnotationForTagValueExpression(
|
||
@SpanTag(key = "test", expression = "'hello' + ' characters'") String test) {
|
||
}</programlisting>
|
||
<simpara>No custom implementation of a <literal>TagValueExpressionResolver</literal> leads to evaluation of the SPEL expression, and a tag with a value of <literal>4 characters</literal> is set on the span.
|
||
If you want to use some other expression resolution mechanism, you can create your own implementation of the bean.</simpara>
|
||
</section>
|
||
<section xml:id="_using_the_tostring_method">
|
||
<title>Using the <literal>toString()</literal> method</title>
|
||
<simpara>Consider the following annotated method:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@NewSpan
|
||
public void getAnnotationForArgumentToString(@SpanTag("test") Long param) {
|
||
}</programlisting>
|
||
<simpara>Running the preceding method with a value of <literal>15</literal> leads to setting a tag with a String value of <literal>"15"</literal>.</simpara>
|
||
</section>
|
||
</section>
|
||
</chapter>
|
||
<chapter xml:id="_customizations">
|
||
<title>Customizations</title>
|
||
<section xml:id="_http">
|
||
<title>HTTP</title>
|
||
<simpara>If a customization of client / server parsing of the HTTP related spans is required,
|
||
just register a bean of type <literal>brave.http.HttpClientParser</literal> or
|
||
<literal>brave.http.HttpServerParser</literal>. If client /server sampling is required, just
|
||
register a bean of type <literal>brave.http.HttpSampler</literal> and name the bean
|
||
<literal>sleuthClientSampler</literal> for client sampler and <literal>sleuthServerSampler</literal> for server sampler.
|
||
For your convenience the <literal>@ClientSampler</literal> and <literal>@ServerSampler</literal>
|
||
annotations can be used to inject the proper beans or to
|
||
reference the bean names via their static String <literal>NAME</literal> fields.</simpara>
|
||
<simpara>Check out Brave’s code to see an example of how to make a path-based sampler
|
||
<link xl:href="https://github.com/openzipkin/brave/tree/master/instrumentation/http#sampling-policy">https://github.com/openzipkin/brave/tree/master/instrumentation/http#sampling-policy</link></simpara>
|
||
<simpara>If you want to completely rewrite the <literal>HttpTracing</literal> bean you can use the <literal>SkipPatternProvider</literal>
|
||
interface to retrieve the URL <literal>Pattern</literal> for spans that should be not sampled. Below you can see
|
||
an example of usage of <literal>SkipPatternProvider</literal> inside a server side, <literal>HttpSampler</literal>.</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Configuration
|
||
class Config {
|
||
@Bean(name = ServerSampler.NAME)
|
||
HttpSampler myHttpSampler(SkipPatternProvider provider) {
|
||
Pattern pattern = provider.skipPattern();
|
||
return new HttpSampler() {
|
||
|
||
@Override
|
||
public <Req> Boolean trySample(HttpAdapter<Req, ?> adapter, Req request) {
|
||
String url = adapter.path(request);
|
||
boolean shouldSkip = pattern.matcher(url).matches();
|
||
if (shouldSkip) {
|
||
return false;
|
||
}
|
||
return null;
|
||
}
|
||
};
|
||
}
|
||
}</programlisting>
|
||
</section>
|
||
<section xml:id="_tracingfilter">
|
||
<title><literal>TracingFilter</literal></title>
|
||
<simpara>You can also modify the behavior of the <literal>TracingFilter</literal>, which is the component that is responsible for processing the input HTTP request and adding tags basing on the HTTP response.
|
||
You can customize the tags or modify the response headers by registering your own instance of the <literal>TracingFilter</literal> bean.</simpara>
|
||
<simpara>In the following example, we register the <literal>TracingFilter</literal> bean, add the <literal>ZIPKIN-TRACE-ID</literal> response header containing the current Span’s trace id, and add a tag with key <literal>custom</literal> and a value <literal>tag</literal> to the span.</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Component
|
||
@Order(TraceWebServletAutoConfiguration.TRACING_FILTER_ORDER + 1)
|
||
class MyFilter extends GenericFilterBean {
|
||
|
||
private final Tracer tracer;
|
||
|
||
MyFilter(Tracer tracer) {
|
||
this.tracer = tracer;
|
||
}
|
||
|
||
@Override
|
||
public void doFilter(ServletRequest request, ServletResponse response,
|
||
FilterChain chain) throws IOException, ServletException {
|
||
Span currentSpan = this.tracer.currentSpan();
|
||
if (currentSpan == null) {
|
||
chain.doFilter(request, response);
|
||
return;
|
||
}
|
||
// for readability we're returning trace id in a hex form
|
||
((HttpServletResponse) response).addHeader("ZIPKIN-TRACE-ID",
|
||
currentSpan.context().traceIdString());
|
||
// we can also add some custom tags
|
||
currentSpan.tag("custom", "tag");
|
||
chain.doFilter(request, response);
|
||
}
|
||
|
||
}</programlisting>
|
||
</section>
|
||
<section xml:id="_custom_service_name">
|
||
<title>Custom service name</title>
|
||
<simpara>By default, Sleuth assumes that, when you send a span to Zipkin, you want the span’s service name to be equal to the value of the <literal>spring.application.name</literal> property.
|
||
That is not always the case, though.
|
||
There are situations in which you want to explicitly provide a different service name for all spans coming from your application.
|
||
To achieve that, you can pass the following property to your application to override that value (the example is for a service named <literal>myService</literal>):</simpara>
|
||
<programlisting language="yaml" linenumbering="unnumbered">spring.zipkin.service.name: myService</programlisting>
|
||
</section>
|
||
<section xml:id="_customization_of_reported_spans">
|
||
<title>Customization of Reported Spans</title>
|
||
<simpara>Before reporting spans (for example, to Zipkin) you may want to modify that span in some way.
|
||
You can do so by using the <literal>FinishedSpanHandler</literal> interface.</simpara>
|
||
<simpara>In Sleuth, we generate spans with a fixed name.
|
||
Some users want to modify the name depending on values of tags.
|
||
You can implement the <literal>FinishedSpanHandler</literal> interface to alter that name.</simpara>
|
||
<simpara>The following example shows how to register two beans that implement <literal>FinishedSpanHandler</literal>:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Bean
|
||
FinishedSpanHandler handlerOne() {
|
||
return new FinishedSpanHandler() {
|
||
@Override
|
||
public boolean handle(TraceContext traceContext, MutableSpan span) {
|
||
span.name("foo");
|
||
return true; // keep this span
|
||
}
|
||
};
|
||
}
|
||
|
||
@Bean
|
||
FinishedSpanHandler handlerTwo() {
|
||
return new FinishedSpanHandler() {
|
||
@Override
|
||
public boolean handle(TraceContext traceContext, MutableSpan span) {
|
||
span.name(span.name() + " bar");
|
||
return true; // keep this span
|
||
}
|
||
};
|
||
}</programlisting>
|
||
<simpara>The preceding example results in changing the name of the reported span to <literal>foo bar</literal>, just before it gets reported (for example, to Zipkin).</simpara>
|
||
</section>
|
||
<section xml:id="_host_locator">
|
||
<title>Host Locator</title>
|
||
<important>
|
||
<simpara>This section is about defining <emphasis role="strong">host</emphasis> from service discovery.
|
||
It is <emphasis role="strong">NOT</emphasis> about finding Zipkin through service discovery.</simpara>
|
||
</important>
|
||
<simpara>To define the host that corresponds to a particular span, we need to resolve the host name and port.
|
||
The default approach is to take these values from server properties.
|
||
If those are not set, we try to retrieve the host name from the network interfaces.</simpara>
|
||
<simpara>If you have the discovery client enabled and prefer to retrieve the host address from the registered instance in a service registry, you have to set the <literal>spring.zipkin.locator.discovery.enabled</literal> property (it is applicable for both HTTP-based and Stream-based span reporting), as follows:</simpara>
|
||
<programlisting language="yaml" linenumbering="unnumbered">spring.zipkin.locator.discovery.enabled: true</programlisting>
|
||
</section>
|
||
</chapter>
|
||
<chapter xml:id="_sending_spans_to_zipkin">
|
||
<title>Sending Spans to Zipkin</title>
|
||
<simpara>By default, if you add <literal>spring-cloud-starter-zipkin</literal> as a dependency to your project, when the span is closed, it is sent to Zipkin over HTTP.
|
||
The communication is asynchronous.
|
||
You can configure the URL by setting the <literal>spring.zipkin.baseUrl</literal> property, as follows:</simpara>
|
||
<programlisting language="yaml" linenumbering="unnumbered">spring.zipkin.baseUrl: http://192.168.99.100:9411/</programlisting>
|
||
<simpara>If you want to find Zipkin through service discovery, you can pass the Zipkin’s service ID inside the URL, as shown in the following example for <literal>zipkinserver</literal> service ID:</simpara>
|
||
<programlisting language="yaml" linenumbering="unnumbered">spring.zipkin.baseUrl: http://zipkinserver/</programlisting>
|
||
<simpara>To disable this feature just set <literal>spring.zipkin.discoveryClientEnabled</literal> to `false.</simpara>
|
||
<simpara>When the Discovery Client feature is enabled, Sleuth uses
|
||
<literal>LoadBalancerClient</literal> to find the URL of the Zipkin Server. It means
|
||
that you can set up the load balancing configuration e.g. via Ribbon.</simpara>
|
||
<programlisting language="yaml" linenumbering="unnumbered">zipkinserver:
|
||
ribbon:
|
||
ListOfServers: host1,host2</programlisting>
|
||
<simpara>If you have web, rabbit, or kafka together on the classpath, you might need to pick the means by which you would like to send spans to zipkin.
|
||
To do so, set <literal>web</literal>, <literal>rabbit</literal>, or <literal>kafka</literal> to the <literal>spring.zipkin.sender.type</literal> property.
|
||
The following example shows setting the sender type for <literal>web</literal>:</simpara>
|
||
<programlisting language="yaml" linenumbering="unnumbered">spring.zipkin.sender.type: web</programlisting>
|
||
<simpara>To customize the <literal>RestTemplate</literal> that sends spans to Zipkin via HTTP, you can register
|
||
the <literal>ZipkinRestTemplateCustomizer</literal> bean.</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Configuration
|
||
class MyConfig {
|
||
@Bean ZipkinRestTemplateCustomizer myCustomizer() {
|
||
return new ZipkinRestTemplateCustomizer() {
|
||
@Override
|
||
void customize(RestTemplate restTemplate) {
|
||
// customize the RestTemplate
|
||
}
|
||
};
|
||
}
|
||
}</programlisting>
|
||
<simpara>If, however, you would like to control the full process of creating the <literal>RestTemplate</literal>
|
||
object, you will have to create a bean of <literal>zipkin2.reporter.Sender</literal> type.</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered"> @Bean Sender myRestTemplateSender(ZipkinProperties zipkin,
|
||
ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer) {
|
||
RestTemplate restTemplate = mySuperCustomRestTemplate();
|
||
zipkinRestTemplateCustomizer.customize(restTemplate);
|
||
return myCustomSender(zipkin, restTemplate);
|
||
}</programlisting>
|
||
</chapter>
|
||
<chapter xml:id="_zipkin_stream_span_consumer">
|
||
<title>Zipkin Stream Span Consumer</title>
|
||
<important>
|
||
<simpara>We recommend using Zipkin’s native support for message-based span sending.
|
||
Starting from the Edgware release, the Zipkin Stream server is deprecated.
|
||
In the Finchley release, it got removed.</simpara>
|
||
</important>
|
||
<simpara>If for some reason you need to create the deprecated Stream Zipkin server, see the <link xl:href="http://cloud.spring.io/spring-cloud-static/Dalston.SR4/multi/multi__span_data_as_messages.html#_zipkin_consumer">Dalston Documentation</link>.</simpara>
|
||
</chapter>
|
||
<chapter xml:id="_integrations">
|
||
<title>Integrations</title>
|
||
<section xml:id="_opentracing">
|
||
<title>OpenTracing</title>
|
||
<simpara>Spring Cloud Sleuth is compatible with <link xl:href="http://opentracing.io/">OpenTracing</link>.
|
||
If you have OpenTracing on the classpath, we automatically register the OpenTracing <literal>Tracer</literal> bean.
|
||
If you wish to disable this, set <literal>spring.sleuth.opentracing.enabled</literal> to <literal>false</literal></simpara>
|
||
</section>
|
||
<section xml:id="_runnable_and_callable">
|
||
<title>Runnable and Callable</title>
|
||
<simpara>If you wrap your logic in <literal>Runnable</literal> or <literal>Callable</literal>, you can wrap those classes in their Sleuth representative, as shown in the following example for <literal>Runnable</literal>:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">Runnable runnable = new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
// do some work
|
||
}
|
||
|
||
@Override
|
||
public String toString() {
|
||
return "spanNameFromToStringMethod";
|
||
}
|
||
};
|
||
// Manual `TraceRunnable` creation with explicit "calculateTax" Span name
|
||
Runnable traceRunnable = new TraceRunnable(this.tracing, spanNamer, runnable,
|
||
"calculateTax");
|
||
// Wrapping `Runnable` with `Tracing`. That way the current span will be available
|
||
// in the thread of `Runnable`
|
||
Runnable traceRunnableFromTracer = this.tracing.currentTraceContext()
|
||
.wrap(runnable);</programlisting>
|
||
<simpara>The following example shows how to do so for <literal>Callable</literal>:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">Callable<String> callable = new Callable<String>() {
|
||
@Override
|
||
public String call() throws Exception {
|
||
return someLogic();
|
||
}
|
||
|
||
@Override
|
||
public String toString() {
|
||
return "spanNameFromToStringMethod";
|
||
}
|
||
};
|
||
// Manual `TraceCallable` creation with explicit "calculateTax" Span name
|
||
Callable<String> traceCallable = new TraceCallable<>(this.tracing, spanNamer,
|
||
callable, "calculateTax");
|
||
// Wrapping `Callable` with `Tracing`. That way the current span will be available
|
||
// in the thread of `Callable`
|
||
Callable<String> traceCallableFromTracer = this.tracing.currentTraceContext()
|
||
.wrap(callable);</programlisting>
|
||
<simpara>That way, you ensure that a new span is created and closed for each execution.</simpara>
|
||
</section>
|
||
<section xml:id="_hystrix">
|
||
<title>Hystrix</title>
|
||
<section xml:id="_custom_concurrency_strategy">
|
||
<title>Custom Concurrency Strategy</title>
|
||
<simpara>We register a custom <link xl:href="https://github.com/Netflix/Hystrix/wiki/Plugins#concurrencystrategy"><literal>HystrixConcurrencyStrategy</literal></link> called <literal>TraceCallable</literal> that wraps all <literal>Callable</literal> instances in their Sleuth representative.
|
||
The strategy either starts or continues a span, depending on whether tracing was already going on before the Hystrix command was called.
|
||
To disable the custom Hystrix Concurrency Strategy, set the <literal>spring.sleuth.hystrix.strategy.enabled</literal> to <literal>false</literal>.</simpara>
|
||
</section>
|
||
<section xml:id="_manual_command_setting">
|
||
<title>Manual Command setting</title>
|
||
<simpara>Assume that you have the following <literal>HystrixCommand</literal>:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">HystrixCommand<String> hystrixCommand = new HystrixCommand<String>(setter) {
|
||
@Override
|
||
protected String run() throws Exception {
|
||
return someLogic();
|
||
}
|
||
};</programlisting>
|
||
<simpara>To pass the tracing information, you have to wrap the same logic in the Sleuth version of the <literal>HystrixCommand</literal>, which is called
|
||
<literal>TraceCommand</literal>, as shown in the following example:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">TraceCommand<String> traceCommand = new TraceCommand<String>(tracer, setter) {
|
||
@Override
|
||
public String doRun() throws Exception {
|
||
return someLogic();
|
||
}
|
||
};</programlisting>
|
||
</section>
|
||
</section>
|
||
<section xml:id="_rxjava">
|
||
<title>RxJava</title>
|
||
<simpara>We registering a custom <link xl:href="https://github.com/ReactiveX/RxJava/wiki/Plugins#rxjavaschedulershook"><literal>RxJavaSchedulersHook</literal></link> that wraps all <literal>Action0</literal> instances in their Sleuth representative, which is called <literal>TraceAction</literal>.
|
||
The hook either starts or continues a span, depending on whether tracing was already going on before the Action was scheduled.
|
||
To disable the custom <literal>RxJavaSchedulersHook</literal>, set the <literal>spring.sleuth.rxjava.schedulers.hook.enabled</literal> to <literal>false</literal>.</simpara>
|
||
<simpara>You can define a list of regular expressions for thread names for which you do not want spans to be created.
|
||
To do so, provide a comma-separated list of regular expressions in the <literal>spring.sleuth.rxjava.schedulers.ignoredthreads</literal> property.</simpara>
|
||
<important>
|
||
<simpara>The suggest approach to reactive programming and Sleuth is to use
|
||
the Reactor support.</simpara>
|
||
</important>
|
||
</section>
|
||
<section xml:id="_http_integration">
|
||
<title>HTTP integration</title>
|
||
<simpara>Features from this section can be disabled by setting the <literal>spring.sleuth.web.enabled</literal> property with value equal to <literal>false</literal>.</simpara>
|
||
<section xml:id="_http_filter">
|
||
<title>HTTP Filter</title>
|
||
<simpara>Through the <literal>TracingFilter</literal>, all sampled incoming requests result in creation of a Span.
|
||
That Span’s name is <literal>http:</literal> + the path to which the request was sent.
|
||
For example, if the request was sent to <literal>/this/that</literal> then the name will be <literal>http:/this/that</literal>.
|
||
You can configure which URIs you would like to skip by setting the <literal>spring.sleuth.web.skipPattern</literal> property.
|
||
If you have <literal>ManagementServerProperties</literal> on classpath, its value of <literal>contextPath</literal> gets appended to the provided skip pattern.
|
||
If you want to reuse the Sleuth’s default skip patterns and just append your own, pass those patterns by using the <literal>spring.sleuth.web.additionalSkipPattern</literal>.</simpara>
|
||
<simpara>By default, all the spring boot actuator endpoints are automatically added to the skip pattern.
|
||
If you want to disable this behaviour set <literal>spring.sleuth.web.ignore-auto-configured-skip-patterns</literal>
|
||
to <literal>true</literal>.</simpara>
|
||
<simpara>To change the order of tracing filter registration, please set the
|
||
<literal>spring.sleuth.web.filter-order</literal> property.</simpara>
|
||
<simpara>To disable the filter that logs uncaught exceptions you can disable the
|
||
<literal>spring.sleuth.web.exception-throwing-filter-enabled</literal> property.</simpara>
|
||
</section>
|
||
<section xml:id="_handlerinterceptor">
|
||
<title>HandlerInterceptor</title>
|
||
<simpara>Since we want the span names to be precise, we use a <literal>TraceHandlerInterceptor</literal> that either wraps an existing <literal>HandlerInterceptor</literal> or is added directly to the list of existing <literal>HandlerInterceptors</literal>.
|
||
The <literal>TraceHandlerInterceptor</literal> adds a special request attribute to the given <literal>HttpServletRequest</literal>.
|
||
If the the <literal>TracingFilter</literal> does not see this attribute, it creates a "<literal>fallback</literal>" span, which is an additional span created on the server side so that the trace is presented properly in the UI.
|
||
If that happens, there is probably missing instrumentation.
|
||
In that case, please file an issue in Spring Cloud Sleuth.</simpara>
|
||
</section>
|
||
<section xml:id="_async_servlet_support">
|
||
<title>Async Servlet support</title>
|
||
<simpara>If your controller returns a <literal>Callable</literal> or a <literal>WebAsyncTask</literal>, Spring Cloud Sleuth continues the existing span instead of creating a new one.</simpara>
|
||
</section>
|
||
<section xml:id="_webflux_support">
|
||
<title>WebFlux support</title>
|
||
<simpara>Through <literal>TraceWebFilter</literal>, all sampled incoming requests result in creation of a Span.
|
||
That Span’s name is <literal>http:</literal> + the path to which the request was sent.
|
||
For example, if the request was sent to <literal>/this/that</literal>, the name is <literal>http:/this/that</literal>.
|
||
You can configure which URIs you would like to skip by using the <literal>spring.sleuth.web.skipPattern</literal> property.
|
||
If you have <literal>ManagementServerProperties</literal> on the classpath, its value of <literal>contextPath</literal> gets appended to the provided skip pattern.
|
||
If you want to reuse Sleuth’s default skip patterns and append your own, pass those patterns by using the <literal>spring.sleuth.web.additionalSkipPattern</literal>.</simpara>
|
||
<simpara>To change the order of tracing filter registration, please set the
|
||
<literal>spring.sleuth.web.filter-order</literal> property.</simpara>
|
||
</section>
|
||
<section xml:id="_dubbo_rpc_support">
|
||
<title>Dubbo RPC support</title>
|
||
<simpara>Via the integration with Brave, Spring Cloud Sleuth supports <link xl:href="http://dubbo.io/">Dubbo</link>.
|
||
It’s enough to add the <literal>brave-instrumentation-dubbo-rpc</literal> dependency:</simpara>
|
||
<programlisting language="xml" linenumbering="unnumbered"><dependency>
|
||
<groupId>io.zipkin.brave</groupId>
|
||
<artifactId>brave-instrumentation-dubbo-rpc</artifactId>
|
||
</dependency></programlisting>
|
||
<simpara>You need to also set a <literal>dubbo.properties</literal> file with the following contents:</simpara>
|
||
<programlisting language="properties" linenumbering="unnumbered">dubbo.provider.filter=tracing
|
||
dubbo.consumer.filter=tracing</programlisting>
|
||
<simpara>You can read more about Brave - Dubbo integration <link xl:href="https://github.com/openzipkin/brave/tree/master/instrumentation/dubbo-rpc">here</link>.
|
||
An example of Spring Cloud Sleuth and Dubbo can be found <link xl:href="https://github.com/openzipkin/sleuth-webmvc-example/compare/add-dubbo-tracing">here</link>.</simpara>
|
||
</section>
|
||
</section>
|
||
<section xml:id="_http_client_integration">
|
||
<title>HTTP Client Integration</title>
|
||
<section xml:id="_synchronous_rest_template">
|
||
<title>Synchronous Rest Template</title>
|
||
<simpara>We inject a <literal>RestTemplate</literal> interceptor to ensure that all the tracing information is passed to the requests.
|
||
Each time a call is made, a new Span is created.
|
||
It gets closed upon receiving the response.
|
||
To block the synchronous <literal>RestTemplate</literal> features, set <literal>spring.sleuth.web.client.enabled</literal> to <literal>false</literal>.</simpara>
|
||
<important>
|
||
<simpara>You have to register <literal>RestTemplate</literal> as a bean so that the interceptors get injected.
|
||
If you create a <literal>RestTemplate</literal> instance with a <literal>new</literal> keyword, the instrumentation does NOT work.</simpara>
|
||
</important>
|
||
</section>
|
||
<section xml:id="_asynchronous_rest_template">
|
||
<title>Asynchronous Rest Template</title>
|
||
<important>
|
||
<simpara>Starting with Sleuth <literal>2.0.0</literal>, we no longer register a bean of <literal>AsyncRestTemplate</literal> type.
|
||
It is up to you to create such a bean.
|
||
Then we instrument it.</simpara>
|
||
</important>
|
||
<simpara>To block the <literal>AsyncRestTemplate</literal> features, set <literal>spring.sleuth.web.async.client.enabled</literal> to <literal>false</literal>.
|
||
To disable creation of the default <literal>TraceAsyncClientHttpRequestFactoryWrapper</literal>, set <literal>spring.sleuth.web.async.client.factory.enabled</literal>
|
||
to <literal>false</literal>.
|
||
If you do not want to create <literal>AsyncRestClient</literal> at all, set <literal>spring.sleuth.web.async.client.template.enabled</literal> to <literal>false</literal>.</simpara>
|
||
<section xml:id="_multiple_asynchronous_rest_templates">
|
||
<title>Multiple Asynchronous Rest Templates</title>
|
||
<simpara>Sometimes you need to use multiple implementations of the Asynchronous Rest Template.
|
||
In the following snippet, you can see an example of how to set up such a custom <literal>AsyncRestTemplate</literal>:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Configuration
|
||
@EnableAutoConfiguration
|
||
static class Config {
|
||
|
||
@Bean(name = "customAsyncRestTemplate")
|
||
public AsyncRestTemplate traceAsyncRestTemplate() {
|
||
return new AsyncRestTemplate(asyncClientFactory(),
|
||
clientHttpRequestFactory());
|
||
}
|
||
|
||
private ClientHttpRequestFactory clientHttpRequestFactory() {
|
||
ClientHttpRequestFactory clientHttpRequestFactory = new CustomClientHttpRequestFactory();
|
||
// CUSTOMIZE HERE
|
||
return clientHttpRequestFactory;
|
||
}
|
||
|
||
private AsyncClientHttpRequestFactory asyncClientFactory() {
|
||
AsyncClientHttpRequestFactory factory = new CustomAsyncClientHttpRequestFactory();
|
||
// CUSTOMIZE HERE
|
||
return factory;
|
||
}
|
||
|
||
}</programlisting>
|
||
</section>
|
||
</section>
|
||
<section xml:id="_webclient">
|
||
<title><literal>WebClient</literal></title>
|
||
<simpara>We inject a <literal>ExchangeFilterFunction</literal> implementation that creates a span and, through on-success and on-error callbacks, takes care of closing client-side spans.</simpara>
|
||
<simpara>To block this feature, set <literal>spring.sleuth.web.client.enabled</literal> to <literal>false</literal>.</simpara>
|
||
<important>
|
||
<simpara>You have to register <literal>WebClient</literal> as a bean so that the tracing instrumentation gets applied.
|
||
If you create a <literal>WebClient</literal> instance with a <literal>new</literal> keyword, the instrumentation does NOT work.</simpara>
|
||
</important>
|
||
</section>
|
||
<section xml:id="_traverson">
|
||
<title>Traverson</title>
|
||
<simpara>If you use the <link xl:href="http://docs.spring.io/spring-hateoas/docs/current/reference/html/#client.traverson">Traverson</link> library, you can inject a <literal>RestTemplate</literal> as a bean into your Traverson object.
|
||
Since <literal>RestTemplate</literal> is already intercepted, you get full support for tracing in your client. The following pseudo code
|
||
shows how to do that:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Autowired RestTemplate restTemplate;
|
||
|
||
Traverson traverson = new Traverson(URI.create("http://some/address"),
|
||
MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_UTF8).setRestOperations(restTemplate);
|
||
// use Traverson</programlisting>
|
||
</section>
|
||
<section xml:id="_apache_httpclientbuilder_and_httpasyncclientbuilder">
|
||
<title>Apache <literal>HttpClientBuilder</literal> and <literal>HttpAsyncClientBuilder</literal></title>
|
||
<simpara>We instrument the <literal>HttpClientBuilder</literal> and <literal>HttpAsyncClientBuilder</literal> so that
|
||
tracing context gets injected to the sent requests.</simpara>
|
||
<simpara>To block these features, set <literal>spring.sleuth.web.client.enabled</literal> to <literal>false</literal>.</simpara>
|
||
</section>
|
||
<section xml:id="_netty_httpclient">
|
||
<title>Netty <literal>HttpClient</literal></title>
|
||
<simpara>We instrument the Netty’s <literal>HttpClient</literal>.</simpara>
|
||
<simpara>To block this feature, set <literal>spring.sleuth.web.client.enabled</literal> to <literal>false</literal>.</simpara>
|
||
<important>
|
||
<simpara>You have to register <literal>HttpClient</literal> as a bean so that the instrumentation happens.
|
||
If you create a <literal>HttpClient</literal> instance with a <literal>new</literal> keyword, the instrumentation does NOT work.</simpara>
|
||
</important>
|
||
</section>
|
||
<section xml:id="_userinforesttemplatecustomizer">
|
||
<title><literal>UserInfoRestTemplateCustomizer</literal></title>
|
||
<simpara>We instrument the Spring Security’s <literal>UserInfoRestTemplateCustomizer</literal>.</simpara>
|
||
<simpara>To block this feature, set <literal>spring.sleuth.web.client.enabled</literal> to <literal>false</literal>.</simpara>
|
||
</section>
|
||
</section>
|
||
<section xml:id="_feign">
|
||
<title>Feign</title>
|
||
<simpara>By default, Spring Cloud Sleuth provides integration with Feign through <literal>TraceFeignClientAutoConfiguration</literal>.
|
||
You can disable it entirely by setting <literal>spring.sleuth.feign.enabled</literal> to <literal>false</literal>.
|
||
If you do so, no Feign-related instrumentation take place.</simpara>
|
||
<simpara>Part of Feign instrumentation is done through a <literal>FeignBeanPostProcessor</literal>.
|
||
You can disable it by setting <literal>spring.sleuth.feign.processor.enabled</literal> to <literal>false</literal>.
|
||
If you set it to <literal>false</literal>, Spring Cloud Sleuth does not instrument any of your custom Feign components.
|
||
However, all the default instrumentation is still there.</simpara>
|
||
</section>
|
||
<section xml:id="_grpc">
|
||
<title>gRPC</title>
|
||
<simpara>Spring Cloud Sleuth provides instrumentation for <link xl:href="https://grpc.io/">gRPC</link> through <literal>TraceGrpcAutoConfiguration</literal>. You can disable it entirely by setting <literal>spring.sleuth.grpc.enabled</literal> to <literal>false</literal>.</simpara>
|
||
<section xml:id="_variant_1">
|
||
<title>Variant 1</title>
|
||
<section xml:id="_dependencies">
|
||
<title>Dependencies</title>
|
||
<important>
|
||
<simpara>The gRPC integration relies on two external libraries to instrument clients and servers and both of those libraries must be on the class path to enable the instrumentation.</simpara>
|
||
</important>
|
||
<simpara>Maven:</simpara>
|
||
<screen> <dependency>
|
||
<groupId>io.github.lognet</groupId>
|
||
<artifactId>grpc-spring-boot-starter</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>io.zipkin.brave</groupId>
|
||
<artifactId>brave-instrumentation-grpc</artifactId>
|
||
</dependency></screen>
|
||
<simpara>Gradle:</simpara>
|
||
<screen> compile("io.github.lognet:grpc-spring-boot-starter")
|
||
compile("io.zipkin.brave:brave-instrumentation-grpc")</screen>
|
||
</section>
|
||
<section xml:id="_server_instrumentation">
|
||
<title>Server Instrumentation</title>
|
||
<simpara>Spring Cloud Sleuth leverages grpc-spring-boot-starter to register Brave’s gRPC server interceptor with all services annotated with <literal>@GRpcService</literal>.</simpara>
|
||
</section>
|
||
<section xml:id="_client_instrumentation">
|
||
<title>Client Instrumentation</title>
|
||
<simpara>gRPC clients leverage a <literal>ManagedChannelBuilder</literal> to construct a <literal>ManagedChannel</literal> used to communicate to the gRPC server. The native <literal>ManagedChannelBuilder</literal> provides static methods as entry points for construction of <literal>ManagedChannel</literal> instances, however, this mechanism is outside the influence of the Spring application context.</simpara>
|
||
<important>
|
||
<simpara>Spring Cloud Sleuth provides a <literal>SpringAwareManagedChannelBuilder</literal> that can be customized through the Spring application context and injected by gRPC clients. <emphasis role="strong">This builder must be used when creating <literal>ManagedChannel</literal> instances.</emphasis></simpara>
|
||
</important>
|
||
<simpara>Sleuth creates a <literal>TracingManagedChannelBuilderCustomizer</literal> which inject Brave’s client interceptor into the <literal>SpringAwareManagedChannelBuilder</literal>.</simpara>
|
||
</section>
|
||
</section>
|
||
<section xml:id="_variant_2">
|
||
<title>Variant 2</title>
|
||
<simpara><link xl:href="https://github.com/yidongnan/grpc-spring-boot-starter">Grpc Spring Boot Starter</link> automatically detects the presence of Spring Cloud Sleuth and brave’s instrumentation for gRPC and registers the necessary client and/or server tooling.</simpara>
|
||
</section>
|
||
</section>
|
||
<section xml:id="_asynchronous_communication">
|
||
<title>Asynchronous Communication</title>
|
||
<section xml:id="_async_annotated_methods">
|
||
<title><literal>@Async</literal> Annotated methods</title>
|
||
<simpara>In Spring Cloud Sleuth, we instrument async-related components so that the tracing information is passed between threads.
|
||
You can disable this behavior by setting the value of <literal>spring.sleuth.async.enabled</literal> to <literal>false</literal>.</simpara>
|
||
<simpara>If you annotate your method with <literal>@Async</literal>, we automatically create a new Span with the following characteristics:</simpara>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara>If the method is annotated with <literal>@SpanName</literal>, the value of the annotation is the Span’s name.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>If the method is not annotated with <literal>@SpanName</literal>, the Span name is the annotated method name.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>The span is tagged with the method’s class name and method name.</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</section>
|
||
<section xml:id="_scheduled_annotated_methods">
|
||
<title><literal>@Scheduled</literal> Annotated Methods</title>
|
||
<simpara>In Spring Cloud Sleuth, we instrument scheduled method execution so that the tracing information is passed between threads.
|
||
You can disable this behavior by setting the value of <literal>spring.sleuth.scheduled.enabled</literal> to <literal>false</literal>.</simpara>
|
||
<simpara>If you annotate your method with <literal>@Scheduled</literal>, we automatically create a new span with the following characteristics:</simpara>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara>The span name is the annotated method name.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara>The span is tagged with the method’s class name and method name.</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
<simpara>If you want to skip span creation for some <literal>@Scheduled</literal> annotated classes, you can set the <literal>spring.sleuth.scheduled.skipPattern</literal> with a regular expression that matches the fully qualified name of the <literal>@Scheduled</literal> annotated class.
|
||
If you use <literal>spring-cloud-sleuth-stream</literal> and <literal>spring-cloud-netflix-hystrix-stream</literal> together, a span is created for each Hystrix metrics and sent to Zipkin.
|
||
This behavior may be annoying. That’s why, by default, <literal>spring.sleuth.scheduled.skipPattern=org.springframework.cloud.netflix.hystrix.stream.HystrixStreamTask</literal>.</simpara>
|
||
</section>
|
||
<section xml:id="_executor_executorservice_and_scheduledexecutorservice">
|
||
<title>Executor, ExecutorService, and ScheduledExecutorService</title>
|
||
<simpara>We provide <literal>LazyTraceExecutor</literal>, <literal>TraceableExecutorService</literal>, and <literal>TraceableScheduledExecutorService</literal>. Those implementations create spans each time a new task is submitted, invoked, or scheduled.</simpara>
|
||
<simpara>The following example shows how to pass tracing information with <literal>TraceableExecutorService</literal> when working with <literal>CompletableFuture</literal>:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">CompletableFuture<Long> completableFuture = CompletableFuture.supplyAsync(() -> {
|
||
// perform some logic
|
||
return 1_000_000L;
|
||
}, new TraceableExecutorService(beanFactory, executorService,
|
||
// 'calculateTax' explicitly names the span - this param is optional
|
||
"calculateTax"));</programlisting>
|
||
<important>
|
||
<simpara>Sleuth does not work with <literal>parallelStream()</literal> out of the box.
|
||
If you want to have the tracing information propagated through the stream, you have to use the approach with <literal>supplyAsync(...)</literal>, as shown earlier.</simpara>
|
||
</important>
|
||
<simpara>If there are beans that implement the <literal>Executor</literal> interface that you would like
|
||
to exclude from span creation, you can use the <literal>spring.sleuth.async.ignored-beans</literal>
|
||
property where you can provide a list of bean names.</simpara>
|
||
<section xml:id="_customization_of_executors">
|
||
<title>Customization of Executors</title>
|
||
<simpara>Sometimes, you need to set up a custom instance of the <literal>AsyncExecutor</literal>.
|
||
The following example shows how to set up such a custom <literal>Executor</literal>:</simpara>
|
||
<programlisting language="java" linenumbering="unnumbered">@Configuration
|
||
@EnableAutoConfiguration
|
||
@EnableAsync
|
||
// add the infrastructure role to ensure that the bean gets auto-proxied
|
||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||
static class CustomExecutorConfig extends AsyncConfigurerSupport {
|
||
|
||
@Autowired
|
||
BeanFactory beanFactory;
|
||
|
||
@Override
|
||
public Executor getAsyncExecutor() {
|
||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||
// CUSTOMIZE HERE
|
||
executor.setCorePoolSize(7);
|
||
executor.setMaxPoolSize(42);
|
||
executor.setQueueCapacity(11);
|
||
executor.setThreadNamePrefix("MyExecutor-");
|
||
// DON'T FORGET TO INITIALIZE
|
||
executor.initialize();
|
||
return new LazyTraceExecutor(this.beanFactory, executor);
|
||
}
|
||
|
||
}</programlisting>
|
||
<tip>
|
||
<simpara>To ensure that your configuration gets post processed, remember
|
||
to add the <literal>@Role(BeanDefinition.ROLE_INFRASTRUCTURE)</literal> on your
|
||
<literal>@Configuration</literal> class</simpara>
|
||
</tip>
|
||
</section>
|
||
</section>
|
||
</section>
|
||
<section xml:id="_messaging">
|
||
<title>Messaging</title>
|
||
<simpara>Features from this section can be disabled by setting the <literal>spring.sleuth.messaging.enabled</literal> property with value equal to <literal>false</literal>.</simpara>
|
||
<section xml:id="_spring_integration_and_spring_cloud_stream">
|
||
<title>Spring Integration and Spring Cloud Stream</title>
|
||
<simpara>Spring Cloud Sleuth integrates with <link xl:href="http://projects.spring.io/spring-integration/">Spring Integration</link>.
|
||
It creates spans for publish and subscribe events.
|
||
To disable Spring Integration instrumentation, set <literal>spring.sleuth.integration.enabled</literal> to <literal>false</literal>.</simpara>
|
||
<simpara>You can provide the <literal>spring.sleuth.integration.patterns</literal> pattern to explicitly provide the names of channels that you want to include for tracing.
|
||
By default, all channels but <literal>hystrixStreamOutput</literal> channel are included.</simpara>
|
||
<important>
|
||
<simpara>When using the <literal>Executor</literal> to build a Spring Integration <literal>IntegrationFlow</literal>, you must use the untraced version of the <literal>Executor</literal>.
|
||
Decorating the Spring Integration Executor Channel with <literal>TraceableExecutorService</literal> causes the spans to be improperly closed.</simpara>
|
||
</important>
|
||
<simpara>If you want to customize the way tracing context is read from and written to message headers,
|
||
it’s enough for you to register beans of types:</simpara>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara><literal>Propagation.Setter<MessageHeaderAccessor, String></literal> - for writing headers to the message</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara><literal>Propagation.Getter<MessageHeaderAccessor, String></literal> - for reading headers from the message</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</section>
|
||
<section xml:id="_spring_rabbitmq">
|
||
<title>Spring RabbitMq</title>
|
||
<simpara>We instrument the <literal>RabbitTemplate</literal> so that tracing headers get injected
|
||
into the message.</simpara>
|
||
<simpara>To block this feature, set <literal>spring.sleuth.messaging.rabbit.enabled</literal> to <literal>false</literal>.</simpara>
|
||
</section>
|
||
<section xml:id="_spring_kafka">
|
||
<title>Spring Kafka</title>
|
||
<simpara>We instrument the Spring Kafka’s <literal>ProducerFactory</literal> and <literal>ConsumerFactory</literal>
|
||
so that tracing headers get injected into the created Spring Kafka’s
|
||
<literal>Producer</literal> and <literal>Consumer</literal>.</simpara>
|
||
<simpara>To block this feature, set <literal>spring.sleuth.messaging.kafka.enabled</literal> to <literal>false</literal>.</simpara>
|
||
</section>
|
||
<section xml:id="_spring_jms">
|
||
<title>Spring JMS</title>
|
||
<simpara>We instrument the <literal>JmsTemplate</literal> so that tracing headers get injected
|
||
into the message. We also support <literal>@JmsListener</literal> annotated methods on the consumer side.</simpara>
|
||
<simpara>To block this feature, set <literal>spring.sleuth.messaging.jms.enabled</literal> to <literal>false</literal>.</simpara>
|
||
<important>
|
||
<simpara>We don’t support baggage propagation for JMS</simpara>
|
||
</important>
|
||
</section>
|
||
</section>
|
||
<section xml:id="_zuul">
|
||
<title>Zuul</title>
|
||
<simpara>We instrument the Zuul Ribbon integration by enriching the Ribbon requests with tracing information.
|
||
To disable Zuul support, set the <literal>spring.sleuth.zuul.enabled</literal> property to <literal>false</literal>.</simpara>
|
||
</section>
|
||
</chapter>
|
||
<chapter xml:id="_running_examples">
|
||
<title>Running examples</title>
|
||
<simpara>You can see the running examples deployed in the <link xl:href="https://run.pivotal.io/">Pivotal Web Services</link>.
|
||
Check them out at the following links:</simpara>
|
||
<itemizedlist>
|
||
<listitem>
|
||
<simpara><link xl:href="http://docssleuth-zipkin-server.cfapps.io/">Zipkin for apps presented in the samples to the top</link>. First make
|
||
a request to <link xl:href="http://docssleuth-service1.cfapps.io/start">Service 1</link> and then check out the trace in Zipkin.</simpara>
|
||
</listitem>
|
||
<listitem>
|
||
<simpara><link xl:href="http://docsbrewing-zipkin-server.cfapps.io/">Zipkin for Brewery on PWS</link>, its <link xl:href="https://github.com/spring-cloud-samples/brewery">Github Code</link>.
|
||
Ensure that you’ve picked the lookback period of 7 days. If there are no traces, go to <link xl:href="https://docsbrewing-presenting.cfapps.io/">Presenting application</link>
|
||
and order some beers. Then check Zipkin for traces.</simpara>
|
||
</listitem>
|
||
</itemizedlist>
|
||
</chapter>
|
||
</book> |