Spatie's `laravel-activitylog` is a popular and robust Laravel package designed to easily add activity logging to your applications. It allows you to track and store user actions and system events in a dedicated database table, providing valuable insights into what's happening within your application.
Key Features and Concepts:
1. Easy Integration: By adding a simple trait (`LogsActivity`) to your Eloquent models, you can automatically log `created`, `updated`, and `deleted` events.
2. Customizable Logging: You have fine-grained control over what attributes are logged. You can log all fillable attributes, specific attributes, or only those that have changed (`logOnlyDirty`). You can also ignore specific attributes.
3. Manual Logging: Beyond model events, the package provides a convenient `activity()` helper or `Activity` facade to manually log any custom event, allowing you to track actions not directly tied to a model (e.g., 'User logged in', 'Report generated', 'API call received').
4. Causer and Subject: Every activity log entry can be associated with a 'causer' (the user who performed the action, typically `Auth::user()`) and a 'subject' (the model on which the action was performed, e.g., a `Post` or `Product`).
5. Properties and Description: You can store additional custom data as `properties` (JSON) with each log entry. You can also customize the `description` for each event, making logs more readable.
6. Log Names: Activities can be grouped into different 'log names' (e.g., 'default', 'system-events', 'audit-log', 'blog_activity'), making it easier to filter and retrieve specific types of logs.
7. Querying Activities: The package provides an Eloquent model (`Spatie\Activitylog\Models\Activity`) that allows you to easily query and retrieve log entries using powerful methods like `forSubject()`, `causedBy()`, `inLog()`, and more.
8. Automatic Cleanup: You can configure the package to automatically delete old activity log entries after a specified period, preventing your database table from growing indefinitely.
Typical Use Cases:
* Auditing: Keep a historical record of changes made to sensitive data for compliance or accountability.
* User Behavior Tracking: Understand how users interact with your application, which features are most used, or identify unusual activity.
* Debugging and Troubleshooting: Trace back events leading to an issue by examining the sequence of actions.
* Security Monitoring: Log failed login attempts, permission changes, or access to sensitive resources.
* System Monitoring: Track background job executions, API requests, or scheduled tasks.
Example Code
<?php
// 1. Installation (run these commands in your Laravel project's root directory)
// composer require spatie/laravel-activitylog
// php artisan vendor:publish --provider="Spatie\\Activitylog\\ActivitylogServiceProvider" --tag="activitylog-config"
// php artisan migrate
// After migration, you will have an 'activity_log' table in your database.
// 2. Model Setup Example (e.g., app/Models/Post.php)
// namespace App\Models;
//
// use Illuminate\Database\Eloquent\Factories\HasFactory;
// use Illuminate\Database\Eloquent\Model;
// use Spatie\Activitylog\Traits\LogsActivity;
// use Spatie\Activitylog\LogOptions;
//
// class Post extends Model
// {
// use HasFactory, LogsActivity;
//
// protected $fillable = ['title', 'content', 'status'];
//
// // Define how activity should be logged for this model
// public function getActivitylogOptions(): LogOptions
// {
// return LogOptions::defaults()
// ->logFillable() // Log all attributes listed in $fillable
// ->logOnlyDirty() // Only log attributes that have changed
// ->dontSubmitEmptyLogs() // Don't log if no changes occurred during an update
// ->setDescriptionForEvent(fn(string $eventName) => "This post was {$eventName}")
// ->useLogName('blog_activity'); // Assign a custom log name for this model's activities
// }
// }
// --- 3. Example Usage (This code snippet can be placed in a route closure, a controller method, or an Artisan command within your Laravel application for demonstration.) ---
use App\Models\Post;
use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
// Define a minimal User model for demonstration purposes if not in a full Laravel app
// In a real Laravel app, App\Models\User would be defined in app/Models/User.php
if (!class_exists('App\\Models\\User')) {
if (!function_exists('bcrypt')) { function bcrypt($value) { return password_hash($value, PASSWORD_BCRYPT); } }
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use \Illuminate\Database\Eloquent\Factories\HasFactory;
protected $fillable = ['name', 'email', 'password'];
}
}
// Restore global namespace for the rest of the script
namespace {
// --- Setup for demonstration (in a real app, models and DB would be ready) ---
// We'll create minimal 'users' and 'posts' tables for this example.
// Create a test 'users' table if it doesn't exist
if (!Schema::hasTable('users')) {
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->timestamps();
});
// Add a test user
\App\Models\User::create(['name' => 'John Doe', 'email' => 'john@example.com', 'password' => bcrypt('password')]);
}
// Create a test 'posts' table if it doesn't exist
if (!Schema::hasTable('posts')) {
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->string('status')->default('draft');
$table->timestamps();
});
}
// Run activitylog migrations if not already done. This ensures the 'activity_log' table exists.
// In a real app, `php artisan migrate` would handle this for all packages.
if (!Schema::hasTable('activity_log')) {
Artisan::call('migrate', ['--path' => 'vendor/spatie/laravel-activitylog/database/migrations', '--force' => true]);
}
// --- A. Simulate User Authentication (for 'causer') ---
// In a real Laravel app, Auth::user() would provide the currently logged-in user.
// For this example, we'll manually log in our test user.
$testUser = \App\Models\User::first();
Auth::login($testUser);
echo "Simulated user logged in: " . Auth::user()->name . " (ID: " . Auth::id() . ")\n\n";
echo "--- Demonstrating Automatic Logging with a Model ---\n";
// 1. Create a Post (logs 'created' event with 'blog_activity' log name)
$post = Post::create([
'title' => 'My First Blog Post',
'content' => 'This is the content of my first post.',
'status' => 'draft'
]);
echo "Created Post with ID: " . $post->id . "\n";
// 2. Update the Post (logs 'updated' event with 'blog_activity' log name)
$post->update(['title' => 'Updated First Blog Post Title', 'status' => 'published']);
echo "Updated Post with ID: " . $post->id . "\n";
// 3. Delete the Post (logs 'deleted' event with 'blog_activity' log name)
$post->delete();
echo "Deleted Post with ID: " . $post->id . "\n\n";
echo "--- Demonstrating Manual Logging ---\n";
// Manual logging using the global activity() helper
activity('user_actions') // Custom log name for this activity
->performedOn($testUser) // Subject can be the user themselves
->causedBy(Auth::user()) // The user who performed the action
->withProperty('ip_address', '192.168.1.100') // Custom properties
->log('User accessed sensitive report');
echo "Manually logged an activity: 'User accessed sensitive report'\n";
// Manual logging without a specific subject, with custom properties
activity('system_events')
->causedBy(Auth::user())
->withProperty('module', 'settings')
->withProperty('action', 'database_backup_initiated')
->log('Database backup initiated');
echo "Manually logged an activity: 'Database backup initiated' under 'system_events' log.\n\n";
echo "--- Retrieving Activity Logs ---\n";
// Get all activities
$allActivities = Activity::all();
echo "Total activities in log: " . $allActivities->count() . "\n\n";
// Get activities for a specific subject (e.g., the deleted Post model)
// Note: forSubject() requires the model instance, even if deleted.
$postActivities = Activity::forSubject($post)->get();
echo "Activities related to the original Post (ID: {$post->id}):\n";
foreach ($postActivities as $activity) {
echo "- [{$activity->log_name}] " . $activity->description . " (Event: " . $activity->event . ") by " . ($activity->causer ? $activity->causer->name : 'N/A') . " on " . $activity->created_at . "\n";
if ($activity->changes) {
echo " Changes: " . json_encode($activity->changes->toArray()) . "\n";
}
}
// Get activities caused by a specific user
$userActivities = Activity::causedBy(Auth::user())->get();
echo "\nActivities caused by current user (ID: " . Auth::id() . "):\n";
foreach ($userActivities as $activity) {
echo "- [{$activity->log_name}] " . $activity->description . " (Event: " . $activity->event . ") on " . $activity->created_at . "\n";
}
// Get activities by log name
$systemActivities = Activity::inLog('system_events')->get();
echo "\nActivities in 'system_events' log:\n";
foreach ($systemActivities as $activity) {
echo "- " . $activity->description . " (Properties: " . json_encode($activity->properties->toArray()) . ")\n";
}
// Clean up test data (optional: uncomment to clear tables after running)
// Activity::truncate();
// Schema::dropIfExists('posts');
// Schema::dropIfExists('users');
// Schema::dropIfExists('activity_log'); // Only if you want to remove the activity log table itself
// Don't forget to revert login if this was a temporary override
Auth::logout();
}
?>








spatie/laravel-activitylog