spatie/laravel-activitylog
Log user and model activity in Laravel with an easy API. Manually record actions or automatically log Eloquent events, attach subjects/causers and custom properties, and query everything via the Activity model stored in the activity_log table.
Installation:
composer require spatie/laravel-activitylog
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-migrations"
php artisan migrate
(Optional: Publish config with --tag="activitylog-config")
First Log Entry:
use Spatie\Activitylog\Facades\Activity;
Activity::log('User performed an action');
Querying Logs:
$logs = \Spatie\Activitylog\Models\Activity::latest()->take(10)->get();
created, updated, deleted) automatically.Post model:
use Spatie\Activitylog\Traits\LogsActivity;
class Post extends Model
{
use LogsActivity;
}
Now, every save() or delete() on Post will log an activity.Activity::log('User {user} created a post', ['user' => $user->name]);
Activity::log('Post updated')
->performedOn($post)
->causedBy($user)
->withProperties(['old_title' => $post->previous('title')]);
use Spatie\Activitylog\Traits\LogsActivity;
class Post extends Model
{
use LogsActivity;
protected static $logAttributes = ['title', 'content'];
protected static $logOnly = ['title']; // Log only these attributes
protected static $logName = 'post'; // Custom log name
}
// Logs 'created', 'updated', 'deleted' by default
// Override with:
protected static $logOnlyEvents = ['created', 'updated'];
Activity::batch(function () {
Activity::log('Action 1');
Activity::log('Action 2');
});
$userActivities = Activity::whereSubjectType(Post::class)
->whereSubjectId($postId)
->whereCauserType(User::class)
->whereCauserId($userId)
->get();
$activities = Activity::wherePropertiesLike('status', 'pending')->get();
Activity::useDescriptionForEvent('creating', function ($model) {
return "{$model->name} is being created";
});
Activity::usePropertiesForEvent('updating', function ($model) {
return ['ip' => request()->ip()];
});
namespace App\Http\Middleware;
use Closure;
use Spatie\Activitylog\Facades\Activity;
class LogUserActivity
{
public function handle($request, Closure $next)
{
$response = $next($request);
Activity::log("User accessed {$request->path()}");
return $response;
}
}
app/Http/Kernel.php:
protected $middleware = [
\App\Http\Middleware\LogUserActivity::class,
];
Activity::shouldQueueLogs();
php artisan queue:work
use Illuminate\Database\Eloquent\SoftDeletes;
class Activity extends \Spatie\Activitylog\Models\Activity
{
use SoftDeletes;
}
deleted_at to migration and config.Performance with Large Logs:
Activity::latest()->paginate(20);
Activity::cleanup() or a scheduled job:
Activity::cleanup()->olderThan(30); // Delete logs older than 30 days
Circular References in Properties:
User has posts, posts has user) can cause errors.->toArray() or exclude problematic relations:
Activity::withProperties(['user' => $user->toArray()]);
Model Events Not Triggering:
LogsActivity trait is used before HasFactory or other traits that might override boot().registerActivitylogMacro() is called in the model’s boot() method.UUID vs Integer IDs:
Schema::table('activity_log', function (Blueprint $table) {
$table->uuid('subject_id')->change();
$table->uuid('causer_id')->change();
});
Queue Listener Issues:
Activity facade is available in the queue worker’s context.AppServiceProvider:
$this->app->bind(\Spatie\Activitylog\Facades\Activity::class, function () {
return new \Spatie\Activitylog\Facades\Activity();
});
Log Format Issues:
description and properties in the database. Use dd() to inspect:
dd(Activity::all()->last()->toArray());
Missing Logs:
LogsActivity is used and events are not overridden. Add a dd() in the trait’s logActivity() method to debug.Property Serialization:
null, ensure they are serializable. Use json_encode() to test:
$properties = ['key' => $value];
if (json_encode($properties) === false) {
throw new \Exception("Invalid properties: " . print_r($properties, true));
}
Custom Log Tables:
'table_name' => 'custom_activity_logs',
Activity::setTableName('custom_table');
Custom Activity Model:
Activity model:
namespace App\Models;
use Spatie\Activitylog\Models\Activity as BaseActivity;
class Activity extends BaseActivity
{
protected $casts = [
'properties' => 'array',
];
}
Event-Based Logging:
use Spatie\Activitylog\Events\ActivityLogged;
Event::listen(ActivityLogged::class, function ($activity) {
// Custom logic (e.g., send notification)
});
Activitylog Macros:
\Spatie\Activitylog\Models\Activity::macro('forAdmin', function () {
return $this->where('description', 'like', '%admin%');
});
Activity::forAdmin()->get();
Logging API Requests:
spatie/laravel-http-middlewares for HTTP activity logging:
Activity::log("API Request: {$request->method()} {$request->path()}")
->withProperties([
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
]);
logOnly vs logAttributes:
logOnly: Logs only these attributes (exclusive).logAttributes: Logs these and all other changed attributes (inclusive).logName:
post instead of App\Models\Post).logUnguarded:
protected static $logUnguarded = true;
How can I help you explore Laravel packages today?