Tracestate observability both
Carries vendor-specific distributed tracing metadata alongside Traceparent — allows multiple tracing systems to coexist on the same request.
What it does
Tracestate travels alongside Traceparent and carries vendor-specific or system-specific tracing metadata that doesn't fit into Traceparent's fixed format. While Traceparent is the standardised trace ID everyone agrees on, Tracestate is the escape hatch where each vendor can add their own data.
The key design goal: multiple tracing systems can coexist on the same request without clobbering each other. Datadog adds its data, Zipkin adds its data, your custom system adds its data — all in the same Tracestate header, all preserved as the request travels through services.
Syntax
Tracestate: <vendor>=<value>[,<vendor>=<value>]*
A comma-separated list of vendor=value pairs. Each vendor owns their key — no two vendors should use the same key:
Tracestate: dd=s:1;t.dm:-0
Tracestate: vendorname=opaquevalue
Tracestate: dd=s:1;t.dm:-0,rojo=00f067aa0ba902b7
Tracestate: congo=t61rcWkgMzE,rojo=00f067aa0ba902b7
Constraints
- Maximum 32 list members (vendor=value pairs)
- Each member's key: 1–256 characters, lowercase letters, digits,
_,-,*,/ - Each member's value: 1–256 characters, printable ASCII, no commas or
= - Total header value: maximum 512 characters
- Vendor keys are registered or follow the
vendorname@systemnameformat for multi-tenant systems
What vendors put in Tracestate
Datadog:
Tracestate: dd=s:1;t.dm:-0;t.tid:674f4b18000000
s:1— sampling priority (1 = keep)t.dm:-0— decision maker (-0 = agent)t.tid— high 64-bits of a 128-bit trace ID
Zipkin B3:
Tracestate: b3=0
Simple sampling flag.
Custom/internal:
Tracestate: mycompany=region:us-east-1;deploy:canary
Always propagate, selectively mutate
The cardinal rule: always forward Tracestate unchanged unless you own a specific vendor key.
- You own
mycompanykey? You may updatemycompany=... - You don't own
ddorb3? Pass them through verbatim - Got no
Tracestateincoming? Start a fresh one with your vendor entry if needed
This is how traces survive multi-vendor environments without data loss.
Traceparent and Tracestate together
They're always a pair. Never forward one without the other:
// Propagating both when making outbound calls
Http::withHeaders([
'Traceparent' => $currentTraceparent,
'Tracestate' => $currentTracestate ?? '',
])->post('http://other-service/endpoint', $data);
If an incoming request has Traceparent but no Tracestate, that's valid — pass an empty Tracestate or omit it. Never generate a Tracestate without a corresponding Traceparent.
Mutating Tracestate correctly
When your service adds/updates its vendor entry:
- Add your updated entry at the beginning of the list (leftmost = most recent)
- Remove your old entry if it was already in the list
- If the list would exceed 32 members, drop entries from the end
- If your entry would exceed 128 chars, drop it (don't truncate — corrupted data is worse than missing data)
function updateTracestate(string $existing, string $myKey, string $myValue): string
{
$pairs = array_filter(explode(',', $existing), fn($p) => !str_starts_with(trim($p), "$myKey="));
array_unshift($pairs, "$myKey=$myValue");
return implode(',', array_slice($pairs, 0, 32));
}
Common mistakes and gotchas
Stripping Tracestate at service boundaries. A service that forwards Traceparent but drops Tracestate breaks Datadog's sampling pipeline, Zipkin's debug flags, and any other vendor data. Always forward both.
Modifying another vendor's key. Each key is owned by its vendor. If you don't own dd, don't touch it. Mutating another vendor's value corrupts their tracing data silently.
Exceeding the 512-char limit. In long service chains, Tracestate can grow. Each service adds entries; old ones accumulate. The spec says drop from the end when the limit is hit — but if your services aren't implementing this, the header eventually gets dropped entirely by intermediaries.
Real-world examples
Incoming request with Datadog context:
GET /api/users HTTP/1.1
Traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
Tracestate: dd=s:1;t.dm:-0;t.tid:674f4b18000000
After passing through an internal service that adds its own entry:
Traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-bb9952d88cf7f9c5-01
Tracestate: internal=region:eu-west-1,dd=s:1;t.dm:-0;t.tid:674f4b18000000
internal entry prepended; dd entry preserved unchanged; Traceparent has new span-id.
Multi-vendor trace:
Tracestate: b3=1,dd=s:1,custom=abc123
FAQ
Is Tracestate required?
No — Traceparent is required for W3C trace context; Tracestate is optional. A trace without Tracestate works fine. Tracestate only matters when vendor-specific data needs to travel with the trace. If you're using a single observability platform that doesn't use Tracestate, you might never set it.
Can I use Tracestate for my own application data?
Yes, with your own vendor key. Pick a key that's clearly yours (yourcompany, yourapp, etc.) and keep the value concise. Don't abuse it for large payloads — it's for tracing metadata, not arbitrary data transport.
What if I don't care about Tracestate and just want basic tracing?
Implement Traceparent propagation and ignore Tracestate. Your traces will work across services. You'll lose vendor-specific features (Datadog's sampling decisions traveling with the trace, etc.), but the basic trace stitching will work perfectly. Forward whatever Tracestate arrives to avoid breaking anything, even if you never read or write it yourself.
Fun fact
The Tracestate design — a shared header where multiple vendors each own a key — solved a political problem as much as a technical one. The W3C working group trying to standardise trace context in 2016–2020 included engineers from Google, Microsoft, Dynatrace, Elastic, Datadog, and Lightstep, all of whom had existing proprietary tracing headers. Getting everyone to adopt a single standard required a way for each vendor to keep their own metadata. Tracestate was the compromise: standardise the core (Traceparent) while giving everyone a namespace to carry their proprietary data alongside it. Classic standards-body diplomacy encoded in an HTTP header.