oluokunkabiru / laravel-audit-log-trail
A robust, diff-aware audit trail package for Laravel Eloquent models.
Package info
github.com/oluokunkabiru/laravel-audit-log-trail
pkg:composer/oluokunkabiru/laravel-audit-log-trail
Requires
- php: ^8.2
- illuminate/database: ^10.0|^11.0
- illuminate/events: ^10.0|^11.0
- illuminate/queue: ^10.0|^11.0
- illuminate/support: ^10.0|^11.0
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^8.0|^9.0
- pestphp/pest: ^2.0
- pestphp/pest-plugin-laravel: ^2.0
README
A fluent, diff-aware audit trail for Eloquent models. Logs exactly what changed, who changed it, and when — with multi-tenant support, a swappable driver system, and a clean suppression API.
Installation
composer require oluokunkabiru/laravel-audit-log-trail
Publish and run the migration:
php artisan vendor:publish --tag=audit-migrations php artisan migrate
Optionally publish the config:
php artisan vendor:publish --tag=audit-config
Quick start
Add the HasAuditTrail trait to any Eloquent model:
use Oluokunkabiru\AuditTrail\Traits\HasAuditTrail; class User extends Model { use HasAuditTrail; }
That's it. Every created, updated, deleted, and restored event is now logged automatically.
Control Dashboard
The package comes with a beautiful, Livewire-powered control dashboard out of the box!
Access the dashboard at /audit-trail in your browser to manage your audit logs.
Features available from the Dashboard include:
- Discover Models: Automatically detect and list all Eloquent models in your project.
- Enable/Disable Tracking: Instantly inject or remove the
HasAuditTrailtrait from your PHP models with a single click. - Global Settings: Configure the audit storage driver, background queues, and pruning policies directly from the UI natively connecting back to your
.envfile.
Requires Livewire 3.
Controlling which fields are logged
class User extends Model { use HasAuditTrail; // Only log these fields protected array $auditInclude = ['name', 'email', 'role']; // Or: exclude specific fields (stacks with global_exclude in config) protected array $auditExclude = ['api_token', 'two_factor_secret']; }
Querying audit logs
use Oluokunkabiru\AuditTrail\Models\AuditLog; // All changes to a specific model instance AuditLog::forModel($user)->latest()->get(); // Filter by event type AuditLog::forModel($user)->event('updated')->get(); // Who changed the email field? AuditLog::forModel($user)->forField('email')->get(); // All deletes in the last 7 days AuditLog::event('deleted')->since(now()->subDays(7))->get(); // By actor AuditLog::byActor($admin)->today()->get();
Via the model relationship:
$user->auditLogs()->latest()->limit(10)->get(); $user->latestAudit;
Suppression
use Oluokunkabiru\AuditTrail\Facades\Auditor; // Suppress all logging inside the callback Auditor::suppress(function () { User::factory()->count(1000)->create(); }); // Suppress on a specific model instance $user->withoutAudit(fn() => $user->update(['name' => 'Silent Bob']));
Custom events
Auditor::log($user, 'password_reset', before: ['method' => 'email']); Auditor::log($order, 'status_changed', before: ['status' => 'pending'], after: ['status' => 'paid']);
Multi-tenancy
In config/audit.php:
'tenant_resolver' => fn() => auth()->user()?->organization_id,
Query scoped to a tenant:
AuditLog::forTenant($tenantId)->latest()->get();
Async writes (recommended for production)
AUDIT_QUEUE_ENABLED=true AUDIT_QUEUE_CONNECTION=redis AUDIT_QUEUE_NAME=audits
Pruning old logs
# Delete logs older than 365 days (or your configured retention) php artisan audit:prune # Override days via option php artisan audit:prune --days=90
Schedule it in routes/console.php:
Schedule::command('audit:prune')->daily();
Configuration reference
// config/audit.php return [ 'driver' => 'database', 'table' => 'audit_logs', 'queue' => ['enabled' => false, 'connection' => null, 'queue_name' => 'audits'], 'tenant_resolver' => null, 'actor_resolver' => null, 'prune' => ['keep_days' => 365], 'global_exclude' => ['password', 'remember_token'], 'metadata' => ['capture_url' => true, 'capture_ip' => true, 'capture_user_agent' => false], ];
Custom drivers
Implement the AuditDriver contract and bind it in a service provider:
use Oluokunkabiru\AuditTrail\Drivers\Contracts\AuditDriver; class ElasticsearchAuditDriver implements AuditDriver { public function log(AuditEntry $entry): void { /* ... */ } public function prune(int $keepDays): int { /* ... */ } } // In AppServiceProvider::register(): $this->app->bind(AuditDriver::class, ElasticsearchAuditDriver::class);
Listening to audit events
use Oluokunkabiru\AuditTrail\Events\AuditLogged; Event::listen(AuditLogged::class, function (AuditLogged $event) { // $event->entry is an AuditEntry value object logger()->info('Audit recorded', $event->entry->toArray()); });
Running tests
composer test
License
MIT