nikolaynesov / laravel-serene
Graceful, noise-free, and rate-limited exception reporting for Laravel
Installs: 57
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/nikolaynesov/laravel-serene
Requires
- php: >=8.1
- illuminate/support: ^10.0|^11.0
Requires (Dev)
- bugsnag/bugsnag-laravel: ^2.29
- larastan/larastan: ^2.0
- orchestra/testbench: ^8.0
- pestphp/pest: ^2.0
- pestphp/pest-plugin-laravel: ^2.0
README
Graceful, noise-free, and rate-limited exception reporting for Laravel. Stop spamming your error tracking service with duplicate errors and get meaningful insights into how many times errors occurred and how many users were affected.
Features
- Rate Limiting: Automatically throttles duplicate errors to prevent spam
- Affected User Tracking: Tracks all users affected by an error during the cooldown period
- Intelligent Error Grouping: Interface-based grouping for exceptions with dynamic messages
- Metrics & Stats: Provides occurrence and throttle statistics for each error
- Memory Protection: Configurable caps prevent cache overflow in catastrophic scenarios
- Provider Agnostic: Works with Bugsnag, Sentry, or any custom error reporter
- Cache Driver Compatible: Works with any Laravel cache driver (Redis, Memcached, File, etc.)
- Easy Integration: Simple facade interface and automatic Laravel discovery
Installation
Install the package via Composer:
composer require nikolaynesov/laravel-serene
The package will automatically register itself via Laravel's package discovery.
Publish Configuration
Publish the configuration file:
php artisan vendor:publish --tag=serene-config
This will create a config/serene.php file.
Environment Variables
Add these to your .env file to configure Serene:
# Error reporting cooldown period in minutes (default: 30) SERENE_REPORTER_COOLDOWN=30 # Enable debug logging (default: false) SERENE_REPORTER_DEBUG=false # Maximum users to track per error (default: 1000) SERENE_REPORTER_MAX_TRACKED_USERS=1000 # Maximum unique errors to track simultaneously (default: 1000) SERENE_REPORTER_MAX_TRACKED_ERRORS=1000
All environment variables are optional. If not set, the defaults shown above will be used.
Using with Bugsnag
If you're using Bugsnag, install the Bugsnag Laravel package:
composer require bugsnag/bugsnag-laravel
Then configure Bugsnag according to their documentation.
Set the provider in config/serene.php:
'provider' => \Nikolaynesov\LaravelSerene\Providers\BugsnagReporter::class,
Using with Laravel Logs
To simply log errors to your Laravel logs:
'provider' => \Nikolaynesov\LaravelSerene\Providers\LogReporter::class,
Usage
Integration with Laravel Exception Handler
The recommended way to use Serene is to integrate it with Laravel's exception handler. This automatically reports all exceptions with user context:
<?php namespace App\Exceptions; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Nikolaynesov\LaravelSerene\Facades\Serene; use Throwable; class Handler extends ExceptionHandler { public function register(): void { $this->reportable(function (Throwable $e) { Serene::report($e, [ 'user_id' => auth()->id(), ]); }); } }
This will automatically report all exceptions with the current user's ID, enabling proper user tracking and Bugsnag identification.
Basic Usage
You can also use the Serene facade directly in your code:
use Nikolaynesov\LaravelSerene\Facades\Serene; try { // Your code that might throw an exception $user = User::findOrFail($id); } catch (\Exception $e) { Serene::report($e); // Handle the error gracefully return response()->json(['error' => 'User not found'], 404); }
Tracking Affected Users
Track which users are affected by an error:
use Nikolaynesov\LaravelSerene\Facades\Serene; try { // Your code $this->processPayment($user, $amount); } catch (\Exception $e) { Serene::report($e, [ 'user_id' => $user->id, ]); // Handle error }
When the error is reported, the context will include:
affected_users: Array of user IDs affected during the cooldown period (up tomax_tracked_users)affected_user_count: Total number of affected users trackeduser_tracking_capped: Boolean indicating if the user tracking limit was reached
Important: If using Bugsnag, the first affected user will be set as the primary user for the error report, enabling proper user filtering and search in Bugsnag's Users tab. All affected users remain visible in the metadata.
Enhanced Bugsnag Integration
When using BugsnagReporter, you can provide additional context fields that will enrich the Bugsnag error report:
Serene::report($exception, [ 'user_id' => $user->id, 'user_email' => $user->email, // Sets Bugsnag user email 'user_name' => $user->name, // Sets Bugsnag user name 'severity' => 'warning', // Sets Bugsnag severity level 'app_version' => config('app.version'), // Adds to app metadata tab ]);
Recognized fields for Bugsnag:
user_email- Sets email on Bugsnag user (visible in Users tab)user_name- Sets name on Bugsnag userseverity- Sets error severity (error,warning,info)app_version- Adds application version to metadata
All fields remain in the error_throttler metadata tab for reference.
Adding Custom Context
Add any additional context to help with debugging:
Serene::report($exception, [ 'user_id' => $user->id, 'order_id' => $order->id, 'payment_method' => 'stripe', 'amount' => $amount, ]);
Error Grouping
Serene provides three ways to control how errors are grouped for throttling, from most flexible to automatic:
1. Groupable Exceptions (Recommended)
Implement the GroupableException interface on your custom exceptions to define automatic error grouping:
use Nikolaynesov\LaravelSerene\Contracts\GroupableException; class PaymentException extends \Exception implements GroupableException { public function getErrorGroup(): string { return 'payment-processing-error'; } } // All PaymentExceptions are automatically grouped together throw new PaymentException("Payment failed for user {$userId}"); throw new PaymentException("Payment declined for order {$orderId}"); // Both grouped as 'payment-processing-error' regardless of message
Benefits:
- Solves the dynamic message problem automatically
- No need to specify keys at call sites
- Self-documenting exceptions
- Works across your entire codebase
Example with dynamic grouping:
class ApiException extends \Exception implements GroupableException { public function __construct( string $message, private string $endpoint ) { parent::__construct($message); } public function getErrorGroup(): string { return "api:{$this->endpoint}"; } } // Automatically groups by endpoint throw new ApiException('Timeout', 'stripe/charges'); throw new ApiException('Invalid request', 'stripe/charges'); // Both grouped as 'api:stripe/charges'
2. Explicit Custom Keys
Override grouping with an explicit key parameter for one-off cases:
// Group by API endpoint Serene::report($exception, ['user_id' => $user->id], 'api:payment:stripe'); // Group by specific operation Serene::report($exception, ['order_id' => $order->id], 'order-processing');
When to use: Third-party exceptions you can't modify, or temporary overrides.
3. Auto-Generated Keys
If no interface or explicit key is provided, errors are grouped by exception class + message hash:
// Auto-grouped by class and message throw new RuntimeException('Database connection failed'); // Key: auto:runtimeexception:abc123
Hierarchy: Explicit key > GroupableException > Auto-generated
How It Works
Rate Limiting Logic
- First Occurrence: When an error occurs for the first time, it's immediately reported to your error tracking service
- Cooldown Period: A cooldown timer starts (default: 30 minutes)
- Subsequent Occurrences: If the same error occurs again during the cooldown:
- It's not reported (preventing spam)
- User IDs are collected if provided (up to
max_tracked_userslimit) - Occurrence and throttle counters are incremented
- After Cooldown: When the cooldown expires, the next occurrence will be reported with full statistics
Reported Context
When an error is reported, the following context is automatically added:
[
'affected_users' => [1, 5, 12, 43], // Array of user IDs (max 1000)
'affected_user_count' => 4, // Total affected users tracked
'user_tracking_capped' => false, // True if hit max_tracked_users limit
'occurrences' => 15, // Total times error occurred
'throttled' => 14, // Times error was throttled
'reported_at' => '2025-12-05 18:30:00', // When reported
'key' => 'auto:runtimeexception:abc123', // Error key
// ... your custom context
]
Debug Logging
Enable debug logging to monitor throttling behavior:
# .env
SERENE_REPORTER_DEBUG=true
Or in config:
// config/serene.php 'debug' => true,
When enabled, Serene logs all activity:
// When an error is reported (info level) [Serene] auto:runtimeexception:abc123 reported { "affected_users": [1, 5, 12], "count": 3, "occurrences": 15, "throttled": 14 } // When an error is throttled (debug level) [Serene] auto:runtimeexception:abc123 throttled { "occurrences": 16, "throttled": 15 }
Note: Keep debug logging disabled in production to avoid log clutter. Only enable when investigating throttling issues.
Creating Custom Providers
Simple Custom Provider
You can create your own error reporter by implementing the ErrorReporter interface:
<?php namespace App\ErrorReporters; use Nikolaynesov\LaravelSerene\Contracts\ErrorReporter; use Throwable; class SentryReporter implements ErrorReporter { public function report(Throwable $exception, array $context = []): void { \Sentry\captureException($exception, [ 'extra' => $context, ]); } }
Then configure it in config/serene.php:
'provider' => \App\ErrorReporters\SentryReporter::class,
Advanced Bugsnag Customization
For advanced Bugsnag integration beyond the built-in fields, extend the BugsnagReporter.
Configure your custom reporter:
// config/serene.php 'provider' => \App\ErrorReporters\EnhancedBugsnagReporter::class,
This approach gives you complete control over the Bugsnag integration while maintaining Serene's rate limiting and user tracking features.
Advanced Configuration
Adjusting Cooldown Period
Change the cooldown period based on your needs:
# .env # 5 minutes for high-frequency errors SERENE_REPORTER_COOLDOWN=5 # 24 hours for low-frequency errors SERENE_REPORTER_COOLDOWN=1440 # Default: 30 minutes SERENE_REPORTER_COOLDOWN=30
Per-Environment Configuration
Configure different settings per environment using .env files:
# .env.local (development) SERENE_REPORTER_COOLDOWN=5 SERENE_REPORTER_DEBUG=true SERENE_REPORTER_MAX_TRACKED_USERS=100 # .env.production (production) SERENE_REPORTER_COOLDOWN=30 SERENE_REPORTER_DEBUG=false SERENE_REPORTER_MAX_TRACKED_USERS=1000
Or dynamically in config config/serene.php
Cache Memory Management
Serene provides two-level protection against cache memory issues:
1. Per-Error User Tracking Cap (max_tracked_users)
Limits the number of users tracked per individual error (default: 1000).
Memory calculation per error:
- 1000 users × 8 bytes = ~8KB per error
When user_tracking_capped is true in the error context, it indicates a widespread issue affecting many users.
Adjust for high-traffic applications:
# .env
SERENE_REPORTER_MAX_TRACKED_USERS=100
2. Global Error Tracking Cap (max_tracked_errors)
Limits the total number of unique errors tracked simultaneously (default: 1000).
Total memory calculation:
- 1000 unique errors × 8KB = ~8MB total (worst case with max users per error)
- Safe for any Redis instance with 512MB+ RAM
How it works:
- Tracks up to 1000 unique errors with full throttling enabled
- When limit is reached, new errors are reported immediately without throttling (fail-open)
- Expired errors are automatically cleaned up as their cooldown periods end
- This prevents catastrophic memory usage if millions of unique errors occur
For catastrophic scenario protection:
# .env # Guaranteed max memory: 1000 errors × 8KB = 8MB SERENE_REPORTER_MAX_TRACKED_ERRORS=1000 # More aggressive cap for memory-constrained environments SERENE_REPORTER_MAX_TRACKED_ERRORS=100 # Max 800KB
When tracking limit is reached:
- Errors include
tracking_limit_reached: truein context - Errors are still reported to Bugsnag (no silent failures)
- Throttling is bypassed to prevent cache overflow
- Once old errors expire, throttling resumes automatically
Testing
Run the test suite:
composer test
Run static analysis:
composer phpstan
Requirements
- PHP 8.1 or higher
- Laravel 10.x or 11.x
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Credits
License
The MIT License (MIT). Please see License File for more information.