Sampling
Sending every trace your application produces gets expensive fast and rarely tells you anything new. Sampling lets you keep a representative slice of traffic and drop the rest. When a trace is dropped, no spans are collected or sent for it.
A sampled trace is not free. Recording spans, capturing query bindings, gathering request and response data, and serialising the trace before it is shipped all add overhead to the request, command, or job that produced it. Picking a sample rate is therefore a trade-off between coverage and runtime cost.
Setting a sample rate
The default sampler is the RateSampler, which applies the same rate to every trace. The default rate is 10%. You can change it in config/flare.php:
'sampler' => [
'class' => \Spatie\FlareClient\Sampling\RateSampler::class,
'config' => [
'rate' => 0.5, // 50% of all traces will be sent to Flare
],
],
Setting the rate to 1.0 samples every trace, 0.0 drops every trace.
Sampling per kind of work
When a single uniform rate is too blunt, switch to the DynamicSampler. It applies a base rate to traces that don't match any rule, and lets you override that rate per route, path, command, or job. Health checks can be sampled rarely, checkout flows aggressively, and noisy queue jobs can follow their own logic.
use Spatie\FlareClient\Sampling\DynamicSampler;
use Spatie\FlareClient\Sampling\SamplingRule;
'sampler' => [
'class' => DynamicSampler::class,
'config' => [
'base_rate' => 0.1,
'rules' => [
SamplingRule::forPath('/health-check', 0.0),
SamplingRule::forPath('/admin/*', 1.0),
SamplingRule::forRoute('checkout.*', 1.0),
SamplingRule::forCommand('horizon:work', 0.0),
SamplingRule::forJob('App\\Jobs\\ProcessPayment', 1.0),
],
],
],
base_rate is used when no rule matches. Rules are evaluated in order, the first one that matches wins.
Rule types
| Method | Matches against | When the entry point type is |
|---|---|---|
SamplingRule::forUrl($pattern, $rate) |
The full URL (including scheme and host) | web |
SamplingRule::forPath($pattern, $rate) |
The URL path | web |
SamplingRule::forRoute($pattern, $rate) |
The matched route name or pattern (without HTTP method prefix) | web |
SamplingRule::forCommand($pattern, $rate) |
The command name (e.g. horizon:work) |
cli |
SamplingRule::forJob($pattern, $rate) |
The job name | queue |
SamplingRule::using($closure) |
Anything you want, evaluated after the handler is resolved | All |
SamplingRule::usingEarly($closure) |
Anything you want, evaluated before the handler is resolved | All |
forUrl, forPath, and forJob can run as soon as the trace starts. forRoute, forCommand, and using need the framework to resolve a handler first. See Deferred sampling below.
Pattern syntax
Patterns are literal strings with * as a wildcard. Any other character is matched literally. There is no support for ?, character classes, or full regex.
SamplingRule::forPath('/api/*', 0.1); // matches /api/users, /api/orders/123
SamplingRule::forJob('App\\Jobs\\*Email', 1.0); // matches SendWelcomeEmail, SendInvoiceEmail
SamplingRule::forCommand('horizon:*', 0.0); // matches horizon:work, horizon:status
Closure rules
When a pattern is not expressive enough, use a closure:
use Spatie\FlareClient\EntryPoint\EntryPoint;
SamplingRule::using(function (EntryPoint $entryPoint) {
if ($entryPoint->handlerName === LongRunningReportJob::class) {
return 1.0;
}
return null; // No opinion. Fall through to the next rule.
});
The closure receives the EntryPoint and returns either a rate (0.0 to 1.0) or null to defer the decision to the next rule.
SamplingRule::using() is evaluated after the framework has resolved the handler. Use SamplingRule::usingEarly() when you need to decide based only on the URL or job name and do not want to wait for routing.
Deferred sampling
Some rules cannot run when the trace starts. A web framework only knows the matched route after routing middleware runs, and a queue worker may only know the job class after it pulls the payload off the queue.
When a rule needs information that has not been resolved yet, the sampler defers its decision. The trace is sampled tentatively (so spans are still collected), and the sampler is re-evaluated as soon as the framework resolves the handler. If the sampler decides to drop the trace at re-evaluation time, the spans collected so far are discarded.
This happens automatically. The RoutingRecorder, CommandRecorder, and JobRecorder always boot for entry point detection, regardless of your collects config, so route, command, and job rules can fire at the right moment.
Custom samplers
You can replace the sampler entirely. Implement the Sampler interface:
use Spatie\FlareClient\EntryPoint\EntryPoint;
use Spatie\FlareClient\Sampling\Sampler;
class AlwaysSampler implements Sampler
{
public function __construct(protected array $config) {}
public function shouldSample(EntryPoint $entryPoint): bool
{
return true;
}
}
Register it in the Flare config:
'sampler' => [
'class' => AlwaysSampler::class,
'config' => [],
],
The config array is passed to the sampler's constructor.
If your sampler should support deferred sampling, implement DeferrableSampler instead. It adds three methods, the DynamicSampler is the reference implementation. See the PHP sampling docs for more information.