An activity log answers practical questions: who changed a record, when did it change, and what kind of action occurred? Eloquent model events provide a central place to capture creates, updates and deletes without repeating logging code in every controller.
Design the log record
Create an activity_logs table with an action, model type, model ID, JSON changes, nullable actor ID and timestamps. A nullable actor allows scheduled jobs and other system actions to be recorded. Keep the log model itself out of the logging hook to avoid recursion.
Listen to model events
Use a model observer or a reusable trait for the models you want to audit. The created, updated and deleted events run after their respective operations, so the record already has an ID when a create event is logged.
protected static function booted(): void
{
static::created(fn (self $model) => ActivityLog::create([
'action' => 'created',
'model_type' => $model::class,
'model_id' => $model->getKey(),
'performed_by' => auth()->id(),
]));
static::updated(fn (self $model) => ActivityLog::create([
'action' => 'updated',
'model_type' => $model::class,
'model_id' => $model->getKey(),
'changes' => $model->getChanges(),
'performed_by' => auth()->id(),
]));
}Register a deleted callback in the same way for deletion events. Cast the log’s changes field to an array, and allow only the attributes you write through ActivityLog::create() in its fillable list. Avoid logging passwords, tokens or other sensitive fields; define an explicit allowlist of fields for your audit trail.
Important: mass updates and deletes performed directly through an Eloquent query do not dispatch individual model events, because the models are never retrieved. Use per-model operations when the audit log must capture every record. The Laravel Eloquent events guide explains the event lifecycle.
Based on: Abdo Host’s original article on Medium.

