Skip to content

[BUG] Trace API: tracestate truncation ignores the 512-character budget and the 128-character rule #126

Description

@michaelbushe

Spec requirement

Spec: W3C Trace Context, reached through OpenTelemetry Specification v1.60.0
Requirement level: MUST (truncate whole entries) and SHOULD (the rest)
Pinned source: https://www.w3.org/TR/trace-context/#tracestate-limits

TraceState is an API type (specification/trace/api.md:278), and the spec delegates its rules rather than restating them:

These operations MUST follow the rules described in the W3C Trace Context specification.

specification/trace/api.md:289.

W3C §3.3.1.5 then says:

Vendors SHOULD propagate at least 512 characters of a combined header.

the vendor MUST truncate whole entries. Entries larger than 128 characters long SHOULD be removed first. Then entries SHOULD be removed starting from the end of tracestate.

The problem

We implement one third of the truncation procedure, and we drive it off the wrong trigger.

Rule Level Implemented
Truncate whole entries MUST yes
Entries over 128 characters removed first SHOULD no
Then remove starting from the end SHOULD yes
Propagate at least 512 characters of a combined header SHOULD no

There is no 512 and no 128-character handling anywhere in lib/. What we do instead is cap the member count at 32 (W3C §3.3.1.1, "There can be a maximum of 32 list-members in a list") and drop from the end when a put would exceed it.

The 32 cap is correct on its own terms, but it is a grammar limit on the list, not the truncation procedure. A tracestate with 32 short members can still exceed 512 characters and go out oversized. And when something does have to go, we drop a small oldest entry while keeping a 300-character one that the spec says should have gone first.

"Combined header" here means the tracestate header value itself, including the case where it arrives split across repeated header instances and is recombined. It is not traceparent plus tracestate. So this is measurable from TraceState alone and does not need any propagator context.

Offending code

The member cap is enforced in two places and the character budget in neither:

  • lib/src/api/trace/trace_state.dart:43 (fromString)
  • lib/src/api/trace/trace_state.dart:95 (put)

toString() at lib/src/api/trace/trace_state.dart serializes with no length check:

String toString() {
  return _entries.entries.map((e) => '${e.key}=${e.value}').join(',');
}

How to correct it

This belongs here rather than in a propagator. The 32-member cap already lives in this class, toString() is the serializer, and every propagator that emits a tracestate header should get the same behavior without reimplementing it. See dartastic_opentelemetry#295 for the SDK half, where the W3C propagator currently duplicates this serializer instead of calling it.

  1. Apply §3.3.1.5 when producing the header value: remove whole entries only, entries over 128 characters first, then from the end.
  2. Keep the existing 32-member cap. It is a separate rule and it is correct.
  3. Decide whether toString() truncates or a dedicated method does. A getter that silently drops data is surprising, so a separate method the propagator calls may be the better shape. Worth settling in this issue before implementation.
  4. Report dropped entries through OTelErrorHandling, matching how invalid entries are handled today.

Open question worth resolving here: 512 is a floor on what vendors should propagate, not a ceiling we are obliged to impose. Truncating to 512 is the conservative reading. Whether the budget should be configurable is undecided.

Notes

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghelp wantedExtra attention is neededspec-complianceOpenTelemetry specification compliance

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions