Skip to main content
Sampling is the process of deciding which traces to collect and export. Tracing every single request can be expensive at scale. Sampling lets you reduce costs while keeping visibility into your application’s behavior. This package supports two complementary sampling strategies:
AspectHead samplingTail sampling
Decision timeAt span startAt trace end
Criteria availableTrace ID, parent contextFull trace data, errors, duration
Use caseRate limiting, percentage samplingError traces, slow traces, custom rules
Multi-serviceWorks per-serviceMust 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
Configure head sampling in the traces.sampler section of config/opentelemetry.php:
'traces' => [
    'sampler' => [
        // Wrap the sampler in a parent-based sampler so upstream decisions are respected
        'parent' => env('OTEL_TRACES_SAMPLER_PARENT', true),

        // Supported values: "always_on", "always_off", "traceidratio"
        'type' => env('OTEL_TRACES_SAMPLER_TYPE', 'always_on'),

        'args' => [
            // Sampling ratio for the "traceidratio" sampler (0.0 – 1.0)
            'ratio' => env('OTEL_TRACES_SAMPLER_TRACEIDRATIO_RATIO', 0.05),
        ],
    ],
],

Sampler types

TypeBehavior
always_onKeep every trace (default)
always_offDrop every trace
traceidratioKeep a percentage of traces determined by ratio

Parent-based sampling

When parent 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.
Enable tail sampling with the environment variable:
OTEL_TRACES_TAIL_SAMPLING_ENABLED=true

How it works

When tail sampling is enabled, spans are buffered until the trace completes or the decision_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.
Use traceidratio as your head sampler fallback when tail sampling is enabled. This way, traces that don’t match any tail rule are still sampled at a configured rate instead of all being kept.

Environment variables

VariableDescriptionDefault
OTEL_TRACES_TAIL_SAMPLING_ENABLEDEnable tail samplingfalse
OTEL_TRACES_TAIL_SAMPLING_DECISION_WAITMaximum time to wait for trace completion before making a decision (milliseconds)5000
OTEL_TRACES_TAIL_SAMPLING_RULE_KEEP_ERRORSEnable the built-in errors ruletrue
OTEL_TRACES_TAIL_SAMPLING_RULE_SLOW_TRACESEnable the built-in slow trace ruletrue
OTEL_TRACES_TAIL_SAMPLING_SLOW_TRACES_THRESHOLD_MSDuration threshold for the slow trace rule (milliseconds)2000

Built-in rules

Two rules are included and enabled by default:
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).
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 implementing TailSamplingRuleInterface:
use Keepsuit\LaravelOpenTelemetry\TailSampling\TailSamplingRuleInterface;
use Keepsuit\LaravelOpenTelemetry\TailSampling\SamplingResult;
use Keepsuit\LaravelOpenTelemetry\TailSampling\TraceBuffer;

class MyCustomRule implements TailSamplingRuleInterface
{
    public function initialize(array $options): void
    {
        // Configure the rule using options from config
    }

    public function evaluate(TraceBuffer $trace): SamplingResult
    {
        // Return SamplingResult::Keep to keep the trace
        // Return SamplingResult::Drop to drop the trace
        // Return SamplingResult::Forward to pass the decision to the next rule
    }
}
The TraceBuffer gives you access to:
  • getSpans() — all spans in the trace
  • getRootSpan() — the root span (no parent)
  • getTraceDurationMs() — total trace duration in milliseconds
  • getDecisionDurationMs() — time elapsed since the first span was buffered
Register your custom rule in config/opentelemetry.php:
'traces' => [
    'sampler' => [
        'tail_sampling' => [
            'enabled' => true,
            'rules' => [
                MyCustomRule::class => [
                    // Options passed to initialize()
                    'my_option' => 'value',
                ],
            ],
        ],
    ],
],
Rules are evaluated in the order they appear in the array, so place higher-priority rules first.

Build docs developers (and LLMs) love