Skip to content

✨ add trace sampling rules - #199

Open
rgaignault wants to merge 6 commits into
mainfrom
roman.gaignault/trace-sampling-rules
Open

✨ add trace sampling rules#199
rgaignault wants to merge 6 commits into
mainfrom
roman.gaignault/trace-sampling-rules

Conversation

@rgaignault

@rgaignault rgaignault commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Motivation

Let Electron applications reduce noisy main-process traces with targeted sampling rules.

Changes

Adds ordered traceSamplingRules for 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:unit
  • yarn typecheck
  • yarn playwright test -c e2e --project=e2e e2e/scenarios/trace-sampling-rules.scenario.ts
  • Playground: use Main Fetch — Keep Trace and Main Fetch — Drop Trace

Checklist

  • Tested locally (playground)
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.
  • Updated related documentation.
  • Agentic code review findings addressed or explicitly dismissed.

@rgaignault

Copy link
Copy Markdown
Contributor Author

@codex review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.configure with optional chaining, so a dd-trace internals change would silently skip customer rules rather than surfacing a misconfiguration. Rules also apply only after SDK init(), so spans between instrument import and init() still use dd-trace defaults.


Findings

  • [Minor] Silent sampler wiring_prioritySampler?.configure optional chain means configured rules can be ignored without any observable failure.
  • [Nit] Pre-init sampling window — Traces started after @datadog/electron-sdk/instrument but before init() bypass traceSamplingRules.

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
Loading

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.

Open in Web View Automation 

Sent by Cursor Automation: electron-sdk reviews

Comment thread src/domain/tracing/Tracing.ts
Comment thread src/domain/tracing/Tracing.ts
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 5e2a1dc543

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

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".

@rgaignault
rgaignault marked this pull request as ready for review August 17, 2026 06:41
@rgaignault
rgaignault requested a review from a team as a code owner August 17, 2026 06:41

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?.configure optional 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
Loading

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.

Open in Web View Automation 

Sent by Cursor Automation: electron-sdk reviews

Comment thread src/domain/tracing/Tracing.ts
@sbarrio
sbarrio requested a review from bcaudan August 17, 2026 07:05
Comment thread README.md Outdated
Comment thread playground/src/index.html Outdated
Comment thread src/config.ts Outdated
Comment thread src/config.ts
* 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[];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion: ‏we should add something like useTraceSamplingRules to configuration telemetry

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created the PR here : DataDog/rum-events-format#430, I will do an update after the schema change.

Comment thread README.md Outdated
Comment thread src/domain/tracing/SpanProcessor.ts Outdated
Comment thread src/domain/tracing/SpanProcessor.ts Outdated
Comment thread e2e/scenarios/trace-sampling-rules.scenario.ts Outdated
Comment thread src/instrument/net.ts
Comment on lines +237 to +257
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;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread e2e/scenarios/trace-sampling-rules.scenario.ts Outdated
Comment thread src/domain/tracing/Tracing.ts Outdated
Comment on lines +93 to +108
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);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread src/instrument/net.ts Outdated
Comment on lines +102 to +105
traceSamplingRules: [
{ name: 'electron.main.handle', resource: 'mainNetRequest', sampleRate: 0 },
{ name: 'electron.main.handle', resource: 'mainNetRequest', sampleRate: 100 },
],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion: ‏in order to cover dd-trace behavior, what about adding:

  • name/resource only cases
  • tags matching case

@rgaignault
rgaignault requested a review from bcaudan August 18, 2026 12:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants