Baseline — the Log facade
By default, Laravel's exception handler writes unhandled exceptions to the configured log channel. Look in storage/logs/laravel.log or your configured driver (daily, papertrail, syslog, custom).
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'errorlog'],
'ignore_exceptions' => false,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'error',
'days' => 14,
],This catches everything. Missing: grouping, trending, and anything that looks like a UI. Good enough for a side project, not for production.
Fan out with the Exceptions handler
In Laravel 11+, register reporters in bootstrap/app.php:
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Http\Exceptions\HttpResponseException;
->withExceptions(function (Exceptions $exceptions) {
// Add deploy version to every captured exception
$exceptions->context(fn () => [
'release' => config('app.release_sha'),
'node' => gethostname(),
]);
// Skip exceptions you don't care about
$exceptions->dontReport([
HttpResponseException::class,
]);
// Route custom exceptions to your tracker
$exceptions->report(function (CustomBusinessException $e) {
app('tracker')->capture($e, [
'order_id' => $e->orderId,
'severity' => 'business',
]);
});
})Enrich exceptions with user context
An exception stack without the user who hit it is half-useful. Attach user context via middleware so every captured exception in that request includes who, not just what.
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class AttachUserContext
{
public function handle($request, Closure $next)
{
if (Auth::check()) {
// For NightOwl / Nightwatch — sets the user on every exception + request
app('nightwatch')->setUser([
'id' => Auth::id(),
'email' => Auth::user()->email,
'plan' => Auth::user()->subscription_plan ?? null,
]);
}
return $next($request);
}
}Fingerprint and group
A production Laravel app throws thousands of exceptions per day. Most are duplicates. Fingerprinting rolls them into issues.
A good fingerprint uses:
- Exception class (
QueryException) - The top app-level stack frame (skip vendor frames)
- A normalized message (strip dynamic values like IDs, emails, timestamps)
Every production-grade tracker (NightOwl, Nightwatch Cloud, Sentry, Bugsnag, Flare, Rollbar) does this automatically. If you're using raw logs, you're manually grep-ing — don't.
Silence the noise at the source
Common Laravel exceptions worth suppressing before they hit your tracker (to avoid billing costs and UI clutter):
use Illuminate\Auth\AuthenticationException;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Illuminate\Session\TokenMismatchException; // CSRF — usually noise
$exceptions->dontReport([
NotFoundHttpException::class, // 404s
MethodNotAllowedHttpException::class, // 405s
ValidationException::class, // expected input errors
AuthenticationException::class, // expected auth failures
TokenMismatchException::class, // stale tabs submitting CSRF
]);Alert on signal, not noise
Page on grouped signal, not per-occurrence. The alert matrix that works for most teams:
- First seen — any new fingerprint appearing in the last hour
- Spike — existing fingerprint's rate jumps 10x over baseline
- Threshold — more than N of fingerprint X in 15 minutes, where X is in a critical path
- Regression — fingerprint that was resolved reappears after a deploy
Send all alerts to a Slack or Discord channel. Email is fine as a low-urgency fallback; never page on email.
THE EASY WAY
NightOwl fingerprints every exception with full context
NightOwl's exception watcher captures every unhandled exception with full stack, request context, user, deploy SHA, and the exact SQL / job / schedule that triggered it. Exceptions are fingerprinted and grouped into issues — with status (open, resolved, ignored), priority, assignee, comments, and activity timeline.
Alert channels ship to Slack, Discord, email, or any webhook. First-seen, spike, and threshold alerts built in.
composer require nightowl/agent
php artisan nightowl:installData lives in your PostgreSQL. Flat from $5/month. 14-day free trial.