Updated June 2026. Tested on Laravel 13 and PHP 8.4. Reporting used to be configured in App\Exceptions\Handler. Since Laravel 11 it lives in the withExceptions() closure in bootstrap/app.php, which is what this post uses. If you have not read it yet, start with Introduction to Exception Handling in Laravel.

Reporting is the half of exception handling the user never sees. When something goes wrong, you want a record of it so you can find out what happened and fix it. Out of the box Laravel does this for you, and it gives you plenty of room to send errors wherever your team actually looks.

You already get logging for free

Without configuring anything, every reported exception is written to a log file under storage/logs. By default that is daily files, so each day gets its own file and you can go straight to the right one. Open it and you have the message, the stack trace, and the context for everything that went wrong that day. That is the baseline, and for a small app it is often all you need.

Some exceptions are not worth reporting

Not every exception is a bug. A failed login, a 404, a validation error, these are normal parts of running an app, and logging every one of them would bury the real problems in noise. So before reporting anything, Laravel checks whether the exception is one it should ignore, and it ships with a sensible list of types it never reports.

That internal list includes the everyday, expected exceptions.

// Reported by Laravel internally as "do not report"
AuthenticationException::class,   // not logged in
AuthorizationException::class,    // not allowed
HttpException::class,             // abort(404), abort(403), ...
HttpResponseException::class,
ModelNotFoundException::class,    // findOrFail miss
TokenMismatchException::class,    // stale CSRF token
ValidationException::class,       // failed form validation

You can add your own to that list. In bootstrap/app.php, tell the exceptions config which types to stop reporting.

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->dontReport([
        PaymentDeclinedException::class,
    ]);
})

If you would rather keep that decision next to the exception itself, implement the Illuminate\Contracts\Debug\ShouldntReport interface on the exception class and Laravel will skip reporting it without any central config.

use Illuminate\Contracts\Debug\ShouldntReport;

class PaymentDeclinedException extends Exception implements ShouldntReport
{
    //
}

One more useful switch: if the same exception fires many times in a single request, you can collapse the duplicates so your log is not spammed.

$exceptions->dontReportDuplicates();

Reporting somewhere other than the log

A log file is fine until your app has real traffic, at which point nobody is tailing storage/logs at three in the morning. Laravel's logging is built on the Monolog library and ships with a stack of channels you can switch on in config/logging.php.

  • single: one storage/logs/laravel.log file
  • daily: a new log file per day
  • slack: post errors to a Slack channel
  • syslog and errorlog: the system log and PHP's error log
  • papertrail and other hosted services
  • stderr: useful in containers, where logs are collected from the output stream

There is also a special stack channel that fans out to several of the above at once, so you can keep daily files and also push the serious stuff to Slack. You pick the default channel and compose the stack in config/logging.php, no code changes needed.

Custom reporting logic

Sometimes you want to do something specific when a particular exception is reported, send it to an error tracker like Sentry, attach extra context, or fire an alert. You have two clean places to put that.

The first is a reportable callback in bootstrap/app.php. Register a closure for a given exception type and Laravel runs it whenever that exception is reported.

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->report(function (PaymentDeclinedException $e) {
        // send to your error tracker, alert a channel, etc.
    });
})

By default Laravel still also writes the exception to the log after your callback runs. If this callback should be the only reporting for that type, stop the default log by chaining ->stop() or returning false.

$exceptions->report(function (PaymentDeclinedException $e) {
    // ...
})->stop();

The second place is a report() method directly on the exception class. If an exception knows how to report itself, put the logic there and keep it self contained.

class PaymentDeclinedException extends Exception
{
    public function report(): void
    {
        // your own reporting for this exception
    }
}

Be aware of one behaviour here: once Laravel finds a report() method on the exception, it treats reporting as handled and will not also write it to the default log. So if you still want the log entry, do the logging yourself inside that method, or return nothing special and let it fall through. The reportable callback approach is usually easier to reason about because the default logging only stops when you explicitly say ->stop().

Adding context and log levels

Two smaller touches worth knowing. You can attach global context that gets added to every log entry, handy for things like a request id.

$exceptions->context(fn () => ['request_id' => request()->header('X-Request-Id')]);

And you can set the log level per exception type, so an expected but notable event is logged as a warning rather than an error.

$exceptions->level(PaymentDeclinedException::class, LogLevel::WARNING);

Wrapping up

Reporting is about you, not the user. Laravel logs every unhandled exception to storage/logs by default, skips the noisy expected ones, and lets you add your own to the ignore list with dontReport() or the ShouldntReport interface. When a log file is not enough, the channels in config/logging.php, including the fan out stack channel, send errors to Slack and beyond. And for anything bespoke, a report() callback in bootstrap/app.php or a report() method on the exception gives you full control.

Next, the other half of the story: Rendering Exceptions in Laravel, where the exception becomes the response your user sees. Questions welcome in the comments.