spatie/flare-client-php
The PHP client for Flare: capture errors, exceptions, and traces from PHP 8.2+ apps and send them to Flare for debugging. Works standalone or with Laravel via laravel-flare.
First, pick the right package. spatie/flare-client-php is the framework-agnostic engine: it builds reports (and now traces/logs) and ships them to Flare. If you are in Laravel, you almost certainly want spatie/laravel-flare, which wires this package into the exception handler, queue events, and a config/flare.php. Reach for flare-client-php directly when you have a non-Laravel app (plain PHP, Symfony, a worker/daemon, a CLI tool), or when you need to instrument something Laravel's package doesn't cover.
Minimal setup:
composer require spatie/flare-client-php
Grab a project API key from flareapp.io, then boot the client once in your front controller/bootstrap and register the handlers:
use Spatie\FlareClient\Flare;
$flare = Flare::make(getenv('FLARE_KEY'))
->registerFlareHandlers()
->registerExceptionHandler()
->registerErrorHandler()
->registerShutdownHandler();
Your first use case is usually the boring one: uncaught exceptions in a queue worker or CLI script that never show up in your web logs. Once registerExceptionHandler() is in place, $flare->report($throwable) is the manual entry point for anything you catch yourself.
Where to look first:
flareapp.io/docs/php — the README deliberately delegates to them (general/introduction, then logs/introduction, performance/sampling, data-collection/*, attribute-providers, application-lifecycle).UPGRADING.md if you are coming from v1/v2 — v3 is a rewrite.src/ for the actual contracts (Flare, Config, recorders, samplers, senders) since method names drift between majors.CHANGELOG.md per-release notes are unusually descriptive here; they're the fastest way to learn what's newly possible (e.g. Flare::start()/end(), DynamicSampler, daemon sender).Boot once, flush per unit of work. The client is stateful: trace IDs, span buffers, pending reports, and the lifecycle object all live on the instance. Register it in your container as a singleton, then in long-running processes (queue workers, RoadRunner/Swoole, custom daemons) close out each unit of work so a trace from job #1 doesn't attach to job #900:
$flare->lifecycle->flush(); // send what's pending
// and reset per-request/per-job state before handling the next item
v3 added Flare::start() / Flare::end() for exactly this — tie the trace lifetime to the request/job lifetime rather than to process lifetime.
Report exceptions, don't re-implement logging. Let the handlers catch uncaught throwables and call $flare->report($e) only for things you deliberately swallow. Recorders enrich the report with request/command/job context for free, so resist the urge to dump everything into a custom context array.
Use $flare->log() for application logs. v3 replaced reportMessage() with a first-class logger that records OpenTelemetry-format log entries and uses Monolog's Level enum. If you already use Monolog, write a thin PSR-3 handler that forwards to $flare->log() so log lines land next to the trace that produced them.
Prefer attribute providers over ad-hoc arrays. The v3 contracts — RequestAttributesProvider, ResponseAttributesProvider, RouteAttributesProvider, CommandAttributesProvider, JobAttributesProvider, UserAttributesProvider — are the extension point for "add the tenant ID / user ID / feature flag to every span and report." Implement one provider and every recorder picks it up; stuffing the same keys into individual recorder calls is how you end up with inconsistent context.
Sample by entry point, not globally. DynamicSampler + SamplingRule lets you say "health checks 0%, checkout 100%, webhooks 10%" from a single config. If you participate in distributed tracing, honour the inbound W3C traceparent: samplers receive ?bool $parentSampled, and returning false/true there lets you inherit or override the upstream decision.
Watch for subtask vs. root traces. JobRecorder and QueueRecorder ship with the client. Jobs and commands executed inside an existing trace attach as subtasks; dispatched cold they start a new root. If your queue dashboard shows a flat list where you expected nesting, check whether the dispatch happened inside a traced request.
Censor before you go to production. Cookies, session data, and request body fields are all censorable, with dot and wildcard syntax for nested body keys (user.password, card.*). Also confirm the git remote URL is credential-free — the client strips embedded credentials, but it's worth verifying your own collectors.
Choose grouping deliberately. Defaults are usually fine, but FullStacktraceAndExceptionClassAndCode and the other grouping overrides exist so a noisy exception doesn't shatter into hundreds of issues (or collapse into one useless bucket). Set this early; re-grouping later loses history.
Route through the daemon when it's there. v3 bundles DaemonSender, which pushes reports/traces/logs through the local Flare daemon and falls back to curl when it's unreachable. That's a latency win on busy apps; on serverless or read-only containers, expect to be on the curl path and budget for it.
config/flare.php here and no facade. If you're hunting for a config file, you want spatie/laravel-flare. This package is configured by constructing Flare/Config yourself.reportMessage(), includeStackTraceWithMessages(), MessageLevels, sendReportsImmediately(), Flare::reset(), error solutions, and Tracer::addRawSpan() (now addSpan()) are all gone in v3. collectLogs()/ignoreLogs() were renamed to collectLogsWithErrors()/ignoreLogsWithErrors(). Read UPGRADING.md before copying anything from a blog post.$flare->tracer, $flare->backTracer, $flare->sentReports, $flare->ids, $flare->time, $flare->lifecycle, $flare->logger, $flare->reporter. Code that called $flare->tracer() will break silently-ish (property access on a closure).$flare->sentReports (or log around the sender) when a report "disappeared"; a bad/revoked API key shows up as a successful-looking no-op rather than a crash.registerExceptionHandler() — you want the same boot path everywhere. flare:test (from the Laravel package) sends a test trace; the client also exposes a test-trace helper for non-Laravel setups.composer.json constraint of the version you install, and read the release notes for the minor you're on.expectAttributeIf helper (3.2.1) show the intended style — assert on attributes rather than on serialized JSON strings.report() in a catch gives you two issues for one bug. Pick one owner per exception path.How can I help you explore Laravel packages today?