laravel-logger maintained by aifst
Laravel Logger
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.