| Aspect | Head sampling | Tail sampling |
|---|---|---|
| Decision time | At span start | At trace end |
| Criteria available | Trace ID, parent context | Full trace data, errors, duration |
| Use case | Rate limiting, percentage sampling | Error traces, slow traces, custom rules |
| Multi-service | Works per-service | Must use Collector |
Head sampling
Head sampling makes the keep/drop decision at the very beginning of a trace, based only on the trace ID and parent context. This is fast and stateless. Good uses for head sampling:- Percentage-based sampling (e.g., keep 5% of all traces)
- Parent-based sampling (inherit the decision from an upstream service)
- Simple rate limiting
traces.sampler section of config/opentelemetry.php:
Sampler types
| Type | Behavior |
|---|---|
always_on | Keep every trace (default) |
always_off | Drop every trace |
traceidratio | Keep a percentage of traces determined by ratio |
Parent-based sampling
Whenparent is true, the sampler is wrapped in a parent-based sampler. If an incoming request carries a sampled trace context (e.g., from an upstream service or load balancer), the decision is inherited rather than re-evaluated.
Tail sampling
Tail sampling makes the keep/drop decision after a trace completes, giving you access to the full trace — including error status, total duration, and every span. This lets you keep only the traces that matter while discarding routine successful requests.Tail sampling implemented at the application level is only suitable for single-service scenarios. For multi-service tail sampling, use the OpenTelemetry Collector instead — it has visibility into the complete trace across all services.
How it works
When tail sampling is enabled, spans are buffered until the trace completes or thedecision_wait timeout expires. The package then evaluates the buffered trace against the configured rules in order. The first rule that returns Keep or Drop wins. If no rule makes a decision, the configured head sampler is used as the fallback.
Environment variables
| Variable | Description | Default |
|---|---|---|
OTEL_TRACES_TAIL_SAMPLING_ENABLED | Enable tail sampling | false |
OTEL_TRACES_TAIL_SAMPLING_DECISION_WAIT | Maximum time to wait for trace completion before making a decision (milliseconds) | 5000 |
OTEL_TRACES_TAIL_SAMPLING_RULE_KEEP_ERRORS | Enable the built-in errors rule | true |
OTEL_TRACES_TAIL_SAMPLING_RULE_SLOW_TRACES | Enable the built-in slow trace rule | true |
OTEL_TRACES_TAIL_SAMPLING_SLOW_TRACES_THRESHOLD_MS | Duration threshold for the slow trace rule (milliseconds) | 2000 |
Built-in rules
Two rules are included and enabled by default:Errors rule
Errors rule
Keeps any trace that contains at least one span with an error status code. This ensures you never lose visibility into failures.Controlled by
OTEL_TRACES_TAIL_SAMPLING_RULE_KEEP_ERRORS (default: true).Slow trace rule
Slow trace rule
Keeps any trace whose total duration meets or exceeds the configured threshold. Useful for catching performance regressions.Controlled by
OTEL_TRACES_TAIL_SAMPLING_RULE_SLOW_TRACES (default: true) and OTEL_TRACES_TAIL_SAMPLING_SLOW_TRACES_THRESHOLD_MS (default: 2000 ms).Custom rules
You can add your own sampling logic by implementingTailSamplingRuleInterface:
TraceBuffer gives you access to:
getSpans()— all spans in the tracegetRootSpan()— the root span (no parent)getTraceDurationMs()— total trace duration in millisecondsgetDecisionDurationMs()— time elapsed since the first span was buffered
config/opentelemetry.php:
