Skip to content

Let the sampler vote on traces inherited from a traceparent#75

Merged
rubenvanassche merged 1 commit into
mainfrom
feat/sampler-parent-decision
May 20, 2026
Merged

Let the sampler vote on traces inherited from a traceparent#75
rubenvanassche merged 1 commit into
mainfrom
feat/sampler-parent-decision

Conversation

@rubenvanassche

Copy link
Copy Markdown
Member

Summary

  • Tracer::startTrace no longer treats a W3C traceparent (or an explicit traceId/spanId/sample tuple) as the final word on sampling. Trace continuity (the traceId and parent spanId) is now decoupled from the sampling decision, and the configured sampler is always called.
  • Sampler::shouldSample() gains an optional ?bool $parentSampled = null that carries the parent's sampled flag (or null when there is no parent).
  • AlwaysSampler and NeverSampler ignore the hint. RateSampler honors it when set. DynamicSampler lets a matching rule win, falls back to the parent decision, then to base_rate.
  • The ?bool $sample parameter on startTrace is dropped. startFromTraceparent and startFromDefined were inlined and removed.

Why

Previously, forJob rules in DynamicSampler were effectively dormant. Jobs almost always run with an inherited traceparent (written by QueueRecorder::createPayloadUsing), and that traceparent set the sampling decision directly without ever calling the sampler. With this change, rules can opt a job in or out regardless of the dispatching context's decision.

Breaking changes

  • Sampler::shouldSample() signature change. Acceptable pre-v3. Custom samplers can ignore $parentSampled to preserve existing behavior.
  • Tracer::startTrace() drops ?bool $sample. The only legitimate user of this parameter was traceparent propagation, which is now handled internally.

UPGRADING.md is updated accordingly.

Notes

A latent behavior question surfaced during this change: JobRecorder rewrites the inherited traceparent to sampled=0 when a job matches the ignore list. With rule-overrides-parent semantics, a forJob rule could re-sample a force-unsampled job. Whether the ignore list should be a hard veto or just a default is a separate decision worth following up on.

Previously, when a trace was continued from a W3C traceparent (or an
explicit traceId/spanId/sample tuple), the sampling decision was taken
directly from the parent and the configured sampler was bypassed. That
made DynamicSampler rules like forJob effectively dormant in practice,
since jobs almost always run with an inherited traceparent.

Trace continuity (the traceId and parent spanId) is now decoupled from
the sampling decision. The sampler is always called, and receives the
parent's sampling flag as a hint via a new optional parameter.

- Sampler::shouldSample gains ?bool \$parentSampled = null
- AlwaysSampler and NeverSampler ignore the hint
- RateSampler honors the hint when set, otherwise rolls the rate
- DynamicSampler evaluates rules first (a match overrides the parent),
  then falls back to the parent decision, then to base_rate
- Tracer::startTrace drops the ?bool \$sample parameter; startFromTraceparent
  and startFromDefined are inlined

Breaking change, acceptable pre-v3.
@rubenvanassche rubenvanassche merged commit ccbf309 into main May 20, 2026
19 checks passed
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.

1 participant