Propagation is needed to ensure activity originating from the same root are collected together in the same trace. The most common propagation approach is to copy a trace context from a client sending an RPC request to a server receiving it.
For example, when an downstream Http call is made, its trace context is sent along with it, encoded as request headers:
Client Span Server Span ┌──────────────────┐ ┌──────────────────┐ │ │ │ │ │ TraceContext │ Http Request Headers │ TraceContext │ │ ┌──────────────┐ │ ┌───────────────────┐ │ ┌──────────────┐ │ │ │ TraceId │ │ │ X─B3─TraceId │ │ │ TraceId │ │ │ │ │ │ │ │ │ │ │ │ │ │ ParentSpanId │ │ Extract │ X─B3─ParentSpanId │ Inject │ │ ParentSpanId │ │ │ │ ├─┼─────────>│ ├────────┼>│ │ │ │ │ SpanId │ │ │ X─B3─SpanId │ │ │ SpanId │ │ │ │ │ │ │ │ │ │ │ │ │ │ Sampled │ │ │ X─B3─Sampled │ │ │ Sampled │ │ │ └──────────────┘ │ └───────────────────┘ │ └──────────────┘ │ │ │ │ │ └──────────────────┘ └──────────────────┘
The names above are from B3 Propagation, which is built-in to Brave and has implementations in many languages and frameworks.
Most users will use a framework interceptor which automates propagation. Here’s how they might work internally.
Here’s what client-side propagation might look like
// configure a function that injects a trace context into a request injector = tracing.propagation().injector(Request.Builder::addHeader); // before a request is sent, add the current span's context to it injector.inject(span.context(), request);
Here’s what server-side propagation might look like
// configure a function that extracts the trace context from a request extracted = tracing.propagation().extractor(Request::getHeader); // when a server receives a request, it joins or starts a new trace span = tracer.nextSpan(extracted, request);
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:
// when you initialize the builder, define the extra field you want to propagate tracingBuilder.propagationFactory( ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "x-vcap-request-id") ); // later, you can tag that request ID or use it in log correlation requestId = ExtraFieldPropagation.get("x-vcap-request-id");
You may also need to propagate a trace context you aren’t using. For example, you may be in an Amazon Web Services environment, but not reporting data to X-Ray. To ensure X-Ray can co-exist correctly, pass-through its tracing header like so.
tracingBuilder.propagationFactory(
ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "x-amzn-trace-id")
);You can also prefix fields, if they follow a common pattern. For example, the following will propagate the field "x-vcap-request-id" as-is, but send the fields "country-code" and "user-id" on the wire as "x-baggage-country-code" and "x-baggage-user-id" respectively.
Setup your tracing instance with allowed fields:
tracingBuilder.propagationFactory(
ExtraFieldPropagation.newFactoryBuilder(B3Propagation.FACTORY)
.addField("x-vcap-request-id")
.addPrefixedFields("baggage-", Arrays.asList("country-code", "user-id"))
.build()
);Later, you can call below to affect the country code of the current trace context
ExtraFieldPropagation.set("country-code", "FO"); String countryCode = ExtraFieldPropagation.get("country-code");
Or, if you have a reference to a trace context, use it explicitly
ExtraFieldPropagation.set(span.context(), "country-code", "FO"); String countryCode = ExtraFieldPropagation.get(span.context(), "country-code");
![]() | Important |
|---|---|
In comparison to previous versions of Sleuth, with
Brave it’s required to pass the list of baggage keys.
There are two properties to achieve this. Via the |
The TraceContext.Extractor<C> reads trace identifiers and sampling status
from an incoming request or message. The carrier is usually a request object
or headers.
This utility is used in standard instrumentation like [HttpServerHandler](../instrumentation/http/src/main/java/sleuth/http/HttpServerHandler.java), but can also be used for custom RPC or messaging code.
TraceContextOrSamplingFlags is usually only used with Tracer.nextSpan(extracted), unless you are
sharing span IDs between a client and a server.
A normal instrumentation pattern is creating a span representing the server
side of an RPC. Extractor.extract might return a complete trace context when
applied to an incoming client request. Tracer.joinSpan attempts to continue
the this trace, using the same span ID if supported, or creating a child span
if not. When span ID is shared, data reported includes a flag saying so.
Here’s an example of B3 propagation:
┌───────────────────┐ ┌───────────────────┐
Incoming Headers │ TraceContext │ │ TraceContext │
┌───────────────────┐(extract)│ ┌───────────────┐ │(join)│ ┌───────────────┐ │
│ X─B3-TraceId │─────────┼─┼> TraceId │ │──────┼─┼> TraceId │ │
│ │ │ │ │ │ │ │ │ │
│ X─B3-ParentSpanId │─────────┼─┼> ParentSpanId │ │──────┼─┼> ParentSpanId │ │
│ │ │ │ │ │ │ │ │ │
│ X─B3-SpanId │─────────┼─┼> SpanId │ │──────┼─┼> SpanId │ │
└───────────────────┘ │ │ │ │ │ │ │ │
│ │ │ │ │ │ Shared: true │ │
│ └───────────────┘ │ │ └───────────────┘ │
└───────────────────┘ └───────────────────┘Some propagation systems only forward the parent span ID, detected when
Propagation.Factory.supportsJoin() == false. In this case, a new span ID is
always provisioned and the incoming context determines the parent ID.
Here’s an example of AWS propagation:
┌───────────────────┐ ┌───────────────────┐
x-amzn-trace-id │ TraceContext │ │ TraceContext │
┌───────────────────┐(extract)│ ┌───────────────┐ │(join)│ ┌───────────────┐ │
│ Root │─────────┼─┼> TraceId │ │──────┼─┼> TraceId │ │
│ │ │ │ │ │ │ │ │ │
│ Parent │─────────┼─┼> SpanId │ │──────┼─┼> ParentSpanId │ │
└───────────────────┘ │ └───────────────┘ │ │ │ │ │
└───────────────────┘ │ │ SpanId: New │ │
│ └───────────────┘ │
└───────────────────┘Note: Some span reporters do not support sharing span IDs. For example, if you
set Tracing.Builder.spanReporter(amazonXrayOrGoogleStackdrive), disable join
via Tracing.Builder.supportsJoin(false). This will force a new child span on
Tracer.joinSpan().
TraceContext.Extractor<C> is implemented by a Propagation.Factory plugin. Internally, this code
will create the union type TraceContextOrSamplingFlags with one of the following:
* TraceContext if trace and span IDs were present.
* TraceIdContext if a trace ID was present, but not span IDs.
* SamplingFlags if no identifiers were present
Some Propagation implementations carry extra data from point of extraction (ex reading incoming
headers) to injection (ex writing outgoing headers). For example, it might carry a request ID. When
implementations have extra data, here’s how they handle it.
* If a TraceContext was extracted, add the extra data as TraceContext.extra()
* Otherwise, add it as TraceContextOrSamplingFlags.extra(), which Tracer.nextSpan handles.