feat(node): cap attributes and events per span - #4586
Conversation
|
Size Change: +4.2 kB (+0.02%) Total Size: 20.8 MB 📦 View Changed
ℹ️ View Unchanged
|
a9ff420 to
5c7301c
Compare
fd9c549 to
896f06b
Compare
e36495f to
9365a6c
Compare
896f06b to
a8a3011
Compare
8192514 to
2fb2797
Compare
028b15c to
5b18f84
Compare
2fb2797 to
c3b0a82
Compare
5b18f84 to
3f8a2b3
Compare
cc7b6d8 to
ce6e01a
Compare
3f8a2b3 to
7068edf
Compare
ce6e01a to
a4e6d3f
Compare
7068edf to
3a0059b
Compare
a4e6d3f to
dcd14c4
Compare
Caps user-supplied attributes and events at OpenTelemetry's 128 default, reporting the dropped counts on the exported span. SDK-attached join keys are exempt.
3a0059b to
21b0b64
Compare
|
Folded into #4584. The spec requires The failure is not theoretical: the hook writes to the plain record rather than through the span's guarded writer, so a hook that enriches a span pushes it back past the cap with no trim and no dropped count, and it is then rejected as too large and lost whole. The two branches also conflicted in six files, so they were never independently reviewable in practice. Everything here moved to #4584 unchanged, plus the re-apply and four tests for it. The changeset is unchanged and still ships as its own entry. |
Problem
Stacked on #4579 — review that first.
Nothing bounded how much a single span could carry. A loop that calls
span.addEvent()per iteration, or an instrumentation layer that copies every request header onto a span, grows it without limit until the ingestion endpoint rejects the payload as too large — at which point the 413 path shrinks the batch to a single span, that span is still too big, and it is dropped entirely with a warning. You lose the whole span rather than the excess.Implements the
Span limitsrequirement of the traces capability spec.Changes
Caps user-supplied content per span, at OpenTelemetry's defaults.
droppedAttributesCount/droppedEventsCount, so a truncated span is visibly truncated rather than quietly wrong. Both are omitted when nothing was dropped.posthogDistinctId,sessionId,url.full,screen.nameandapp.statedo not count toward the cap, so a span at the limit still links back to its person and session — those are the join keys, and losing them to a noisy attribute loop would defeat the point of the span.setAttribute('plan', …)twice on a span at the cap updates rather than drops.startSpanare capped the same way as ones set later.Reviewer notes
beforeSpanSend, because a hook can add attributes.beforeSpanSendlives in feat(node): beforeSpanSend hook and per-span limits #4584, which is a sibling branch off the same base — neither PR can implement that half alone. Whichever merges second should wire it up; I have not tried to coordinate it inside either branch, since that would make each depend on the other.PostHogSpan, not at encode time, so an over-cap span never occupies memory in the first place — the point is to bound growth, not to trim on the way out.posthogDistinctIdthemselves gets the exempt treatment for that key. That seemed better than special-casing a hardcoded list.Verification
Six core tests covering both spec scenarios plus the boundary cases (overwrite-at-cap, start-time attributes, counters omitted when unused), and two node tests exercising the real client including the 128 default.
packages/core1003 pass,packages/node951 pass, typecheck clean, lint clean, public API references unchanged.Release info Sub-libraries affected
Libraries affected
@posthog/coreis also bumped (minor); it has no checkbox above.Checklist
Additive from a consumer's perspective: two new optional config keys and two optional wire fields.
SpanInitandResolvedTracesConfiggain required members, but both ship for the first time in #4579 and neither is constructible by a published consumer.Behaviour does change for a span that exceeds 128 user attributes or events — it is now truncated and marked rather than sent whole. Given the alternative today is the whole span being dropped by the endpoint, truncation is the more useful outcome, and the caps are configurable for anyone who disagrees.
If releasing new changes
pnpm changesetto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Built with Claude Code, directed by @turnipdabeets, against the
Span limitsrequirement in the traces capability spec.Two decisions worth a look: