onlime/exception-report-bundle
onlime/exception-report-bundle is a Symfony bundle for configurable exception reporting. It captures application errors, attaches useful request and environment context, and routes reports to logs, email, or custom handlers, helping teams monitor failures, triage issues, and debug faster.
This package gives you an EmailReporter (renamed from EmailReport in 0.3.5) that mails you details about errors instead of making you tail a log file.
composer require <vendor>/<package>
php artisan vendor:publish --tag=<package>-config
config/<package>.php, then confirm your mailer works — MAIL_MAILER=log locally means "reports" go to storage/logs/laravel.log, not your inbox.EmailReporter class itself and the config file — those two files are the entire surface area you'll touch day to day.First use case: register the reporter in your exception handling so unhandled exceptions in production produce an email. On Laravel 11+:
// bootstrap/app.php
->withExceptions(function (Exceptions $exceptions) {
$exceptions->report(function (Throwable $e) {
app(\Vendor\Package\EmailReporter::class)->report($e);
});
})
On Laravel 10 and below, the same call goes in App\Exceptions\Handler::report(). Guard it with if (app()->isProduction()) — you do not want this in local or test.
Handler-level reporting (the default). Put a single hook in the exception handler and let it cover everything — HTTP 500s, console command failures, queue worker crashes. This is the lowest-effort integration and the one to start with.
Report non-exception failures yourself. The most valuable use of a package like this is the stuff that doesn't throw a 500: a scheduled task that quietly returned false, a webhook payload that failed validation, a job that exhausted retries in failed(). Call the reporter directly from App\Console\Kernel::command() wrappers or a job's failed(Throwable $e) method.
Wrap it in your own reportable. Rather than sprinkling the vendor class through your app, create App\Support\ErrorReporter that composes EmailReporter, adds request/user/tenant context, applies throttling, and is the only thing your app references. When the package renames a class or changes config keys again (it already has once), you edit one file.
Queue the send. Email over SMTP is slow and can block a request that is already failing. If the reporter doesn't queue internally, wrap the call in dispatch(fn () => ...) or a queued mailable so a 500 response isn't held hostage by a 5-second SMTP timeout.
Feed it context, not just the stack trace. Most of these reporters accept a context array or let you attach data. Include the route, the authenticated user ID, the tenant/workspace, and a correlation ID — that's what turns the email from "something broke" into something you can act on without opening the server.
Deduplicate before you send. In a loop over 10k jobs, one bad row generates 10k emails. Gate with Cache::add("reported:{$fingerprint}", true, now()->addMinutes(15)) keyed on exception class + file + line, or rely on Laravel's $dontReport plus your own fingerprinting.
0.3.5 is a breaking rename. EmailReport → EmailReporter. There's no mention of a class_alias shim, so any use Vendor\Package\EmailReport;, container binding, published config referencing the class string, or subclass will fatal on upgrade. Grep your codebase and config for EmailReport before running composer update, and check config/<package>.php — published configs are not updated by the package and are the classic place a stale class name hides. If you maintain your own wrapper (above), you're insulated.
Failures are now silent by design. Since 0.3.5 the reporter swallows its own exceptions and writes to the log instead of throwing. That's the right call for an error reporter, but it means a misconfigured mailer, an expired API key, or a rate-limited SMTP provider produces no visible signal — your error emails just stop. Mitigations:
try/catch around the reporter call to detect a broken mailer — it won't fire anymore.Beware recursive reporting. If the log channel or mail transport failure can itself raise an exception that flows back into the handler, you get a loop. Keep the reporter's failure path strictly log-only and make sure your handler's report() excludes the package's namespace.
Throttle and cap. Mailboxes and providers have limits. Combine a per-fingerprint cache gate with a global ceiling (e.g. max 10 reports/minute via RateLimiter) so a traffic spike doesn't turn into a mail storm or a provider ban.
Config caching. php artisan config:cache in deploy means edits to the package config need a re-cache. If recipients "disappear" after a deploy, that's usually it — or the env var was only present in the local .env, since config:cache snapshots at build time.
Test the failure path deliberately. Locally, set an invalid SMTP host and confirm you get a log line and no email, then fix it and confirm the email arrives. That's the only way to know the 0.3.5 silent-failure behaviour works for you rather than against you.
Watch the changelog for more renames. Two releases in, naming is clearly still settling. Pin the version ("<vendor>/<package>": "~0.3.5" or an exact constraint) in composer.json until the API stabilises at 1.0, and keep the vendor class behind your own wrapper.
How can I help you explore Laravel packages today?