skip to content

Report Callbacks & Throttling

Laravel's withExceptions() hooks decide which exceptions get logged, at what level and how often, through report() callbacks, dontReport and throttle(). Interviewers ask how to stop a log flood.

on this pageshow

explore

questions

5

In Laravel, what does the report() helper do, and when would you call it instead of letting an exception propagate?

level: juniorimportance: must knowfreq 50%

answer

  1. record it, keep going
  2. returns void, renders nothing
  3. a string is wrapped in Exception
  4. report_if, report_unless, rescue()
  5. dontReportDuplicates tracks object identity

basics

~20 s

report($e) passes a Throwable to Laravel's exception handler, which applies its filters, throttling, callbacks and default logging, then returns so your code carries on. Call it in a catch block when a failure must be recorded without failing the request.

solid answer

~40 s

`report()` is a global helper that resolves the `ExceptionHandler` from the container and calls its `report()` method. The exception goes through the same pipeline as an uncaught one, `dontReport`, `throttle()`, report callbacks and the default log, but nothing is rendered and the helper returns `void`, so the request or job continues. Passing a string wraps it in a plain `Exception`. Use it for best-effort side work: the booking is saved, the confirmation SMS fails, so you catch, `report($e)` and return a fallback instead of showing an error page. `report_if()` and `report_unless()` add a condition, and `rescue(fn () => ..., $fallback)` packages try, report and return-a-default in one call. If an instance can be reported twice, for example reported in a catch and then rethrown, `$exceptions->dontReportDuplicates()` makes Laravel report each exception object once.

code

php · 27 lines
php
<?php

namespace App\Actions;

use App\Models\Booking;
use App\Services\SmsGateway;
use Throwable;

class ConfirmBooking
{
    public function __construct(private SmsGateway $sms) {}

    public function __invoke(Booking $booking): bool
    {
        $booking->update(['status' => 'confirmed']);

        try {
            $this->sms->send($booking->phone, 'Your booking is confirmed.');
        } catch (Throwable $e) {
            report($e); // recorded; the booking stays confirmed

            return false;
        }

        return true;
    }
}

go deeper

for a junior

Remember report($e) logs through the handler and returns nothing, so code after it keeps running.

for a middle

Explain that report() uses the full handler pipeline, and when report_if, report_unless or rescue() fit better than a try/catch.

for a senior

Decide which failures should fail the request and which should be reported and survived, and switch on dontReportDuplicates when code reports then rethrows.

for a principal

Set a team rule for swallowed exceptions: every catch that continues must report, so best-effort work never fails silently.

## What report() does Laravel's **exception handler** (`Illuminate\Foundation\Exceptions\Handler`, bound to the `Illuminate\Contracts\Debug\ExceptionHandler` contract) has two jobs: **reporting** a failure (logging it or forwarding it) and **rendering** it into a response. The framework calls both for an exception nobody caught. The global **`report()` helper** lets your own code trigger only the first half: ```php function report($exception): void ``` - It accepts a `Throwable` or a string; a string becomes `new Exception($message)`. - It resolves the handler from the container and calls `report()` on it. - It returns `void`. It does not rethrow, render a page or stop the request. Because it goes through the real handler, everything configured in `bootstrap/app.php` still applies: the `dontReport` list, `ShouldntReport`, `dontReportWhen`, `throttle()`, the exception's own `report()` and `context()` methods, the registered report callbacks and `level()`. `report()` is not a way to force an entry past those filters. ## When to report and continue, and when to let it propagate The choice is about whether the failure should fail the operation. | Situation | Better choice | |---|---| | Core work failed: the order could not be saved | Let it propagate; the handler reports it and renders an error | | Side work failed: the confirmation SMS could not be sent | Catch, `report($e)`, continue with a fallback | | A cache warm-up or analytics ping failed | `rescue()` with a default value | | The failure is expected and meaningless to operators | Handle it without reporting, or mark the class `ShouldntReport` | The danger in the second row is swallowing an exception silently. A bare `catch (Throwable $e) { return false; }` hides the failure from everyone; adding `report($e)` keeps the operator informed while the user gets a working page. ## Shortcuts: report_if, report_unless and rescue Three helpers wrap common patterns: - `report_if($condition, $exception)` reports only when the condition is true. - `report_unless($condition, $exception)` reports only when it is false. - `rescue(callable $callback, $rescue = null, $report = true)` runs the callback, and if it throws, reports the exception and returns the `$rescue` value. `$rescue` may be a closure that receives the exception, and `$report` may be `false` or a closure deciding per exception. So `$sent = rescue(fn () => $sms->send($phone, $text), false);` either returns the send result or reports the failure and returns `false`. ## Where the framework already reports for you Knowing where Laravel calls the handler itself tells you where an explicit `report()` would be redundant: - **HTTP requests:** the HTTP kernel reports any exception that escapes the route pipeline, then renders it. - **Queued jobs:** the queue worker reports an exception a job throws, after it has released the job for a retry or marked it failed. - **Artisan commands:** an uncaught exception in a command is reported and then printed to the console. So inside a job, catching an exception, calling `report($e)` and rethrowing records the same failure twice: once by you and once by the worker. Either rethrow without reporting, or report and return without rethrowing, or turn on deduplication. ## Deduplication with dontReportDuplicates() Reporting in a catch block and then rethrowing is a common way to report the same failure twice: once by your `report($e)` and again by the framework when the rethrown exception goes uncaught. Calling `$exceptions->dontReportDuplicates()` in `bootstrap/app.php` turns on deduplication: 1. Every exception the handler actually reports is recorded in a `WeakMap`, keyed by the exception **object**. 2. Before reporting, the handler checks the map and skips an instance it has already reported. 3. The `WeakMap` holds no strong reference, so recorded exceptions are still garbage-collected. Deduplication is by **identity**, not by message or class. Two separate `new RuntimeException('Gateway timeout')` objects are two reports. Floods of similar exceptions are a job for `throttle()`, not for deduplication. ## Common mistakes - Believing `report()` rethrows, so the code after it never runs. - Expecting `report()` to return a response or a boolean you can branch on. - Calling `report()` and rethrowing without `dontReportDuplicates()`, then seeing each failure twice. - Assuming `dontReportDuplicates()` collapses exceptions with the same message. - Using `report()` to log an exception class that is in `dontReport` and wondering why nothing appears.

  • Does report() bypass the dontReport list or throttle()?
    No. `report()` calls the same handler the framework uses for uncaught exceptions, so `dontReport`, `ShouldntReport`, `dontReportWhen` and `throttle()` all apply, followed by the exception's `report()` method, the report callbacks and the default log.
  • With dontReportDuplicates() on, is a second exception with the same message reported?
    Yes. Deduplication is keyed by object identity: the handler records each reported instance in a `WeakMap` and skips only that same object. A new exception with the same class and message is a separate report; use `throttle()` to limit floods of similar exceptions.
  • What can the second and third arguments of rescue() be?
    The second is the fallback returned when the callback throws; if it is a closure, it is called with the exception. The third, default `true`, decides whether to report; pass `false` to swallow silently or a closure that receives the exception and returns a boolean.

saying these in an interview costs you the question

  • report() rethrows the exception after logging it
  • report() writes straight to the log, skipping dontReport and throttle()
  • rescue() swallows exceptions without reporting them by default
  • dontReportDuplicates() collapses exceptions that share a message
  • report() returns true when the exception was logged
open as a page

In Laravel 13, how do you register a report callback for one exception type in bootstrap/app.php, and how do you keep that exception out of the default log?

level: middleimportance: must knowfreq 55%

basics

~10 s

Inside withExceptions() in bootstrap/app.php, call $exceptions->report(function (SmsGatewayException $e) { ... }); the closure's type-hint selects the class. Laravel still logs the exception afterwards unless you chain ->stop() or the closure returns false.

open as a page

In Laravel 13, which exceptions does the handler never report by default, and how do dontReport, ShouldntReport and stopIgnoring change that?

level: middleimportance: should knowfreq 40%

basics

~10 s

Laravel skips an internal list: HTTP exceptions (so any abort()), validation, authentication, authorization, missing-model, CSRF-token and origin-mismatch errors. dontReport() and the ShouldntReport interface add types, dontReportWhen() adds a condition, stopIgnoring() removes a listed class.

open as a page

In Laravel 13, how do you control the log level and the extra context an exception's default log entry carries?

level: middleimportance: should knowfreq 28%

basics

~10 s

$exceptions->level(Type::class, LogLevel::WARNING) sets the PSR-3 level for a type; unmapped exceptions log at error. Context comes from the exception's context() method and $exceptions->context() closures, plus the logged-in user's id and the exception itself.

open as a page

An SMS provider outage makes a Laravel 13 app report thousands of identical exceptions a minute; how do you throttle that reporting with withExceptions()?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Register $exceptions->throttle() in bootstrap/app.php and return Limit::perMinute(n), optionally ->by() a key, for the SMS exception, or Lottery::odds(1, 1000) to sample. Returning null leaves other exceptions unthrottled.

open as a page