PHP Logospatie/laravel-activitylog

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();

}
?>