korioinc / laravel-exception-viewer
Aggregate Laravel exceptions into the database with masked request context for operations and LLM analysis.
Package info
github.com/korioinc/laravel-exception-viewer
pkg:composer/korioinc/laravel-exception-viewer
Fund package maintenance!
Requires
- php: ^8.2
- illuminate/contracts: ^11.0||^12.0||^13.0
- illuminate/database: ^11.0||^12.0||^13.0
- illuminate/http: ^11.0||^12.0||^13.0
- illuminate/support: ^11.0||^12.0||^13.0
- monolog/monolog: ^3.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^11.0.0||^10.0.0||^9.0.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- spatie/laravel-ray: ^1.35
This package is auto-updated.
Last update: 2026-03-29 09:32:39 UTC
README
Laravel Exception Viewer keeps Laravel's native exception reporting flow intact, stores aggregated exception rows in exception_logs, ships with a Blade viewer, and exposes markdown endpoints that work well with curl, automation, and LLM workflows.
What It Does
- Records exceptions that reach Laravel's exception reporting flow
- Aggregates repeated exceptions into one fingerprinted row
- Stores the latest exception text plus masked context
- Captures HTTP exceptions
- Captures CLI exceptions
- Captures queued job exceptions, including job metadata and payload
- Provides a Blade viewer at
/exception-viewer - Provides markdown export endpoints for one exception or all exceptions
- Can dispatch Discord alarm jobs for repeated exceptions
Installation
Requirements:
- PHP 8.2+
- Laravel 11, 12, or 13
Install the package with Composer:
composer require korioinc/laravel-exception-viewer
Laravel auto-discovers the package service provider, so no manual provider registration is required.
Publish the default install set in one shot:
php artisan vendor:publish --tag="exception-viewer-install"
php artisan migrate
This publishes:
- config
- migrations
Or publish only what you need:
php artisan vendor:publish --tag="exception-viewer-migrations"
php artisan migrate
php artisan vendor:publish --tag="exception-viewer-config"
php artisan vendor:publish --tag="exception-viewer-views"
If you explicitly want every publishable artifact registered by the provider, including views, you can still use:
php artisan vendor:publish --provider="Korioinc\ExceptionViewer\ExceptionViewerServiceProvider"
Published views are placed at:
resources/views/vendor/exception-viewer/pages/index.blade.php
If you store exception_logs on a dedicated database connection, set exception-viewer.database_connection before running php artisan migrate. The published migration uses that configured connection when it creates or drops the table.
Configuration
Published config:
use Korioinc\ExceptionViewer\Http\Middleware\DenyInProduction; return [ 'enabled' => env('EL_ENABLED', true), 'database_connection' => null, 'alarm_enabled' => env('EL_ALARM_ENABLED', env('EL_ALARM_ENALBED', true)), 'log_time_frame' => (int) env('EL_LOG_TIME_FRAME', 3), 'log_per_time_frame' => (int) env('EL_LOG_PER_TIME_FRAME', 2), 'delay_between_alarms' => (int) env('EL_DELAY_BETWEEN_ALARMS', 5), 'notification_message' => env('EL_NOTIFICATION_MESSAGE', ''), 'discord_webhook_url' => env('EL_DISCORD_WEBHOOK_URL', ''), 'notification_title' => 'Log Alarm Notification', 'route_path' => 'exception-viewer', 'assets_path' => 'vendor/exception-viewer', 'middleware' => [ 'web', DenyInProduction::class, ], 'request_context' => [ 'enabled' => true, 'masked_keys' => [ 'authorization', 'x-api-key', 'password', ], 'max_headers_size' => null, 'max_payload_size' => null, ], ];
Key options:
enabled: master switch for recording and alarm evaluationdatabase_connection: optional connection forexception_logs;nulluses the app default, and the published migration uses this connection tooroute_path: viewer route prefixassets_path: asset base path exposed to the Blade viewermiddleware: viewer route middleware stack; default is['web', DenyInProduction::class]request_context.enabled: enables request or execution context capturerequest_context.masked_keys: keys masked before headers or payload are stored; the default list isauthorization,x-api-key, andpasswordrequest_context.max_headers_size,request_context.max_payload_size: optional truncation limits
Alarm delivery and cache failures are swallowed so the package never interrupts Laravel's native exception reporting flow.
Recording Model
The package records one aggregated row per exception fingerprint in exception_logs.
Only exceptions that reach Laravel's exception reporting flow are recorded.
Stored columns:
keynamemessagefilelineraw_exceptionrequest_methodrequest_endpointrequest_headersrequest_payloadcountlatest_at
Fingerprinting currently uses:
- exception class
- exception message
- file
- line
- the first part of the stack trace
Repeated exceptions increment count and refresh the latest exception text and context fields.
Captured Context
The package records different execution contexts:
- HTTP exception
request_method: HTTP method such asGETorPOSTrequest_endpoint: request path or URLrequest_headers: masked request headersrequest_payload: masked request payload
- CLI exception
- request-specific fields can be empty because there is no HTTP request
- Job exception
request_method:JOBrequest_endpoint: queued job class namerequest_headers: queue metadata such asqueue,attempts,job_id,job_namerequest_payload: masked job payload- Synchronous jobs dispatched during an HTTP request are still recorded as job exceptions, not as HTTP request exceptions
By default, authorization, x-api-key, and password are masked before storage. Add keys such as token, secret, cookie, or set-cookie to request_context.masked_keys if your app needs them masked too.
Viewer
By default, the Blade viewer is available at:
/exception-viewer
The default middleware stack includes DenyInProduction, so the viewer returns 404 in production unless you explicitly override the middleware.
In shared non-production environments such as staging, add your own access control middleware like auth or an internal allowlist. By default, the package does not add authentication on top of web.
The viewer includes:
- left sidebar grouped by exception
name - latest-first aggregated exception list
- expandable detail rows
- copy button for markdown output
- link copy button for one exception
- all-export copy button
- purge action for clearing
exception_logs
Markdown endpoints:
/exception-viewer/all
/exception-viewer/{key}
These endpoints return text/markdown.
Markdown Output
The single-exception endpoint returns a markdown document shaped like this:
# Exception - Name: `RuntimeException` - Message: Example failure while processing checkout - File: `/var/www/html/app/Services/CheckoutService.php` - Line: 184 ## Request - Method: POST - Endpoint: /api/checkout ## Headers ~~~json {"authorization":"[MASKED]","x-request-id":"trace-123"} ~~~ ## Payload ~~~json {"order_id":1001,"password":"[MASKED]"} ~~~ ## Context ~~~text [2026-03-26 10:00:00] stack.ERROR: Example failure while processing checkout ... ~~~
If request context is missing, the request-related sections are omitted entirely.
LLM Workflow
The markdown endpoints are designed to work with curl, scripts, and LLM tools.
Use all exceptions when you want broad triage:
http://localhost/exception-viewer/all
Use one exception when you want focused analysis:
http://localhost/exception-viewer/629a80482b8e84f9412715b427b8a1d9db08845ba59071352d9f557d444dc2db
Example curl usage:
curl http://localhost/exception-viewer/all
curl http://localhost/exception-viewer/629a80482b8e84f9412715b427b8a1d9db08845ba59071352d9f557d444dc2db
Example prompt for an LLM:
Read this exception export and explain the root cause, likely blast radius, and the smallest safe fix:
http://localhost/exception-viewer/all
For a single issue:
Read this exception detail and propose a fix with verification steps:
http://localhost/exception-viewer/629a80482b8e84f9412715b427b8a1d9db08845ba59071352d9f557d444dc2db
If your app is not running on localhost, replace the host and port with the actual viewer URL.
Alarm
If EL_ALARM_ENABLED=true and EL_DISCORD_WEBHOOK_URL is configured, the package can dispatch a Discord alarm job.
Supported env keys:
EL_ENABLED=true EL_ALARM_ENABLED=true EL_LOG_TIME_FRAME=3 EL_LOG_PER_TIME_FRAME=2 EL_DELAY_BETWEEN_ALARMS=5 EL_NOTIFICATION_MESSAGE= EL_DISCORD_WEBHOOK_URL=
Alarm behavior:
- only exceptions handled by this package are considered
- alarms are grouped by exception fingerprint
- alarms are sent immediately while the fingerprint is below the configured limit
- once the same fingerprint has been sent
EL_LOG_PER_TIME_FRAMEtimes withinEL_LOG_TIME_FRAMEminutes, further alarms are blocked forEL_DELAY_BETWEEN_ALARMSminutes - the delay is applied per fingerprint
- alarm dispatch always happens through a queued job
- Discord delivery is optional and never interrupts Laravel's native exception flow
- if
notification_messageis empty, the package sendsLOG_LEVEL,LOG_MESSAGE,LOG_FILE, andLOG_LINE - alarm messages always include an
Open in Viewerdetail link; whether that URL is reachable depends on your viewer middleware configuration
Example with the current defaults:
- up to 2 alarms can be sent within 3 minutes for the same fingerprint
- after that, the same fingerprint is muted for 5 minutes
Queue and Async Delivery
Alarm dispatch always uses a queued job.
If the host app uses:
QUEUE_CONNECTION=sync
the alarm job still runs in the same process.
For real async delivery, use a non-sync queue such as:
QUEUE_CONNECTION=redis
Then run a worker or Horizon:
php artisan horizon
or
php artisan queue:work
If you use Redis plus Horizon, Discord delivery happens in Horizon workers.
Publish Commands
Publish all package assets:
php artisan vendor:publish --provider="Korioinc\ExceptionViewer\ExceptionViewerServiceProvider"
Or publish individual assets:
php artisan vendor:publish --tag="exception-viewer-config" php artisan vendor:publish --tag="exception-viewer-views" php artisan vendor:publish --tag="exception-viewer-migrations"
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Credits
License
The MIT License (MIT). Please see License File for more information.