aifst / laravel-logger
Logger package for Laravel 5.6 and up
Requires
- php: ^7.4|^8.0
- ext-json: *
- illuminate/database: ^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- nesbot/carbon: ^2.31|^3.0
README
A lightweight audit log for Eloquent models. Add one trait and every create / update / delete is recorded — with the acting user, a before/after diff, and an optional owner (the scope the event happened in, e.g. a project or a site).
Each entry stores:
- subject — the model the action was performed on (
model_type/model_id) - action —
created/updated/deleted - user — who caused it (
user_id) - before / after — the changed attributes (a diff for updates, the full record for create/delete)
- owner (optional) — the polymorphic scope container the event happened in (
owner_type/owner_id), or null for a global entry - comment (optional) — a free-text reason for the action, attached by the application
through the
LogSavingevent
Installation
Install via Composer:
composer require "aifst/laravel-logger:^1.0.0"
The service provider is auto-discovered. To register it manually, add it to config/app.php:
'providers' => [ // ... Aifst\Logger\LoggerServiceProvider::class, ];
Publish the migration and config/logger.php, then migrate:
php artisan vendor:publish --provider="Aifst\Logger\LoggerServiceProvider"
php artisan migrate
Usage
Add the Logger trait to any model you want audited:
use Aifst\Logger\Traits\Logger; class Course extends Model { use Logger; }
By default all three events are logged. Opt out per model by overriding the flags:
protected static function loggedCreated(): bool { return true; } protected static function loggedUpdating(): bool { return true; } protected static function loggedDeleting(): bool { return false; }
Recording the acting user
The trait does not know your auth layer, so tell it how to resolve the current user id
(return null for system/unauthenticated actions):
protected static function loggerUserId() { return auth()->id(); }
Limiting the logged attributes
By default the whole record (minus id / timestamps) is captured. Restrict it to specific columns:
protected static function loggedFields(): ?array { return ['title', 'status']; }
Owner scope (optional)
A log entry records the subject it happened to. You may also record an owner — the scope container the event happened in (a project, a site, a tenant…). The owner is polymorphic and nullable, so entries can be scoped to a project, to a site, or left global, without tying the log to any single entity — and it is distinct from the subject.
Override loggerOwner() to return the owning model:
protected function loggerOwner() { return $this->project; // or Site::current(), or null for a global entry }
The entry then stores owner_type / owner_id (honouring a registered morph map).
Reason for the action (comment) via the LogSaving event
Why something was deleted or changed cannot be derived from a diff — only the calling code knows
it. Every entry is therefore dispatched as Aifst\Logger\Events\LogSaving after it is built and
before it is saved, so a listener can enrich it in place:
use Aifst\Logger\Events\LogSaving; Event::listen(function (LogSaving $event) { // $event->log — the unsaved entry, $event->model — the subject, $event->action — created/updated/deleted $event->log->comment = DeletionReason::current(); // e.g. a request-scoped reason });
A common pattern is a request-scoped holder: the service that deletes a record sets the reason it
received from the user, the listener copies it onto the entry, and the holder is cleared afterwards.
Anything the listener writes to $event->log is persisted with the entry.
Querying
The Log model exposes read helpers:
use Aifst\Logger\Models\Log; // A model's own history (via the Logger trait's relation) $course->logs()->latest()->get(); // By owner scope (all activity in a project) Log::forOwner($project)->latest()->get(); Log::forOwner($project->getMorphClass(), $project->id)->get(); // By action Log::wasCreated()->get(); Log::wasUpdated()->get(); Log::wasDeleted()->get(); // By subject entity Log::entity($course->getMorphClass(), $course->id)->get(); // In a time window Log::between($from, $to)->get(); // Reconstruct a model's state at a point in time $snapshot = $course->logs()->stateOn($datetime);
before and after are returned as arrays (JSON is decoded automatically).
Upgrading an existing install (owner scope)
If your logs table predates the owner columns, add them with a migration:
$table->string('owner_type')->nullable(); $table->unsignedBigInteger('owner_id')->nullable(); $table->index(['owner_type', 'owner_id']);
Existing rows keep a null owner; nothing else changes.
Upgrading an existing install (comment)
If your logs table predates the comment column, add it with a migration:
$table->string('comment', 255)->nullable();
Existing rows keep a null comment; the LogSaving event fires regardless of the column, so add
the column before any listener starts writing to it.
License
MIT.