spatie/laravel-activitylog
Log user and model activity in Laravel with a simple API. Automatically record Eloquent events, track subjects and causers, attach custom properties, and query everything via the Activity model. Stores logs in the activity_log table.
The package can automatically log events such as when a model is created, updated and deleted. To make this work, all you need to do is let your model use the Spatie\Activitylog\Models\Concerns\LogsActivity trait.
The simplest usage requires no configuration at all:
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
class NewsItem extends Model
{
use LogsActivity;
}
This will log created, updated, and deleted events, but won't track attribute changes. To also track attribute changes, override the getActivitylogOptions() method. It should return a LogOptions instance built from LogOptions::defaults() using fluent methods.
The attributes that need to be logged can be defined either by their name, or you can pass the wildcard ['*'] to log all attributes.
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
use Spatie\Activitylog\Support\LogOptions;
class NewsItem extends Model
{
use LogsActivity;
protected $fillable = ['name', 'text'];
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['name', 'text']);
}
}
Note that we start from sensible defaults, but any of them can be overridden as needed by chaining fluent methods. Review the Spatie\Activitylog\Support\LogOptions class for a full list of supported options.
If you want to log changes to all the $fillable attributes of the model, you can chain ->logFillable() on the LogOptions class.
Alternatively, if you have a lot of attributes and used $guarded instead of $fillable, you can also chain ->logUnguarded() to add all attributes that are not listed in $guarded.
These can be combined with each other and with ->logOnly(). The final set of logged attributes is the union of all sources.
Let's see what gets logged when creating an instance of that model.
$newsItem = NewsItem::create([
'name' => 'original name',
'text' => 'Lorem'
]);
//creating the newsItem will cause an activity to be logged
$activity = Activity::all()->last();
$activity->description; //returns 'created'
$activity->subject; //returns the instance of NewsItem that was created
$activity->attribute_changes; //returns a collection containing ['attributes' => ['name' => 'original name', 'text' => 'Lorem']];
Now let's update that $newsItem.
$newsItem->name = 'updated name';
$newsItem->save();
//updating the newsItem will cause an activity to be logged
$activity = Activity::all()->last();
$activity->description; //returns 'updated'
$activity->subject; //returns the instance of NewsItem that was updated
Calling $activity->attribute_changes will return a collection containing:
[
'attributes' => [
'name' => 'updated name',
'text' => 'Lorem',
],
'old' => [
'name' => 'original name',
'text' => 'Lorem',
],
];
Pretty cool, right?
Now, what happens when you call delete?
$newsItem->delete();
//deleting the newsItem will cause an activity to be logged
$activity = Activity::all()->last();
$activity->description; //returns 'deleted'
$activity->attribute_changes; //returns a collection containing ['old' => ['name' => 'updated name', 'text' => 'Lorem']];
By default the package will log the created, updated, deleted events. You can modify this behavior by setting the $recordEvents property on a model.
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
class NewsItem extends Model
{
use LogsActivity;
//only the `deleted` event will get logged automatically
protected static $recordEvents = ['deleted'];
}
Alternatively, you can use $doNotRecordEvents to exclude specific events while keeping all others.
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
class NewsItem extends Model
{
use LogsActivity;
//the `created` event will not be logged
protected static $doNotRecordEvents = ['created'];
}
By default the package will log created, updated, deleted in the description of the activity. You can modify this text by providing a callback to the ->setDescriptionForEvent() method on the LogOptions class.
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
use Spatie\Activitylog\Support\LogOptions;
class NewsItem extends Model
{
use LogsActivity;
protected $fillable = ['name', 'text'];
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->setDescriptionForEvent(fn(string $eventName) => "This model has been {$eventName}");
}
}
Let's see what happens now:
$newsItem = NewsItem::create([
'name' => 'original name',
'text' => 'Lorem'
]);
//creating the newsItem will cause an activity to be logged
$activity = Activity::all()->last();
$activity->description; //returns 'This model has been created'
You can pass a string to ->useLogName() to make the model use a different log name than the default.
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
use Spatie\Activitylog\Support\LogOptions;
class NewsItem extends Model
{
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->useLogName('system');
}
}
If your model contains attributes whose changes alone should not trigger an activity log, you can use ->dontLogIfAttributesChangedOnly().
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
use Spatie\Activitylog\Support\LogOptions;
class NewsItem extends Model
{
use LogsActivity;
protected $fillable = ['name', 'text'];
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['name', 'text'])
->dontLogIfAttributesChangedOnly(['text']);
}
}
Changing only text will not trigger an activity log. If both name and text change, the activity will still be logged (and both attributes will appear in the changes).
By default the updated_at attribute is not ignored and will still trigger logging. You can add the updated_at attribute to the ->dontLogIfAttributesChangedOnly() array to override this behavior.
If you do not want to log every attribute passed into ->logOnly(), but only those that have actually changed after the update, you can call ->logOnlyDirty().
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
use Spatie\Activitylog\Support\LogOptions;
class NewsItem extends Model
{
use LogsActivity;
protected $fillable = ['name', 'text'];
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['name', 'text'])
->logOnlyDirty();
}
}
Changing only name means only the name attribute will be logged in the activity, and text will be left out.
If you would like to log an attribute of a directly related model, you may use dot notation to log an attribute of the model's relationship.
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
use Spatie\Activitylog\Support\LogOptions;
class NewsItem extends Model
{
use LogsActivity;
protected $fillable = ['name', 'text', 'user_id'];
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['name', 'text', 'user.name']);
}
public function user()
{
return $this->belongsTo(User::class);
}
}
If you would like to log only the changes to specific sub-keys of a JSON column, you can use arrow notation in ->logOnly().
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
use Spatie\Activitylog\Support\LogOptions;
class NewsItem extends Model
{
use LogsActivity;
protected $fillable = ['preferences', 'name'];
protected $casts = [
'preferences' => 'collection' // casting the JSON database column
];
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['preferences->notifications->status', 'preferences->hero_url']);
}
}
Only the specified sub-keys will be logged. Other keys inside preferences will be left out.
Here's an example:
// Create a news item.
$newsItem = NewsItem::create([
'name' => 'Title',
'preferences' => [
'notifications' => [
'status' => 'off',
],
'hero_url' => ''
],
]);
// Update the json object
$newsItem->update([
'preferences' => [
'notifications' => [
'status' => 'on',
],
'hero_url' => 'http://example.com/hero.png'
],
]);
$lastActivity = Activity::latest()->first();
$lastActivity->attribute_changes->toArray();
// output
[
"attributes" => [
"preferences" => [
"notifications" => [
"status" => "on",
],
"hero_url" => "http://example.com/hero.png",
],
],
"old" => [
"preferences" => [
"notifications" => [
"status" => "off",
],
"hero_url" => "",
],
],
]
Calling ->dontLogEmptyChanges() prevents the package from storing empty logs. Empty logs can occur when you're tracking specific attributes but none of them actually changed in a given update.
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
use Spatie\Activitylog\Support\LogOptions;
class NewsItem extends Model
{
use LogsActivity;
protected $fillable = ['name', 'text'];
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['text'])
->logOnlyDirty()
->dontLogEmptyChanges();
}
}
If you use wildcard logging but want to exclude certain attributes from appearing in the logged output, use ->logExcept():
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
use Spatie\Activitylog\Support\LogOptions;
class NewsItem extends Model
{
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logAll()
->logExcept(['password', 'remember_token']);
}
}
The password and remember_token attributes will never appear in the logged changes.
Note: logExcept() removes attributes from the log output. This is different from dontLogIfAttributesChangedOnly() which prevents the entire activity from being created when only the specified attributes changed.
The package ships with a Spatie\Activitylog\Models\Concerns\CausesActivity trait which can be added to any model that you use as a causer. It provides an activitiesAsCauser() relationship which returns all activities that are caused by the model.
If you include it in the User model, you can retrieve all the current user's activities like this:
Auth::user()->activitiesAsCauser;
If your model both causes and logs activities (common for User models), use the HasActivity trait which combines LogsActivity and CausesActivity:
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Models\Concerns\HasActivity;
use Spatie\Activitylog\Support\LogOptions;
class User extends Model
{
use HasActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logFillable();
}
}
This provides three relationships:
activities() returns activities where this model is the subject (alias for activitiesAsSubject())activitiesAsSubject() returns activities where this model is the subjectactivitiesAsCauser() returns activities where this model is the causerThe package provides an ActivityEvent enum for the built-in event types:
use Spatie\Activitylog\Enums\ActivityEvent;
ActivityEvent::Created; // 'created'
ActivityEvent::Updated; // 'updated'
ActivityEvent::Deleted; // 'deleted'
ActivityEvent::Restored; // 'restored'
You can use this enum when querying activities or when manually logging:
Activity::forEvent(ActivityEvent::Created)->get();
activity()->event(ActivityEvent::Updated)->log('...');
All methods that accept an event also accept a plain string, so custom event names still work.
If your model uses the SoftDeletes trait, the package will automatically log restored events in addition to created, updated, and deleted. No extra configuration is needed.
The Activity model provides several query scopes for filtering activities:
use Spatie\Activitylog\Models\Activity;
// get all activities for a specific subject
Activity::forSubject($newsItem)->get();
// get all activities caused by a specific user
Activity::causedBy($user)->get();
// get all activities for a specific event
Activity::forEvent('updated')->get();
// these scopes can be combined
Activity::forSubject($newsItem)->causedBy($user)->forEvent('updated')->get();
The inLog scope is documented in using multiple logs.
You can disable logging for a specific model instance by using the disableLogging() method. This only affects that instance, not other models. To disable all logging globally, see disabling logging.
$newsItem = NewsItem::create([
'name' => 'original name',
'text' => 'Lorem'
]);
// Updating with logging disabled
$newsItem->disableLogging();
$newsItem->update(['name' => 'The new name is not logged']);
You can also chain disableLogging() with the update() method.
You can use the enableLogging() method to re-enable logging.
$newsItem = NewsItem::create([
'name' => 'original name',
'text' => 'Lorem'
]);
// Updating with logging disabled
$newsItem->disableLogging();
$newsItem->update(['name' => 'The new name is not logged']);
// Updating with logging enabled
$newsItem->enableLogging();
$newsItem->update(['name' => 'The new name is logged']);
In addition to the tap() method on ActivityLogger, you can use the beforeActivityLogged() method on your model. This allows you to fill properties and add custom fields before the activity is saved.
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Contracts\Activity;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
class NewsItem extends Model
{
use LogsActivity;
public function beforeActivityLogged(Activity $activity, string $eventName)
{
$activity->description = "activity.logs.message.{$eventName}";
}
}
Sometimes you want to log changes on your pivot model, for example if it contains additional data.
By default pivot models don't have a primary key column, which means they can't be used as activity subjects.
To solve this, add a primary key column id to your pivot table ($table->id()) and configure your pivot model to use it.
use Illuminate\Database\Eloquent\Relations\Pivot;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
final class PivotModel extends Pivot
{
use LogsActivity;
public $incrementing = true;
}
After these changes, you can log activities on your pivot models as expected.
How can I help you explore Laravel packages today?