139 lines
24 KiB
HTML
139 lines
24 KiB
HTML
<html><head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
|
<title>51. Features</title><link rel="stylesheet" type="text/css" href="css/manual-multipage.css"><meta name="generator" content="DocBook XSL Stylesheets V1.78.1"><link rel="home" href="multi_spring-cloud.html" title="Spring Cloud"><link rel="up" href="multi__spring_cloud_sleuth.html" title="Part VIII. Spring Cloud Sleuth"><link rel="prev" href="multi__additional_resources.html" title="50. Additional Resources"><link rel="next" href="multi__sampling.html" title="52. Sampling"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">51. Features</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__additional_resources.html">Prev</a> </td><th width="60%" align="center">Part VIII. Spring Cloud Sleuth</th><td width="20%" align="right"> <a accesskey="n" href="multi__sampling.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h2 class="title"><a name="_features_2" href="#_features_2"></a>51. Features</h2></div></div></div><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p class="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:</p><pre class="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] ...</pre><p class="simpara">Notice the <code class="literal">[appname,traceId,spanId,exportable]</code> entries from the MDC:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: circle; "><li class="listitem"><span class="strong"><strong><code class="literal">spanId</code></strong></span>: The ID of a specific operation that took place.</li><li class="listitem"><span class="strong"><strong><code class="literal">appname</code></strong></span>: The name of the application that logged the span.</li><li class="listitem"><span class="strong"><strong><code class="literal">traceId</code></strong></span>: The ID of the latency graph that contains the span.</li><li class="listitem"><span class="strong"><strong><code class="literal">exportable</code></strong></span>: 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.</li></ul></div></li><li class="listitem">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).</li><li class="listitem">Sleuth records timing information to aid in latency analysis.
|
|
By using sleuth, you can pinpoint causes of latency in your applications.</li><li class="listitem"><p class="simpara">Sleuth is written to not log too much and to not cause your production application to crash.
|
|
To that end, Sleuth:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: circle; "><li class="listitem">Propagates structural data about your call graph in-band and the rest out-of-band.</li><li class="listitem">Includes opinionated instrumentation of layers such as HTTP.</li><li class="listitem">Includes a sampling policy to manage volume.</li><li class="listitem">Can report to a Zipkin system for query and visualization.</li></ul></div></li><li class="listitem">Instruments common ingress and egress points from Spring applications (servlet filter, async endpoints, rest template, scheduled actions, message channels, Zuul filters, and Feign client).</li><li class="listitem">Sleuth includes default logic to join a trace across HTTP or messaging boundaries.
|
|
For example, HTTP propagation works over Zipkin-compatible request headers.</li><li class="listitem">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.</li><li class="listitem">Provides a way to create or continue spans and add tags and logs through annotations.</li><li class="listitem"><p class="simpara">If <code class="literal">spring-cloud-sleuth-zipkin</code> 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 <code class="literal">spring.zipkin.baseUrl</code>.</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: circle; "><li class="listitem">If you depend on <code class="literal">spring-rabbit</code>, your app sends traces to a RabbitMQ broker instead of HTTP.</li><li class="listitem">If you depend on <code class="literal">spring-kafka</code>, and set <code class="literal">spring.zipkin.sender.type: kafka</code>, your app sends traces to a Kafka broker instead of HTTP.</li></ul></div></li></ul></div><div class="caution" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Caution"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Caution]" src="images/caution.png"></td><th align="left">Caution</th></tr><tr><td align="left" valign="top"><p><code class="literal">spring-cloud-sleuth-stream</code> is deprecated and should no longer be used.</p></td></tr></table></div><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">Spring Cloud Sleuth is <a class="link" href="http://opentracing.io/" target="_top">OpenTracing</a> compatible.</li></ul></div><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>If you use Zipkin, configure the probability of spans exported by setting <code class="literal">spring.sleuth.sampler.probability</code>
|
|
(default: 0.1, which is 10 percent). Otherwise, you might think that Sleuth is not working be cause it omits some spans.</p></td></tr></table></div><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>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:
|
|
<code class="literal">logging.pattern.level</code> set to <code class="literal">%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]</code>
|
|
(this is a Spring Boot feature for logback users).
|
|
If you do not use SLF4J, this pattern is NOT automatically applied.</p></td></tr></table></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_introduction_to_brave" href="#_introduction_to_brave"></a>51.1 Introduction to Brave</h2></div></div></div><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>Starting with version <code class="literal">2.0.0</code>, Spring Cloud Sleuth uses
|
|
<a class="link" href="https://github.com/openzipkin/brave" target="_top">Brave</a> as the tracing library.
|
|
For your convenience, we embed part of the Brave’s docs here.</p></td></tr></table></div><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>In the vast majority of cases you need to just use the <code class="literal">Tracer</code>
|
|
or <code class="literal">SpanCustomizer</code> beans from Brave that Sleuth provides. The documentation below contains
|
|
a high overview of what Brave is and how it works.</p></td></tr></table></div><p>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.</p><p>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).</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_tracing" href="#_tracing"></a>51.1.1 Tracing</h3></div></div></div><p>Most importantly, you need a <code class="literal">brave.Tracer</code>, configured to <a class="link" href="https://github.com/openzipkin/zipkin-reporter-java" target="_top">report to Zipkin</a>.</p><p>The following example setup sends trace data (spans) to Zipkin over HTTP (as opposed to Kafka):</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> MyClass {
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Tracer tracer;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Tracer will be autowired</span>
|
|
MyClass(Tracer tracer) {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.tracer = tracer;
|
|
}
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> doSth() {
|
|
Span span = tracer.newTrace().name(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"encode"</span>).start();
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// ...</span>
|
|
}
|
|
}</pre><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>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.</p></td></tr></table></div><p>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.</p><p>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.</p><p>Spans have a context that includes trace identifiers that place the span at the correct spot in the tree representing the distributed operation.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_local_tracing" href="#_local_tracing"></a>51.1.2 Local Tracing</h3></div></div></div><p>When tracing code that never leaves your process, run it inside a scoped span.</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@Autowired</span></em> Tracer tracer;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Start a new trace or a span within an existing trace representing an operation</span>
|
|
ScopedSpan span = tracer.startScopedSpan(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"encode"</span>);
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">try</span> {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// The span is in "scope" meaning downstream code such as loggers can see trace IDs</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> encoder.encode();
|
|
} <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">catch</span> (RuntimeException | Error e) {
|
|
span.error(e); <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Unless you handle exceptions, you might not know the operation failed!</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">throw</span> e;
|
|
} <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">finally</span> {
|
|
span.finish(); <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// always finish the span</span>
|
|
}</pre><p>When you need more features, or finer control, use the <code class="literal">Span</code> type:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@Autowired</span></em> Tracer tracer;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Start a new trace or a span within an existing trace representing an operation</span>
|
|
Span span = tracer.nextSpan().name(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"encode"</span>).start();
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Put the span in "scope" so that downstream code such as loggers can see trace IDs</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">try</span> (SpanInScope ws = tracer.withSpanInScope(span)) {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> encoder.encode();
|
|
} <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">catch</span> (RuntimeException | Error e) {
|
|
span.error(e); <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Unless you handle exceptions, you might not know the operation failed!</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">throw</span> e;
|
|
} <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">finally</span> {
|
|
span.finish(); <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// note the scope is independent of the span. Always finish a span.</span>
|
|
}</pre><p>Both of the above examples report the exact same span on finish!</p><p>In the above example, the span will be either a new root span or the
|
|
next child in an existing trace.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_customizing_spans" href="#_customizing_spans"></a>51.1.3 Customizing Spans</h3></div></div></div><p>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:</p><pre class="programlisting">span.tag(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"clnt/finagle.version"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"6.36.0"</span>);</pre><p>When exposing the ability to customize spans to third parties, prefer <code class="literal">brave.SpanCustomizer</code> as opposed to <code class="literal">brave.Span</code>.
|
|
The former is simpler to understand and test and does not tempt users with span lifecycle hooks.</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">interface</span> MyTraceCallback {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> request(Request request, SpanCustomizer customizer);
|
|
}</pre><p>Since <code class="literal">brave.Span</code> implements <code class="literal">brave.SpanCustomizer</code>, you can pass it to users, as shown in the following example:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">for</span> (MyTraceCallback callback : userCallbacks) {
|
|
callback.request(request, span);
|
|
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_implicitly_looking_up_the_current_span" href="#_implicitly_looking_up_the_current_span"></a>51.1.4 Implicitly Looking up the Current Span</h3></div></div></div><p>Sometimes, you do not know if a trace is in progress or not, and you do not want users to do null checks.
|
|
<code class="literal">brave.CurrentSpanCustomizer</code> handles this problem by adding data to any span that’s in progress or drops, as shown in the following example:</p><p>Ex.</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// The user code can then inject this without a chance of it being null.</span>
|
|
<em><span class="hl-annotation" style="color: gray">@Autowired</span></em> SpanCustomizer span;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> userCode() {
|
|
span.annotate(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"tx.started"</span>);
|
|
...
|
|
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_rpc_tracing" href="#_rpc_tracing"></a>51.1.5 RPC tracing</h3></div></div></div><div class="tip" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Tip"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Tip]" src="images/tip.png"></td><th align="left">Tip</th></tr><tr><td align="left" valign="top"><p>Check for <a class="link" href="https://github.com/openzipkin/brave/tree/master/instrumentation" target="_top">instrumentation written here</a> and <a class="link" href="http://zipkin.io/pages/existing_instrumentations.html" target="_top">Zipkin’s list</a> before rolling your own RPC instrumentation.</p></td></tr></table></div><p>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.</p><p>The following example shows how to add a client span:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@Autowired</span></em> Tracing tracing;
|
|
<em><span class="hl-annotation" style="color: gray">@Autowired</span></em> Tracer tracer;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// before you send a request, add metadata that describes the operation</span>
|
|
span = tracer.nextSpan().name(service + <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/"</span> + method).kind(CLIENT);
|
|
span.tag(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"myrpc.version"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"1.0.0"</span>);
|
|
span.remoteServiceName(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"backend"</span>);
|
|
span.remoteIpAndPort(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"172.3.4.1"</span>, <span class="hl-number">8108</span>);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Add the trace context to the request, so it can be propagated in-band</span>
|
|
tracing.propagation().injector(Request::addHeader)
|
|
.inject(span.context(), request);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// when the request is scheduled, start the span</span>
|
|
span.start();
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// if there is an error, tag the span</span>
|
|
span.tag(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"error"</span>, error.getCode());
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// or if there is an exception</span>
|
|
span.error(exception);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// when the response is complete, finish the span</span>
|
|
span.finish();</pre><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_one_way_tracing" href="#_one_way_tracing"></a>One-Way tracing</h4></div></div></div><p>Sometimes, you need to model an asynchronous operation where there is a
|
|
request but no response. In normal RPC tracing, you use <code class="literal">span.finish()</code>
|
|
to indicate that the response was received. In one-way tracing, you use
|
|
<code class="literal">span.flush()</code> instead, as you do not expect a response.</p><p>The following example shows how a client might model a one-way operation:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@Autowired</span></em> Tracing tracing;
|
|
<em><span class="hl-annotation" style="color: gray">@Autowired</span></em> Tracer tracer;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// start a new span representing a client request</span>
|
|
oneWaySend = tracer.nextSpan().name(service + <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/"</span> + method).kind(CLIENT);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Add the trace context to the request, so it can be propagated in-band</span>
|
|
tracing.propagation().injector(Request::addHeader)
|
|
.inject(oneWaySend.context(), request);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// fire off the request asynchronously, totally dropping any response</span>
|
|
request.execute();
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// start the client side and flush instead of finish</span>
|
|
oneWaySend.start().flush();</pre><p>The following example shows how a server might handle a one-way operation:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@Autowired</span></em> Tracing tracing;
|
|
<em><span class="hl-annotation" style="color: gray">@Autowired</span></em> Tracer tracer;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// pull the context out of the incoming request</span>
|
|
extractor = tracing.propagation().extractor(Request::getHeader);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// convert that context to a span which you can name and add tags to</span>
|
|
oneWayReceive = nextSpan(tracer, extractor.extract(request))
|
|
.name(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"process-request"</span>)
|
|
.kind(SERVER)
|
|
... add tags etc.
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// start the server side and flush instead of finish</span>
|
|
oneWayReceive.start().flush();
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// you should not modify this span anymore as it is complete. However,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// you can create children to represent follow-up work.</span>
|
|
next = tracer.newSpan(oneWayReceive.context()).name(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"step2"</span>).start();</pre></div></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi__additional_resources.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="multi__spring_cloud_sleuth.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="multi__sampling.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">50. Additional Resources </td><td width="20%" align="center"><a accesskey="h" href="multi_spring-cloud.html">Home</a></td><td width="40%" align="right" valign="top"> 52. Sampling</td></tr></table></div></body></html> |