Stamp SampleRate on shape spans from a tracestate rate hint#4562
Stamp SampleRate on shape spans from a tracestate rate hint#4562erik-the-implementer wants to merge 7 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4562 +/- ##
==========================================
+ Coverage 58.11% 58.37% +0.26%
==========================================
Files 369 370 +1
Lines 40459 40674 +215
Branches 11471 11555 +84
==========================================
+ Hits 23513 23744 +231
+ Misses 16871 16856 -15
+ Partials 75 74 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Claude Code ReviewSummarySince the last review the PR has been substantially simplified: commit What's Working Well
Issues FoundCritical (Must Fix)None. Important (Should Fix)None. The iteration-6 "Important" item (no SDK-level coverage for the synthesized error-tail span) is moot now that the synthesis is removed. Suggestions (Nice to Have)
Issue ConformanceNo linked issue (flagged per project convention, as before). The PR description is thorough and now internally consistent after the rewrite — motivation (~20x undercount), full per-request behavior matrix, the Sentry-handles-errors rationale for not synthesizing spans, and the embedded-mode no-op note. The changeset accurately scopes the change. No stale references to the removed synthesis remain in the description. Previous Review StatusIteration 7 — change since last review: commit
The change is correct, well-scoped, and merge-ready; only the trivial doc nit remains. Review iteration: 7 | 2026-06-14 |
…tail
The Cloudflare worker in front of Electric head-samples successful
requests at 1:N (currently 1:20) and keeps all >= 500 responses at
sampleRate 1, weighting its exported spans accordingly. Electric only
received the sampled/not-sampled bit via traceparent, so:
1. Electric spans carried no SampleRate attribute and electric-region
aggregates under-reported worker traffic ~20x;
2. ~95% of error traces had no server-side spans at all, because the
worker's keep-on-error decision happens at export time, after a
traceparent with sampled=0 was already propagated.
The worker now sends its rate hint on every proxied request via the
W3C tracestate header (member: `electric=rate:<N>`). This commit makes
Electric honor it:
* TraceContextPlug parses the hint (missing/unparseable/rate < 1 hints
are ignored) and stashes it in the conn together with the remote
parent span context and its sampled flag.
* For sampled remote parents, ServeShapePlug stamps `SampleRate` = N
(status < 500) or `SampleRate` = 1 (status >= 500) on the
Plug_shape_get root span once the response status is known; the
shape_get.plug.stream_chunk child spans get the same attribute at
creation.
* For unsampled remote parents, a 5xx response now synthesizes a
single root request span at response time with `SampleRate` = 1,
carrying the standard root-span attributes, backdated to the request
start, and parented onto the remote span context with the sampled
trace flag forced on — so the parent-based sampler records it and it
lands in the same trace as the worker's kept-on-error spans.
Unsampled successful requests still export nothing.
Direct traffic (no remote parent) is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
f1158ed to
7fe2939
Compare
Motivation
The Cloudflare worker in front of Electric Cloud head-samples successful requests at 1:20 and keeps all >= 500 responses at sampleRate 1, weighting its exported spans accordingly. Electric only ever received the sampled/not-sampled bit via
traceparent, which causes an ~20x undercount: Electric spans carry noSampleRateattribute, so aggregates over electric-region spans under-report worker traffic ~20x relative to the worker's own (weighted) spans.Server-side errors are already captured as Sentry events, so this PR deliberately does not create extra spans to carry error attributes — it only stamps the sampling weight onto spans that are already being recorded.
The worker now sends its sampling rate alongside
traceparenton every proxied request (regardless of the sampled flag) via the W3Ctracestateheader:Tracing backends that understand sampling weights natively read an integer span attribute named
SampleRateand weight aggregates by it.Behavior matrix (per shape GET request)
SampleRateattributerate:NSampleRate = Non the root span and thestream_chunkchild spansrate:NSampleRate = 1(error responses ignore the hint — mirrors the worker's keep-all-errors-at-rate-1 semantics)rate:<1Implementation
Electric.Plug.TraceContextPlug— after the existingtraceparentextraction, parses theelectric=rate:Nmember out of the (already-decoded) tracestate carried by the extracted span context, and stores%{sample_rate_hint: N}inconn.private. Exposestrace_context/1andsample_rate_attrs/2for downstream plugs.Electric.Plug.ServeShapePlug—add_span_attrs_from_conn/1(run at span start and again fromemit_shape_telemetry/1once the final response status is known) merges theSampleRateattribute into the conn-derived attributes and stamps them onto the current root span viaadd_span_attributes/1. When the remote parent was not sampled the parent-based sampler left no recording span, so the stamp is a no-op and nothing is exported — the deliberate volume win for unsampled traffic.Electric.Shapes.Api.Response.send_stream/2— the per-requestshape_get.plug.stream_chunkchild spans (which survive the flattening in Flatten 1:1 shape GET child spans into Plug_shape_get root-span attributes #4561 because they are 1:n, not 1:1) get the sameSampleRateattribute at creation, from the stashed hint and the response status.Notes / scope decisions
Sampler'sexclude_spanssemantics and the flattening work from the base branch are untouched.MIX_TARGET=application, no OTel SDK): the propagator no-ops, so no trace context is ever stored and every path is unchanged.Tests
electric=rate:Nmembers, unparseable values, andrate:<1all map to the expected hint / ignored behavior.🤖 Generated with Claude Code