Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Exception Notify Laravel Package

guanguans/laravel-exception-notify

Laravel Exception Notify automatically sends Laravel exception alerts to DingTalk, Feishu, WeCom, Slack, Telegram, email, and more. Simple configuration, multiple channels, and flexible message formatting help you catch production errors fast.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its single config file:

composer require guanguans/laravel-exception-notify
php artisan vendor:publish --tag=exception-notify-config

Everything you will touch day-to-day lives in config/exception-notify.php and .env. Open the published config first and read it top to bottom before writing any code — the whole package is that file. Expect roughly four blocks: an enabled switch, an environment/scope filter, a channels list, and throttling/serialization settings (rate limit, cache store, pipes/collectors).

Minimum viable setup — one channel, production only:

# .env.example
EXCEPTION_NOTIFY_ENABLED=true
EXCEPTION_NOTIFY_ENVIRONMENT=production
EXCEPTION_NOTIFY_SLACK_WEBHOOK=https://hooks.slack.com/services/...
// config/exception-notify.php
'channels' => [
    'slack' => [
        'driver' => 'slack',
        'webhook' => env('EXCEPTION_NOTIFY_SLACK_WEBHOOK'),
    ],
],

Verify with a synthetic failure rather than waiting for a real one:

// routes/web.php (local/staging only)
Route::get('/_boom', fn () => throw new RuntimeException('exception-notify smoke test'));

Confirm three things at once: the message lands in the channel, the response is still a normal 500 (the notifier did not swallow or break the handler), and the exception still appears in storage/logs. If any of the three fails, stop and fix before adding channels.

First real use case: production 500s surfacing in the team chat instead of being discovered by a customer. Scope it to production only, pick the noisiest-tolerant channel, and leave staging off until you've measured volume.

Implementation Patterns

Zero-code auto-reporting is the default. Once enabled, the package hooks into Laravel's exception reporting path, so ordinary throw statements in controllers, jobs, and commands notify without changes to application code. Do not add try/catch blocks just to notify — that's the anti-pattern this package exists to remove.

Manual reporting for swallowed exceptions. The valuable second pattern is catching errors that never reach the handler — queue callbacks, third-party API wrappers, rescue() blocks that log-and-continue. Report those through the facade:

try {
    $this->crm->push($payload);
} catch (CrmException $e) {
    report($e);                 // normal Laravel logging
    ExceptionNotify::report($e); // explicit alert; check the facade method name in your installed version
    $this->retryLater($payload);
}

Use this sparingly. Every manual call is a place where you can accidentally alert from inside a loop.

Fan out by audience, not by exception. Instead of sending everything everywhere, define multiple channel entries and route by concern: slack-ops for 5xx, mail-oncall for PaymentException, feishu for the China-region team. Because channels are keyed config entries, this is a config change, not code — which is exactly what makes the package cheap to keep or replace.

Filter the expected noise before it starts. The single highest-leverage config is the exclusion list. ValidationException, AuthenticationException, NotFoundHttpException, ThrottleRequestsException, and ModelNotFoundException (when rendered as 404) are not incidents. If your app throws any of these on a normal request path, you will mute the channel within a day and lose the signal permanently.

Make throttling real. Rate limiting is only as good as its cache store. With the default array/file store per-request or per-container, a limit of "1 per minute" can mean "1 per minute per process." Point the package at a shared store (redis, database) so the limit is global:

'rate_limit' => ['max_attempts' => 3, 'decay_seconds' => 300],
'cache' => env('EXCEPTION_NOTIFY_CACHE_STORE', 'redis'),

Queue the send. Notifications are HTTP calls to third parties. If the package version you're on supports queueing, enable it — the exception handler runs during request teardown, and a 5-second webhook timeout there becomes a 5-second slower error page under exactly the load conditions that produce errors. If queueing isn't exposed, wrap the send yourself or accept the timeout and set timeout low (2–3s) with no retries.

Shape the payload with pipes. Customization (adding git SHA, request URL, user id, redacting fields, truncating a 400-line trace) is done by composing pipes/collectors in config rather than subclassing. Keep your custom pipes in app/ExceptionNotify/ so they survive a package upgrade, and keep them pure string transformations with no I/O — they run on the error path.

Coexist with Sentry/Flare explicitly. Decide and document who is authoritative. A good split: Sentry owns grouping, traces, and triage; this package owns the "look at this now" ping. If both page, on-call gets double-notified for one incident and eventually mutes one of them — usually the wrong one.

Local and CI must be inert. EXCEPTION_NOTIFY_ENABLED=false in .env.example defaults, phpunit.xml env overrides set to false, and a log-backed channel for the one test that asserts notification behavior. Never let a test suite hit a real webhook.

Gotchas and Tips

The handler is the worst place to throw. Your notifier runs inside Laravel's exception pipeline. A malformed webhook URL, a DNS failure, or a bad config key can mask the original exception or take down the error response entirely. Before enabling in production, deliberately break the webhook (typo the host) and confirm the app still returns a clean 500 and still logs. If it doesn't, wrap or disable until it does.

Recursion. A notifier that throws during notification can, in the worst configuration, trigger another notification. Confirm the installed version guards against re-entry; if you can't verify it quickly, put your own re-entrancy flag in the custom pipe or in the manual-report path.

Rate-limit key granularity is the tuning knob. A global counter means one noisy exception type silences every other alert for the window. If the config allows scoping by exception class or message hash, use it: throttle the known-noisy type hard, leave rare types immediate.

PII leaves your infrastructure. Stack traces, request bodies, headers, session data, and env context can all be serialized into the message. Before production: redact authorization, cookie, password/token fields, and any regulated data; truncate the trace; and get an explicit decision on which channels are approved to receive production payloads. Chat tools retain messages indefinitely and are searchable by everyone in the workspace.

Config cache traps. php artisan config:cache freezes config/exception-notify.php. Every channel credential must come from env() at build/deploy time, and rotating a webhook means a config rebuild or an env change plus cache clear — not editing the file on the server. Keep webhook URLs in your secret manager, not committed config.

Silent channel death is the default failure mode. Rotated webhook, expired DingTalk/Feishu signature, revoked Slack app → the send fails, and typically the failure is logged at most. Add a heartbeat: a scheduled job that sends a known "still alive" message daily, or an alert on repeated delivery failures. Otherwise you'll discover the channel has been dead for three weeks during an incident.

Provider-specific quirks cost more than the package does. DingTalk requires a keyword or signed secret in the payload; Feishu has its own signing scheme; Slack rejects oversized payloads; WeCom rate-limits aggressively. Budget real time per channel — doc quality varies per driver, and the "one afternoon" estimate is accurate for channel #1 and optimistic for channel #3.

Expect noise sources you forgot about. Queue worker failures (MaxAttemptsExceededException), scheduled-command failures, health-check timeouts, and bot traffic 404s all route through the handler. During the staging trial, instrument a per-day count by exception class before you enable production — this is the single most useful pre-launch measurement.

The one API most likely to drift between majors is the manual-report entry point (facade method vs. helper function) and the pipe/collector config keys. Pin ^7.0, let patch releases flow via automation, and review minor/major bumps by diffing the published config — a config diff tells you more than the changelog. 7.0.4 is dev-dependency and Rector cleanup only; no action needed.

Isolate it behind your own seam. It's a single-maintainer package. Keep the channel list and payload shaping in config plus one thin App\Support\ErrorNotifier wrapper that you call from manual sites, so swapping to Sentry, a custom webhook, or a successor package is a config and one-class change rather than a grep across the codebase.

Debugging checklist when nothing arrives: is enabled true for this env; is the config cached; does the channel key match the driver name; does the payload pass the provider's keyword/signature check; is the rate limiter already tripped from your smoke tests (clear the cache key); and does a direct ExceptionNotify::report() from tinker behave differently from a real request — which tells you whether the problem is the channel or the hook.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator