givanov95/laravel-activity-log
Laravel package for recording activity logs and audit trails. Track user actions and model changes to see who did what and when, with a simple, configurable logging layer for your application.
Install the package, publish its config and migrations, then run php artisan migrate. Look first at:
activity_log_entries migration for indexes used by the query scopes;ActivityLogEntry model and its scopes;ActivityLog facade/builder API and Changes/Actor value objects.The first practical use case is auditing a model change with an explicit actor, subject, action, and changeset:
use ActivityLog\ActivityLog;
use ActivityLog\Actor;
use ActivityLog\Changes;
ActivityLog::record()
->by(Actor::user($user, role: 'admin'))
->subject($post, label: $post->title)
->action('updated')
->changes($changes)
->save();
On Laravel 12, build the changeset after saving with Changes::fromModel($post) so it can use Model::getPrevious(). On Laravel 11, use Changes::snapshot($post)->diff($before) instead. If no explicit ->by() is supplied, the configured actor resolver records the signed-in user, or system when nobody is signed in.
Explicit actors. Use Actor::user($user, role: '...', label: '...') for human actions, Actor::model($model) when another model caused the change, Actor::guest() for anonymous activity, and Actor::system() for jobs, commands, and integrations. Always pass an actor explicitly for queued jobs and console commands; the default resolver cannot reliably identify the actor there.
Actor resolver fallback. Configure the resolver once for web requests where the signed-in user is the actor. Keep explicit ->by() calls for impersonation, admin actions, API clients, and background work.
Subject and related models. Attach the primary subject with a stored label via ->subject($model, label: '...'), and attach related models when an entry spans multiple records. Stored labels keep history readable after the subject is renamed or deleted.
Changesets. Use Changes::fromModel($model) after save on Laravel 12, Changes::created($model) / Changes::deleted($model) for lifecycle events, and Changes::snapshot($model)->diff($before) on Laravel 11. Use only, except, mask, and merge to shape payloads for domain-specific audit needs. Hidden and configured attributes are masked automatically; encrypted casts are treated like hidden attributes.
Transactions. ActivityLog::record() writes synchronously on the caller’s connection, so it joins the caller’s transaction. Wrap business writes and their audit entries in the same transaction when the log must be atomic with the change. For audit records that must survive a rollback, write them outside the transaction or on a separate connection.
Querying. Use the ActivityLogEntry scopes for day-to-day reads: inScope, forSubject, involving, byActor, byRole, action, and between. A batch UUID is generated per request or job; use it to group all entries produced by one operation.
GDPR and retention. ActivityLog::scrub($model) anonymizes entries about the model and removes its IP from entries it made. ActivityLog::forgetScope() removes scope metadata where applicable. The activity-log:prune command is available but disabled by default; enable it via retention_days only after confirming your compliance requirements.
Changes::fromModel() throws a LogicException because it depends on Model::getPrevious() from Laravel 12. Use snapshot()->diff() there.Changes::fromModel() is intended to run after save. Calling it before save will not produce the previous/current diff you expect.->by(), the resolver falls back to the signed-in user or system. In jobs, commands, and service-to-service calls, pass Actor::system() or a model actor explicitly.only/except/mask configuration before relying on payload completeness.scrub() removes the model’s fields from entries about it and its IP from entries it made. It does not necessarily delete every entry involving the model, so verify your GDPR interpretation.retention_days must be configured before activity-log:prune does anything. Treat pruning as a destructive compliance operation and test it on a copy first.How can I help you explore Laravel packages today?