foxen/laravel-model-activity-log
Automatically log Eloquent model activity in Laravel: create, update (with attribute diffs), delete, and restore (SoftDeletes). Supports per-model log names, ignoring or redacting attributes, tracks the authenticated user or falls back to “System”, and can prune old entries.
Installation:
composer require foxen/laravel-model-activity-log
php artisan vendor:publish --provider="Foxen\LaravelModelActivityLog\Providers\ActivityLogServiceProvider" --tag="config"
php artisan migrate
config/foxen_activitylog.php exists and adjust defaults (e.g., log_model_events, prune_old_logs) if needed.Enable Logging for a Model:
Add the LogsActivity trait to your Eloquent model:
use Foxen\LaravelModelActivityLog\Traits\LogsActivity;
class Post extends Model
{
use LogsActivity;
// ...
}
First Use Case:
Post model. Check the activity_log table for entries:
SELECT * FROM activity_log WHERE model_type = 'App\Models\Post';
id, model_id, model_type, action, changes, causer_id, created_at.Model-Specific Logging:
getActivityLogAttributes() to filter sensitive fields:
protected function getActivityLogAttributes(): array
{
return ['title', 'content']; // Excludes 'password' or 'api_token'
}
getActivityLogEvents():
protected function getActivityLogEvents(): array
{
return ['created', 'updated', 'deleted', 'published']; // Add 'published'
}
Causer Resolution:
Auth::id()). Override getActivityLogCauser() for custom logic:
protected function getActivityLogCauser(): ?int
{
return request()->user_agent === 'admin-cli' ? 1 : null;
}
Change Tracking:
updated events, changes captures old/new values. Access via:
$log->changes['title']['old']; // Old title value
Pruning Old Logs:
config('foxen_activitylog.prune_after_days'):
php artisan activity-log:prune
ActivityLogPruner service:
$pruner = app(\Foxen\LaravelModelActivityLog\Services\ActivityLogPruner::class);
$pruner->prune(30); // Prune logs older than 30 days
Querying Logs:
ActivityLog model to query logs:
$logs = \Foxen\LaravelModelActivityLog\Models\ActivityLog::whereModelType('App\Models\Post')
->whereAction('updated')
->with('causer') // If causer is a User model
->get();
protected function getActivityLogCauser(): ?int
{
return request()->header('X-User-ID') ? User::find(request()->header('X-User-ID'))->id : null;
}
changes field for diff views.use Foxen\LaravelModelActivityLog\Traits\LogsActivity;
class PostPolicy
{
public function delete(User $user, Post $post)
{
$post->logActivity('deleted', ['reason' => 'policy_violation']);
return false;
}
}
Performance:
updated event can bloat the database. Use getActivityLogEvents() to exclude frequent updates (e.g., touch() timestamps).protected function getActivityLogEvents(): array
{
return ['created', 'updated:save', 'deleted']; // Exclude 'updated' but include 'updated:save'
}
Causer Resolution:
Auth::id() returns null, logs will lack a causer. Ensure middleware sets the user before logging.getActivityLogCauser() to handle anonymous actions:
protected function getActivityLogCauser(): ?int
{
return Auth::check() ? Auth::id() : config('foxen_activitylog.anonymous_user_id');
}
Soft Deletes:
restored events are only logged if the model uses SoftDeletes. Forgetting to add use SoftDeletes; will miss restores.Change Tracking:
changes field may not reflect expected values if the model uses accessors/mutators. Ensure raw attribute values are logged:
protected function getActivityLogAttributes(): array
{
return array_keys($this->getAttributes());
}
Migration Conflicts:
activity_log table already exists, migrations may fail. Use --force or manually adjust the migration:
php artisan migrate --force
Missing Logs:
activity_log table exists and is writable.config(['foxen_activitylog.debug' => true]);
Logs debug info to storage/logs/laravel.log.Pruning Issues:
prune_after_days config value is set and the cron job has permissions.php artisan activity-log:prune --days=1
Custom Log Models:
ActivityLog model to add fields (e.g., ip_address):
class CustomActivityLog extends \Foxen\LaravelModelActivityLog\Models\ActivityLog
{
protected $fillable = ['ip_address'];
}
Event Listeners:
activity.logged events to trigger side effects:
use Foxen\LaravelModelActivityLog\Events\ActivityLogged;
Event::listen(ActivityLogged::class, function (ActivityLogged $event) {
// Send notification, update cache, etc.
});
Batch Logging:
Model::update()), disable logging temporarily:
\Foxen\LaravelModelActivityLog\Facades\ActivityLog::disableLogging();
Post::where('published', false)->update(['published' => true]);
\Foxen\LaravelModelActivityLog\Facades\ActivityLog::enableLogging();
Custom Actions:
exported):
$post->logActivity('exported', ['format' => 'pdf']);
$logs = ActivityLog::whereAction('exported')->get();
log_model_events:
false to disable logging entirely (e.g., in production for performance).prune_old_logs:
false to disable automatic pruning (use manual pruning instead).anonymous_user_id:
null or a system user ID).How can I help you explore Laravel packages today?