Where Distributed Tracing Breaks
Two Things That Travel Alongside traceparent
In one line
Besides traceparent, baggage and tracestate also flow along with the same request. One carries business values we chose downstream, and the other is a place where tracing tools exchange things with each other. What they have in common is that both are strings that came in from outside.
Why this was needed
The moment you open a payment trace and ask "which tenant does this request belong to," most teams get stuck. The root span has the tenant attached, but the database span six hops down does not. With that span alone you cannot tell which customer's slow query it is, so a person goes up the parents to find the root and reads the value there. You can do it with one trace, but you cannot do it when aggregating ten thousand.
baggage exists for exactly that place. Once you put a value in, the same value is carried in the headers of every downstream call that branches off that request. But there are two traps here. First, a value you put in baggage does not become a span attribute on its own. It is merely in the context, so each service has to take it out and write it onto its own span before it becomes data you can query. Second, that header is carried on every downstream request. If one request calls downstream forty times, the same bytes go out forty times.
tracestate is different in nature. It is not a place to carry business values but a place where tracing tools write down their own state. If you mix the two, our values do not fit within the size the specification guarantees and are silently truncated.
How it works
The W3C Baggage specification defines the header as a key=value list. The key is an RFC 7230 token, and a value must be percent-encoded if it goes outside the defined ASCII range. The size rules are in Baggage 3.3.2 Limits — as long as the resulting baggage string has 64 entries or fewer and 8192 bytes or fewer, the platform must propagate all the entries. Beyond that condition, which entries to drop is up to the implementation, and the specification does not even fix an order. That is, 64 entries and 8192 bytes are not upper limits but the point where the guarantee ends.
The Python SDK's W3CBaggagePropagator adds its own limits on top of that. If a single entry exceeds 4096 characters it leaves that entry out, and if the whole exceeds 8192 bytes it cuts off there. What matters is that no exception is raised. If you put a 9,000-character value into baggage, the program runs without a word and goes out with only that entry missing from the header. When a report comes in from downstream that "the value is sometimes empty," you should suspect this place.
The tracestate rules are in Trace Context 3.3.1.5. The list has at most 32 entries, a value is printable ASCII of at most 256 characters (commas and equals signs are not allowed), and vendors must propagate at least 512 characters in total. When you have to truncate, you drop whole entries, dropping entries over 128 characters first, and after that dropping from the end. And if you have added or modified our entry, you move that entry to the far left — because the left is the place of "the system that wrote traceparent just now." You must leave the order of entries we did not touch as it is so that other tools' correlations survive.
How to read the four fields of traceparent was already covered in an earlier lab in this course. Here we do not split that line — you only need to remember one line of the specification, that a tracestate that arrives without traceparent must be discarded.
The trust boundary is the last piece. Baggage received in front of a public API is a string someone else wrote. If you move it as it is into span attributes, someone else decides your metric cardinality, and if you let it flow on downstream as it is, our internal services read it as our value. So at the boundary you accept by an allowlist — only known keys, only values of a set shape, up to a set count. The specification also says this in the same direction: do not put secrets in baggage, or keep baggage from being carried on requests that cross a trust boundary. While the context boundary lab in the certification course deals with choosing, on the outgoing side, what to carry per destination, the filter you build here is on the incoming side — the place where you decide which of what someone else sent you to accept as ours.
What it looks like in the field
One team put a summary of the request body in baggage to make debugging easier. It ran fine in development, but in production only the paths with many downstream calls got slower. One value was 800 bytes and that request called downstream thirty times. 24 kilobytes more were going out over the network for every single request, and on paths that hit the proxy's header size limit, a 502 occurred. What to put in baggage is decided by "do all downstream services use this value?"
Another incident was on the opposite side. At the gateway that receives partner requests, they moved incoming baggage as it was into span attributes, and one of them was an id that differed on every request. One attribute split the time series into hundreds of thousands of branches, and storage cost multiplied several times in a few days. When they put an allowlist on the gateway and even checked the shape of the values, it stopped the same day. Context that comes from outside is not data but input.
What you will do in the next lab
You put in baggage and flow it on to the next service, and confirm with two spans that it does not become a span attribute on its own. You measure the header bytes of four candidate bundles to see which goes beyond the specification's guaranteed range, and you also see a 9,000-character value silently vanish. Then you build an allowlist filter for baggage that came from outside the trust boundary and leave on the span what remained and what was dropped. Finally, you read tracestate by the specification, build a transformation that keeps the order of incoming entries while putting our entry at the front, harden it into a rules file, and apply it to a second service.