fish/eloquent-logger
Eloquent Logger automatically records Eloquent model events and attribute changes, giving Laravel apps a simple audit trail of who changed what and when. Drop-in logging for creates, updates, and deletes with minimal setup.
composer require fish/eloquent-logger
php artisan vendor:publish --provider="Fish\EloquentLogger\EloquentLoggerServiceProvider"
php artisan migrate
Open the published config file first. Look for the logs table name, the log model class, and any attributes that should be excluded from logging. This is the quickest way to understand what the package will persist.
Add the package’s logging trait to the model you want to audit. The exact trait name/namespace is in the README; the usage generally looks like:
use Fish\EloquentLogger\Traits\LogsChanges;
class Invoice extends Model
{
use LogsChanges;
}
Invoice, Order, or User. Update a record, then inspect the configured logs table:$invoice->update(['status' => 'paid']);
DB::table('logs')->latest()->first();
// or query the package’s log model if one is configured
created, updated, deleted), model type/id, old/new values, actor id, and timestamps. Adjust the config before rolling it out broadly.Selective logging via traits. Apply the trait only to models that need an audit trail. This keeps write volume and storage costs predictable:
class Subscription extends Model
{
use LogsChanges;
}
Centralising actor context. Most audit use cases need “who changed this”. If the package exposes a user resolver or config hook, set it once in a service provider:
// config/eloquent-logger.php or a service provider
'user_resolver' => fn () => auth()->id(),
For queued jobs and console commands, fall back to an explicit actor or null.
Reading logs as a timeline. Query the log model by morph fields and order by created_at:
$logs = Log::where('model_type', Invoice::class)
->where('model_id', $invoice->id)
->latest()
->get();
If the package stores old/new values as JSON, cast them in your own presenter or accessor and render a diff in the admin UI.
Displaying a change diff. Normalise the old/new arrays before rendering so nulls and nested values are readable:
$changes = collect($log->new_values ?? [])
->only(array_keys($log->old_values ?? []))
->map(fn ($value, $key) => [
'field' => $key,
'from' => data_get($log->old_values, $key),
'to' => $value,
]);
Custom log model and table. If you need extra columns, soft deletes, or tenant scoping, extend the package’s log model and point the config at your subclass. Keep the package’s expected columns intact so its writes continue to work.
Event/listener integration. For cross-cutting audit rules, pair the package with model events or a dedicated listener. The package handles persistence; your listener can enrich context, notify Slack, or write to a separate immutable store.
Testing. Assert against the logs table rather than the package internals:
$this->actingAs($user);
$invoice->update(['status' => 'paid']);
$this->assertDatabaseHas('logs', [
'model_type' => Invoice::class,
'model_id' => $invoice->id,
'user_id' => $user->id,
]);
Freeze time or assert on JSON fragments if the package stores payloads as JSON.
composer.json before wiring it into a current app. If it targets older Laravel versions, test heavily or isolate it.php artisan config:clear. Config caching is the usual cause of “my changes aren’t taking effect”.model_type, model_id, and created_at, and avoid logging models that receive high-frequency updates unless you need full history.delete() and forceDelete().null or nested arrays. Cast consistently and handle missing keys when rendering diffs.auth()->id() is unavailable outside HTTP requests. Use an explicit resolver or pass the actor through job payloads.spatie/laravel-activitylog or owen-it/laravel-auditing as alternatives.composer.json and cover the logging behaviour with your own tests so upgrades cannot silently change the audit trail.How can I help you explore Laravel packages today?