How to debug failed and stuck queued jobs in Laravel
A queued job that fails or hangs is harder to debug than a slow request: there's no browser waiting on it, the error lands in a worker log you may not be watching, and the context that caused it (the request that dispatched the job) is somewhere else entirely. Here's how to track one down.
Where Laravel records failures
By default Laravel writes failed jobs to the failed_jobs table. Inspect and
retry them with Artisan:
php artisan queue:failed # list failed jobs with their exception
php artisan queue:retry all # re-dispatch every failed job
php artisan queue:retry <uuid> # re-dispatch one
The failed_jobs row gives you the job class, the queue, and the exception, but
not the request that dispatched it, the queries the job ran, or what the user was
doing when they triggered it. That missing context is usually what you actually
need.
Common causes
- An exception inside
handle()- the job throws and, after its retries are exhausted, lands infailed_jobs. Check the exception and stack trace first. - Timeouts - the job runs longer than its
timeout(orretry_after), and the worker kills it mid-run. A slow query or external call inside the job is the usual reason. - Serialization issues - a model the job serialized no longer exists when it
runs (
ModelNotFoundException), becauseSerializesModelsre-fetches it by id. - Stuck, not failed - the job is still on the queue but no worker is processing it: the worker died, the queue connection is wrong, or the job is blocked behind a long-running one.
See the job in the context that created it
The thing that makes queued jobs hard to debug is that the cause and the effect live in two places. Unravel ties them back together: a queued job keeps the trace of the request that dispatched it, so a checkout and the email job it queued read as one causal story instead of two unrelated log entries.
For each job, Unravel captures its queued, processing and processed phases, the queries it ran, any outbound calls, and, if it failed, the exception attached to that job's trace. You can see a job that's been queued but never processed (stuck), and a job that threw, with the full chain that led to it. Set an alerting rule on new exceptions and you hear about a failing job before the failed-jobs table piles up.
- What's captured - how jobs, phases and exceptions are modelled.
- Async first - jobs, scheduled tasks and websockets traced like requests.
- Alerting - get notified the moment a job starts failing.