Removes duplication of Brave docs (#1601)

This commit is contained in:
Adrian Cole
2020-04-06 13:14:06 +08:00
committed by GitHub
parent 8d72c4a08d
commit 0b838a796d

View File

@@ -12,299 +12,41 @@ include::intro.adoc[]
include::features.adoc[]
=== Introduction to Brave
== Introduction to Brave
IMPORTANT: Starting with version `2.0.0`, Spring Cloud Sleuth uses
https://github.com/openzipkin/brave[Brave] as the tracing library.
For your convenience, we embed part of the Brave's docs here.
Brave is a distributed tracing instrumentation library. Brave typically
intercepts production requests to gather timing data, correlate and propagate
trace contexts. While typically trace data is sent to Zipkin server,
third-party plugins are available to send to alternate services such as Amazon
X-Ray.
IMPORTANT: In the vast majority of cases you need to just use the `Tracer`
or `SpanCustomizer` beans from Brave that Sleuth provides. The documentation below contains
a high overview of what Brave is and how it works.
Spring Cloud Sleuth is a layer over https://github.com/openzipkin/brave[Brave].
It configures everything you need to get started with tracing. Sleuth
configures where trace data (spans) are reported to, how many traces to keep
(sampling), if remote fields (baggage) and which libraries are traced.
Sleuth also adds annotation based tracing features and some instrumentation not
available otherwise, such as Reactor.
// TODO: We should link, not include. We have no idea when that content will change and so no way to keep our copy current. I also have no idea what I should edit, because I don't know what is ours and what is Brave's.
=== Brave Basics
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.
Most instrumentation work is done for you by default. Sleuth provides beans to
allow you to change what's traced, and it even provides annotations to avoid
using tracing libraries! All of this is explained later in this document.
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).
That said, you might want to know more about how things work underneath. Here
are some pointers.
==== Tracing
Here are the most core types you might use:
* `SpanCustomizer` - to change the span currently in progress
* `Tracer` - to get a start new spans ad-hoc
Most importantly, you need a `brave.Tracer`, configured to https://github.com/openzipkin/zipkin-reporter-java[report to Zipkin].
The following example setup sends trace data (spans) to Zipkin over HTTP (as opposed to Kafka):
```java
class MyClass {
private final Tracer tracer;
// Tracer will be autowired
MyClass(Tracer tracer) {
this.tracer = tracer;
}
void doSth() {
Span span = tracer.newTrace().name("encode").start();
// ...
}
}
```
IMPORTANT: If your span contains a name 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.
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.
Spans returned by a tracer report data to Zipkin when finished or do nothing if unsampled.
After starting a span, you can annotate events of interest or add tags containing details or lookup keys.
Spans have a context that includes trace identifiers that place the span at the correct spot in the tree representing the distributed operation.
==== Local Tracing
When tracing code that never leaves your process, run it inside a scoped span.
```java
@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
}
```
When you need more features, or finer control, use the `Span` type:
```java
@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.
}
```
Both of the above examples report the exact same span on finish!
In the above example, the span will be either a new root span or the
next child in an existing trace.
==== Customizing Spans
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:
```java
span.tag("clnt/finagle.version", "6.36.0");
```
When exposing the ability to customize spans to third parties, prefer `brave.SpanCustomizer` as opposed to `brave.Span`.
The former is simpler to understand and test and does not tempt users with span lifecycle hooks.
```java
interface MyTraceCallback {
void request(Request request, SpanCustomizer customizer);
}
```
Since `brave.Span` implements `brave.SpanCustomizer`, you can pass it to users, as shown in the following example:
```java
for (MyTraceCallback callback : userCallbacks) {
callback.request(request, span);
}
```
==== Implicitly Looking up the Current Span
Sometimes, you do not know if a trace is in progress or not, and you do not want users to do null checks.
`brave.CurrentSpanCustomizer` handles this problem by adding data to any span that's in progress or drops, as shown in the following example:
Ex.
```java
// The user code can then inject this without a chance of it being null.
@Autowired SpanCustomizer span;
void userCode() {
span.annotate("tx.started");
...
}
```
==== RPC tracing
TIP: Check for https://github.com/openzipkin/brave/tree/master/instrumentation[instrumentation written here] and https://zipkin.io/pages/existing_instrumentations.html[Zipkin's list] before rolling your own RPC instrumentation.
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.
The following example shows how to add a client span:
```java
@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();
```
===== One-Way tracing
Sometimes, you need to model an asynchronous operation where there is a
request but no response. In normal RPC tracing, you use `span.finish()`
to indicate that the response was received. In one-way tracing, you use
`span.flush()` instead, as you do not expect a response.
The following example shows how a client might model a one-way operation:
```java
@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();
```
The following example shows how a server might handle a one-way operation:
```java
@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();
```
Here are the most relevant links from the OpenZipkin Brave project:
* [Brave's core library](https://github.com/openzipkin/brave/tree/master/brave)
* [Baggage (propagated fields)](https://github.com/openzipkin/brave/tree/master/brave#baggage)
* [HTTP tracing](https://github.com/openzipkin/brave/tree/master/instrumentation/http)
== Sampling
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).
Sampling is an up-front decision, meaning that the decision to report data is made at the first operation in a trace and that decision is propagated downstream.
By default, a global sampler applies a single rate to all traced operations.
`Tracer.Builder.sampler` controls this setting, and it defaults to tracing every request.
=== Declarative sampling
Some applications need to sample based on the type or annotations of a java method.
Most users use a framework interceptor to automate this sort of policy.
The following example shows how that might work internally:
```java
@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();
}
}
```
=== Custom sampling
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.
Most users use a framework interceptor to automate this sort of policy.
The following example shows how that might work internally:
```java
@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();
}
```
=== Sampling in Spring Cloud Sleuth
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).
@@ -320,126 +62,12 @@ A sampler can be installed by creating a bean definition, as shown in the follow
include::{project-root}/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[tags=always_sampler,indent=0]
----
TIP: You can set the HTTP header `X-B3-Flags` to `1`, or, when doing messaging, you can set the `spanFlags` header to `1`.
TIP: You can set the HTTP header `b3` to `1`, or, when doing messaging, you can set the `spanFlags` header to `1`.
Doing so forces the current span to be exportable regardless of the sampling decision.
In order to use the rate-limited sampler set the `spring.sleuth.sampler.rate` 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).
== Propagation
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.
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:
```
Client Span Server Span
┌──────────────────┐ ┌──────────────────┐
│ │ │ │
│ TraceContext │ Http Request Headers │ TraceContext │
│ ┌──────────────┐ │ ┌───────────────────┐ │ ┌──────────────┐ │
│ │ TraceId │ │ │ X─B3─TraceId │ │ │ TraceId │ │
│ │ │ │ │ │ │ │ │ │
│ │ ParentSpanId │ │ Extract │ X─B3─ParentSpanId │ Inject │ │ ParentSpanId │ │
│ │ ├─┼─────────>│ ├────────┼>│ │ │
│ │ SpanId │ │ │ X─B3─SpanId │ │ │ SpanId │ │
│ │ │ │ │ │ │ │ │ │
│ │ Sampled │ │ │ X─B3─Sampled │ │ │ Sampled │ │
│ └──────────────┘ │ └───────────────────┘ │ └──────────────┘ │
│ │ │ │
└──────────────────┘ └──────────────────┘
```
The names above are from https://github.com/openzipkin/b3-propagation[B3 Propagation], which is built-in to Brave and has implementations in many languages and frameworks.
Most users use a framework interceptor to automate propagation.
The next two examples show how that might work for a client and a server.
The following example shows how client-side propagation might work:
```java
@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);
```
The following example shows how server-side propagation might work:
```java
@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));
```
=== Propagating extra fields
Sometimes you need to propagate extra fields, such as a request ID or an alternate trace context.
For example, if you are in a Cloud Foundry environment, you might want to pass the request ID, as shown in the following example:
```java
// 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");
```
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:
```java
tracingBuilder.propagationFactory(
ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "x-amzn-trace-id")
);
```
TIP: In Spring Cloud Sleuth all elements of the tracing builder `Tracing.newBuilder()`
are defined as beans. So if you want to pass a custom `PropagationFactory`, it's enough
for you to create a bean of that type and we will set it in the `Tracing` bean.
[[prefixed-fields]]
==== Prefixed fields
If they follow a common pattern, you can also prefix fields.
The following example shows how to propagate `x-vcap-request-id` the field as-is but send the `country-code` and `user-id` fields on the wire as `x-baggage-country-code` and `x-baggage-user-id`, respectively:
```java
Tracing.newBuilder().propagationFactory(
ExtraFieldPropagation.newFactoryBuilder(B3Propagation.FACTORY)
.addField("x-vcap-request-id")
.addPrefixedFields("x-baggage-", Arrays.asList("country-code", "user-id"))
.build()
);
```
Later, you can call the following code to affect the country code of the current trace context:
```java
ExtraFieldPropagation.set("x-country-code", "FO");
String countryCode = ExtraFieldPropagation.get("x-country-code");
```
Alternatively, if you have a reference to a trace context, you can use it explicitly, as shown in the following example:
```java
ExtraFieldPropagation.set(span.context(), "x-country-code", "FO");
String countryCode = ExtraFieldPropagation.get(span.context(), "x-country-code");
```
IMPORTANT: A difference from previous versions of Sleuth is that, with Brave, you must pass the list of baggage keys.
There are the following properties to achieve this.
== Baggage
With the `spring.sleuth.baggage-keys`, you set keys that get prefixed with `baggage-` for HTTP calls and `baggage_` for messaging.
You can also use the `spring.sleuth.propagation-keys` property to pass a list of prefixed keys that are propagated to remote services without any prefix.
You can also use the `spring.sleuth.local-keys` property to pass a list keys that will be propagated locally but will not be propagated over the wire.
@@ -454,123 +82,6 @@ IMPORTANT: Remember that adding entries to MDC can drastically decrease the perf
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
`spring.sleuth.propagation.tag.whitelisted-keys` with a list of whitelisted baggage keys. To disable the feature you have to pass the `spring.sleuth.propagation.tag.enabled=false` property.
==== Extracting a Propagated Context
The `TraceContext.Extractor<C>` reads trace identifiers and sampling status from an incoming request or message.
The carrier is usually a request object or headers.
This utility is used in standard instrumentation (such as `HttpServerHandler`) but can also be used for custom RPC or messaging code.
`TraceContextOrSamplingFlags` is usually used only with `Tracer.nextSpan(extracted)`, unless you are
sharing span IDs between a client and a server.
==== Sharing span IDs between Client and Server
A normal instrumentation pattern is to create a span representing the server side of an RPC.
`Extractor.extract` might return a complete trace context when applied to an incoming client request.
`Tracer.joinSpan` attempts to continue 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.
The following image shows an example of B3 propagation:
```
┌───────────────────┐ ┌───────────────────┐
Incoming Headers │ TraceContext │ │ TraceContext │
┌───────────────────┐(extract)│ ┌───────────────┐ │(join)│ ┌───────────────┐ │
│ X─B3-TraceId │─────────┼─┼> TraceId │ │──────┼─┼> TraceId │ │
│ │ │ │ │ │ │ │ │ │
│ X─B3-ParentSpanId │─────────┼─┼> ParentSpanId │ │──────┼─┼> ParentSpanId │ │
│ │ │ │ │ │ │ │ │ │
│ X─B3-SpanId │─────────┼─┼> SpanId │ │──────┼─┼> SpanId │ │
└───────────────────┘ │ │ │ │ │ │ │ │
│ │ │ │ │ │ Shared: true │ │
│ └───────────────┘ │ │ └───────────────┘ │
└───────────────────┘ └───────────────────┘
```
Some propagation systems forward only the parent span ID, detected when `Propagation.Factory.supportsJoin() == false`.
In this case, a new span ID is always provisioned, and the incoming context determines the parent ID.
The following image shows an example of AWS propagation:
```
┌───────────────────┐ ┌───────────────────┐
x-amzn-trace-id │ TraceContext │ │ TraceContext │
┌───────────────────┐(extract)│ ┌───────────────┐ │(join)│ ┌───────────────┐ │
│ Root │─────────┼─┼> TraceId │ │──────┼─┼> TraceId │ │
│ │ │ │ │ │ │ │ │ │
│ Parent │─────────┼─┼> SpanId │ │──────┼─┼> ParentSpanId │ │
└───────────────────┘ │ └───────────────┘ │ │ │ │ │
└───────────────────┘ │ │ SpanId: New │ │
│ └───────────────┘ │
└───────────────────┘
```
Note: Some span reporters do not support sharing span IDs.
For example, if you set `Tracing.Builder.spanReporter(amazonXrayOrGoogleStackdrive)`, you should disable join by setting `Tracing.Builder.supportsJoin(false)`.
Doing so forces a new child span on `Tracer.joinSpan()`.
==== Implementing Propagation
`TraceContext.Extractor<C>` is implemented by a `Propagation.Factory` plugin.
Internally, this code creates the union type, `TraceContextOrSamplingFlags`, with one of the following:
* `TraceContext` if trace and span IDs were present.
* `TraceIdContext` if a trace ID was present but span IDs were not present.
* `SamplingFlags` if no identifiers were present.
Some `Propagation` 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 `TraceContext` were extracted, add the extra data as `TraceContext.extra()`.
* Otherwise, add it as `TraceContextOrSamplingFlags.extra()`, which `Tracer.nextSpan` handles.
== Current Tracing Component
Brave supports a "`current tracing component`" concept, which should only be used when you have no other way to get a reference.
This was made for JDBC connections, as they often initialize prior to the tracing component.
The most recent tracing component instantiated is available through `Tracing.current()`.
You can also use `Tracing.currentTracer()` 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.
== Current Span
Brave supports a "`current span`" concept which represents the in-flight operation.
You can use `Tracer.currentSpan()` to add custom tags to a span and `Tracer.nextSpan()` to create a child of whatever is in-flight.
IMPORTANT: In Sleuth, you can autowire the `Tracer` bean to retrieve the current span via
`tracer.currentSpan()` method. To retrieve the current context just call
`tracer.currentSpan().context()`. To get the current trace id as String
you can use the `traceIdString()` method like this: `tracer.currentSpan().context().traceIdString()`.
=== Setting a span in scope manually
When writing new instrumentation, it is important to place a span you created in scope as the current span.
Not only does doing so let users access it with `Tracer.currentSpan()`, but it also allows customizations such as SLF4J MDC to see the current trace IDs.
`Tracer.withSpanInScope(Span)` 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:
```java
@Autowired Tracer tracer;
try (SpanInScope ws = tracer.withSpanInScope(span)) {
return inboundRequest.invoke();
} finally { // note the scope is independent of the span
span.finish();
}
```
In edge cases, you may need to clear the current span temporarily (for example, launching a task that should not be associated with the current request). To do tso, pass null to `withSpanInScope`, as shown in the following example:
```java
@Autowired Tracer tracer;
try (SpanInScope cleared = tracer.withSpanInScope(null)) {
startBackgroundThread();
}
```
== Instrumentation
Spring Cloud Sleuth automatically instruments all your Spring applications, so you should not have to do anything to activate it.
@@ -824,15 +335,19 @@ Running the preceding method with a value of `15` leads to setting a tag with a
== Customizations
=== Customizers
The `Tracer` object is fully managed by sleuth, so you rarely need to affect it. That said,
Sleuth supports a number of `Customizer` types, that allow you to configure
anything not already done by Sleuth with auto-configuration or properties.
With Brave 5.7 you have various options of providing customizers for your project. Brave ships with
If you define one of the following as a `Bean`, Sleuth will invoke it to
customize behaviour:
* `TracingCustomizer` - allows configuration plugins to collaborate on building an instance of `Tracing`.
* `CurrentTraceContextCustomizer` - allows configuration plugins to collaborate on building an instance of `CurrentTraceContext`.
* `ExtraFieldCustomizer` - allows configuration plugins to collaborate on building an instance of `ExtraFieldPropagation.Factory`.
Sleuth will search for beans of those types and automatically apply customizations.
* RpcTracingCustomizer - for RPC tagging and sampling policy
* HttpTracingCustomizer - for HTTP tagging and sampling policy
* MessagingTracingCustomizer - for messaging tagging and sampling policy
* CurrentTraceContextCustomizer - to integrate decorators such as correlation.
* BaggagePropagationCustomizer - for propagating baggage fields in process and over headers
* CorrelationScopeDecoratorCustomizer - for scope decorations such as MDC (logging) field correlation
=== HTTP