✨ add trace sampling rules - #199
Conversation
|
@codex review |
There was a problem hiding this comment.
Stale comment
PR Review — Score: 4.8 / 5
This is a well-designed feature that wires ordered trace sampling rules through dd-trace, keeps RUM resources for dropped HTTP traces without linkage, and blocks propagation of rejected traces through Electron
net— all with solid validation, unit tests, e2e coverage, and customer-facing docs. I would approve.Why 4.8: Clear config surface (
TraceSamplingRule), strict validation, correct end-to-end behavior (sampler → SpanProcessor → net propagation → RUM linkage), and an e2e scenario that exercises both drop and keep paths.Why not 5: Rule wiring relies on dd-trace private
_prioritySampler.configurewith optional chaining, so a dd-trace internals change would silently skip customer rules rather than surfacing a misconfiguration. Rules also apply only after SDKinit(), so spans between instrument import andinit()still use dd-trace defaults.
Findings
- [Minor] Silent sampler wiring —
_prioritySampler?.configureoptional chain means configured rules can be ignored without any observable failure.- [Nit] Pre-init sampling window — Traces started after
@datadog/electron-sdk/instrumentbut beforeinit()bypasstraceSamplingRules.
Architectural flow
LoadingsequenceDiagram participant App as Customer app participant Init as SDK init participant Trace as Tracing participant DD as dd-trace sampler participant Net as net patch participant SP as SpanProcessor participant Intake as Transport App->>Init: init with traceSamplingRules Init->>Trace: new Tracing config Trace->>DD: prioritySampler.configure rules App->>Net: net.request or net.fetch Net->>DD: startSpan and inject DD-->>Net: carrier with sampling priority alt trace rejected Net->>Net: skip header propagation end DD->>SP: diagnostics channel export SP->>SP: isTraceSampled trace SP->>Intake: RUM resource for HTTP spans alt trace kept SP->>Intake: span envelope else trace dropped Note over SP: RUM resource without trace ids endBefore: Main-process traces were kept by default, always linked in RUM resources, and always propagated through outgoing HTTP headers.
After: Ordered
traceSamplingRulesconfigure dd-trace's priority sampler at SDK init. Rejected traces skip the spans intake and outgoing trace headers, but HTTP spans still produce unlinked RUM resources so request observability is preserved without APM cost.Sent by Cursor Automation: electron-sdk reviews
|
Codex Review: Didn't find any major issues. You're on a roll. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
PR Review — Score: 4.8 / 5
This is a well-designed feature that wires ordered trace sampling rules through dd-trace, keeps RUM resources for dropped HTTP traces without linkage, and blocks propagation of rejected traces through Electron net — all with solid validation, unit tests, e2e coverage, and customer-facing docs. I would approve.
Why 4.8: Clear config surface (TraceSamplingRule), strict validation, correct end-to-end behavior (sampler → SpanProcessor → net propagation → RUM linkage), and an e2e scenario that exercises both drop and keep paths.
Why not 5: Rule wiring relies on dd-trace private _prioritySampler.configure with optional chaining, so a dd-trace internals change would silently skip customer rules rather than surfacing a misconfiguration.
Findings
- [Minor] Silent sampler wiring —
_prioritySampler?.configureoptional chain means configured rules can be ignored without any observable failure.
Architectural flow
sequenceDiagram
participant App as Customer app
participant Init as SDK init
participant Trace as Tracing
participant DD as dd-trace sampler
participant Net as net patch
participant SP as SpanProcessor
participant Intake as Transport
App->>Init: init with traceSamplingRules
Init->>Trace: new Tracing config
Trace->>DD: prioritySampler.configure rules
App->>Net: net.request or net.fetch
Net->>DD: startSpan and inject
DD-->>Net: carrier with sampling priority
alt trace rejected
Net->>Net: skip header propagation
end
DD->>SP: diagnostics channel export
SP->>SP: isTraceSampled trace
SP->>Intake: RUM resource for HTTP spans
alt trace kept
SP->>Intake: span envelope
else trace dropped
Note over SP: RUM resource without trace ids
end
Before: Main-process traces were kept by default, always linked in RUM resources, and always propagated through outgoing HTTP headers.
After: Ordered traceSamplingRules configure dd-trace's priority sampler at SDK init. Rejected traces skip the spans intake and outgoing trace headers, but HTTP spans still produce unlinked RUM resources so request observability is preserved without APM cost.
Sent by Cursor Automation: electron-sdk reviews
| * determines the percentage of traces to keep. Traces that do not match a rule are kept. | ||
| * @example [{ tags: { 'http.url': '*health' }, sampleRate: 0 }] | ||
| */ | ||
| traceSamplingRules?: TraceSamplingRule[]; |
There was a problem hiding this comment.
💬 suggestion: we should add something like useTraceSamplingRules to configuration telemetry
There was a problem hiding this comment.
I created the PR here : DataDog/rum-events-format#430, I will do an update after the schema change.
| function shouldPropagateTrace(carrier: Record<string, string>): boolean { | ||
| const datadogPriority = Number(carrier['x-datadog-sampling-priority']); | ||
| if (Number.isFinite(datadogPriority) && datadogPriority <= 0) { | ||
| return false; | ||
| } | ||
| if (carrier['x-b3-sampled'] === '0' || carrier['x-b3-sampled'] === 'false') { | ||
| return false; | ||
| } | ||
| const b3SamplingState = carrier.b3?.split('-')[2] ?? carrier.b3; | ||
| if (b3SamplingState === '0') { | ||
| return false; | ||
| } | ||
| const traceParent = carrier.traceparent; | ||
| if (traceParent) { | ||
| const flags = Number.parseInt(traceParent.split('-')[3] ?? '', 16); | ||
| if (Number.isFinite(flags) && (flags & 1) === 0) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } |
There was a problem hiding this comment.
💬 suggestion: is is copy/pasted from dd-trace? if so, it could be nice to add a comment around that.
Do we need to support all the b3 cases?
There was a problem hiding this comment.
This is not copied from a single dd-trace helper. We inspect the carrier because there is no public API for reading the final sampling decision. B3 is not the default, but it is a supported injection style, so a B3-only setup still needs these checks. I’ll add a short comment explaining that.
| function toDdTraceSamplingRules(rules: TraceSamplingRule[], service: string): DdTraceSamplingRule[] { | ||
| return rules.flatMap(({ service: servicePattern, sampleRate, ...rule }) => { | ||
| if (servicePattern !== undefined && !matchesGlob(servicePattern, service)) { | ||
| return []; | ||
| } | ||
| return [{ ...rule, sampleRate: sampleRate / 100 }]; | ||
| }); | ||
| } | ||
|
|
||
| function matchesGlob(pattern: string, value: string): boolean { | ||
| const escaped = pattern | ||
| .replace(/[.+^${}()|[\]\\]/g, '\\$&') | ||
| .replace(/\*/g, '.*') | ||
| .replace(/\?/g, '.'); | ||
| return new RegExp(`^${escaped}$`, 'i').test(value); | ||
| } |
There was a problem hiding this comment.
All good for sample rate.
However for service, FMU, all the spans generated by the tracer will be for the configured service.
Then, should we just not allow to configure service in the TraceSamplingRule?
| traceSamplingRules: [ | ||
| { name: 'electron.main.handle', resource: 'mainNetRequest', sampleRate: 0 }, | ||
| { name: 'electron.main.handle', resource: 'mainNetRequest', sampleRate: 100 }, | ||
| ], |
There was a problem hiding this comment.
💬 suggestion: in order to cover dd-trace behavior, what about adding:
- name/resource only cases
- tags matching case


Motivation
Let Electron applications reduce noisy main-process traces with targeted sampling rules.
Changes
Adds ordered
traceSamplingRulesfor service, operation, resource, and tag matching. The first matching rule samples the whole trace; unmatched traces are kept.Sampled-out traces are not sent to APM or propagated through Electron HTTP requests. Their RUM Resources are still sent without trace identifiers.
Note: Trace sampling only controls APM spans. The associated RUM Resource is preserved; customers who also want to discard it can do so separately through RUM beforeSend..
Adds unit and E2E coverage, documentation, and playground controls for manual testing.
Test instructions
yarn test:unityarn typecheckyarn playwright test -c e2e --project=e2e e2e/scenarios/trace-sampling-rules.scenario.tsChecklist