A high-performance Laravel package for logging and analyzing HTTP requests with minimal overhead. Built with a cache-first approach and normalized database structure for efficient storage and querying.
NOTE: This package has no connection with the Laravel framework or its creators. It is an independent project developed by Sofiane Lasri, mainly for educational purposes.
- Features
- Requirements
- Installation
- Configuration
- Usage
- Advanced Features
- Database Schema
- Performance Considerations
- Testing
- Contributing
- License
- 🚀 High Performance: Cache-first approach ensures zero impact on request processing
- 📊 Normalized Database: Efficient storage with deduplicated entities (IPs, URLs, User Agents, MIME types)
- 🔄 Async Processing: Background job processing for database persistence
- 📈 Rich Data Capture: Logs IP addresses, HTTP methods, status codes, response times, and more
- 🌍 GeoIP Support: Automatic country code detection from IP addresses
- 🛠️ Fully Configurable: Customize cache TTL, storage keys, and processing behavior
- 🧪 100% Tested: Comprehensive test coverage with factories for all models
- 🔒 Thread-Safe: Cache locking prevents race conditions in high-traffic scenarios
- PHP 8.2 or higher
- Laravel 11.9+ (with support for Laravel 12)
- Redis or Memcached recommended for caching (fallback to file cache supported)
Install the package via Composer:
composer require sl-projects/laravel-request-loggerRun the migrations to create the necessary database tables:
php artisan migrate(Optional) Publish the configuration file for customization:
php artisan vendor:publish --tag=request-logger-configLaravel 11 introduced a new application structure with simplified configuration. Here's how to set up the package:
In bootstrap/app.php:
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;
use SlProjects\LaravelRequestLogger\app\Http\Middleware\SaveRequestMiddleware;
return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
// Global middleware (logs all requests)
$middleware->append(SaveRequestMiddleware::class);
// OR for specific routes only
$middleware->appendToGroup('web', SaveRequestMiddleware::class);
// OR create an alias for selective use
$middleware->alias([
'log.request' => SaveRequestMiddleware::class,
]);
})
->create();In routes/console.php:
use Illuminate\Support\Facades\Schedule;
Schedule::command('save:requests')->everyMinute();For Laravel versions 10 and below, use the traditional configuration approach:
In app/Http/Kernel.php:
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
use SlProjects\LaravelRequestLogger\app\Http\Middleware\SaveRequestMiddleware;
class Kernel extends HttpKernel
{
// Global middleware (logs all requests)
protected $middleware = [
// ... other middleware
SaveRequestMiddleware::class,
];
// OR for web routes only
protected $middlewareGroups = [
'web' => [
// ... other middleware
SaveRequestMiddleware::class,
],
];
// OR create an alias for selective use
protected $middlewareAliases = [
// ... other aliases
'log.request' => SaveRequestMiddleware::class,
];
}In app/Console/Kernel.php:
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
protected function schedule(Schedule $schedule): void
{
$schedule->command('save:requests')->everyMinute();
}
}Once configured, the package automatically logs all incoming requests. The middleware captures request data in the terminate() method to ensure zero impact on response times.
Apply middleware to specific routes only:
// Laravel 11+ (routes/web.php)
Route::middleware(['log.request'])->group(function () {
Route::get('/api/users', [UserController::class, 'index']);
Route::post('/api/users', [UserController::class, 'store']);
});
// Or for individual routes
Route::get('/dashboard', DashboardController::class)
->middleware('log.request');Process cached requests manually without waiting for the scheduler:
php artisan save:requestsuse SlProjects\LaravelRequestLogger\app\Models\LoggedRequest;
// Get all requests from a specific IP
$requests = LoggedRequest::with(['ipAddress', 'url', 'userAgent'])
->whereHas('ipAddress', function ($query) {
$query->where('ip', '192.168.1.1');
})
->get();
// Get requests by status code
$errors = LoggedRequest::where('status_code', '>=', 400)
->where('status_code', '<', 500)
->get();
// Get requests by URL pattern
$apiRequests = LoggedRequest::whereHas('url', function ($query) {
$query->where('url', 'like', '/api/%');
})->get();
// Analyze request patterns
$topUserAgents = LoggedRequest::with('userAgent')
->select('user_agent_id', DB::raw('count(*) as total'))
->groupBy('user_agent_id')
->orderByDesc('total')
->limit(10)
->get();After publishing the config file, you can customize:
// config/request-logger.php
return [
'cache' => [
// Cache key for storing requests
'key' => env('REQUEST_LOGGER_CACHE_KEY', 'logged_requests'),
// Cache TTL in seconds (default: 2 hours)
'ttl' => env('REQUEST_LOGGER_CACHE_TTL', 7200),
// Lock timeout for cache operations
'lock_timeout' => env('REQUEST_LOGGER_LOCK_TIMEOUT', 10),
],
'processing' => [
// Batch size for database inserts
'batch_size' => env('REQUEST_LOGGER_BATCH_SIZE', 100),
// Enable/disable async processing
'async' => env('REQUEST_LOGGER_ASYNC', true),
],
'logging' => [
// Fields to exclude from logging
'exclude_fields' => [
'password',
'password_confirmation',
'credit_card',
],
// URLs to exclude from logging
'exclude_urls' => [
'telescope/*',
'horizon/*',
'_debugbar/*',
],
],
];Create custom processors for specific request types:
use SlProjects\LaravelRequestLogger\app\Jobs\SaveRequestsJob;
class CustomRequestProcessor extends SaveRequestsJob
{
public function handle(): void
{
// Custom processing logic
parent::handle();
// Additional processing
$this->notifyAdminOfHighTraffic();
$this->detectAnomalies();
}
}The package dispatches events you can listen to:
// App\Providers\EventServiceProvider
protected $listen = [
\SlProjects\LaravelRequestLogger\Events\RequestsProcessed::class => [
\App\Listeners\AnalyzeRequestPatterns::class,
\App\Listeners\SendTrafficReport::class,
],
];The package uses a normalized database structure for optimal performance:
logged_requests: Main table containing request records with foreign keysip_addresses: Deduplicated IP addresses with country codesuser_agents: Deduplicated user agent stringsmime_types: Deduplicated MIME typesurls: Deduplicated URL paths
All foreign key columns and frequently queried fields are properly indexed for optimal query performance.
- Requests are initially stored in cache to avoid blocking the response
- Background job processes cached requests in batches
- Model ID caching prevents repeated database lookups
- Use Redis/Memcached: File-based cache can be slow under high load
- Adjust Batch Size: Larger batches reduce database calls but use more memory
- Schedule Frequency: Run
save:requestsmore frequently under high traffic - Database Indexes: Ensure all foreign keys are properly indexed
- Pruning Old Data: Regularly clean old records to maintain performance
// Example: Prune records older than 30 days
LoggedRequest::where('created_at', '<', now()->subDays(30))->delete();Run the test suite:
composer testRun static analysis:
vendor/bin/phpstan analyseThe package includes comprehensive tests for:
- Middleware functionality
- Job processing
- Model relationships and factories
- Command execution
- Cache operations
Contributions are welcome! Since this is my first package, I would appreciate any feedback, suggestions, or improvements you can provide.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please ensure all tests pass and add tests for new features.
This package is open-source software licensed under the MIT license.
Developed by Sofiane Lasri.
For any inquiries or suggestions, feel free to create an issue on GitHub.