Aslen hafiz.dev‘de yayımlanmıştır.
Her SaaS, “bu değişikliği kim yaptı?” sorusunu yanıtlayacak bir aşamaya gelir. Bir ekip üyesi bir projeyi siler, bir ayar değiştirilir ve kimse bunun ne zaman yapıldığını bilmez. Bir kullanıcı erişimini kaybeder ve bunu bir yöneticinin hatasına atfeder. Bir denetim kaydı olmadan, bu tür durumlarda yalnızca tahmin yürütmekle kalırsınız. Ve kurumsal anlaşmalarda denetim günlüklerinin yokluğu, gerçekten bir engel olabilir.
spatie/laravel-activitylog, Laravel ekosisteminde bu durumlar için yıllardır en çok tercih edilen çözümdür ve 48 milyonun üzerinde Packagist kurulumu bulunmaktadır. 5. versiyonu 2026 yılının Mart ayının sonlarında piyasaya sürüldü ve önemli geliştirmeler içeriyor: PHP 8.4+, temiz bir API, yeni bir veritabanı şeması ve doğru bir şekilde değiştirilebilir iç yapılar. Bu yazı, v5. kullanarak tamamen işlevsel bir denetim kaydı sisteminin nasıl oluşturulacağını adım adım gösterecektir.
Eğer zaten v4’te bulunuyorsanız, bu yazının sonunda önemli değişiklikleri kapsayan bir geçiş kısmı bulunmaktadır.
v5’te Neler Değişti
Freek bu konuyu blogunda kapsamlı bir şekilde ele aldı, ancak günlük kullanım açısından en önemli değişiklikler:
Temel model günlüğü için hazırlık kodu yok. V4’te, bir modele LogsActivity trait’ini eklemek, en basit durumlar için bile getActivitylogOptions() yöntemini gerektiriyordu. V5’te ise trait tek başına kayıt yapmaya başlamak için yeterlidir. Sadece özel bir davranış gerektiğinde getActivitylogOptions() ‘u geçersiz kılarsınız.
Yeni attribute_changes sütunu. Önceki changes sütunu, daha temiz bir yapı depolayan attribute_changes ile değiştirildi. Bu, yükseltme sırasında küçük bir şema geçişi anlamına gelir, ancak yeni kurulumlar daha iyi bir temele sahip olacaktır.
ActivityEvent enum’ı. V5, olay türüyle filtreleme yaparken ham dizelere güvenmemek için bir ActivityEvent enum’ı tanıttı:
use Spatie\Activitylog\Enums\ActivityEvent;
Activity::forEvent(ActivityEvent::Created)->get();
Activity::forEvent(ActivityEvent::Updated)->get();
Activity::forEvent(ActivityEvent::Deleted)->get();
Özel olay adları için düz diziler de çalışmaya devam eder. Ancak standart olaylar için bu enum, IDE seviyesinde otomatik tamamlama ve yazım hatalarını yakalamaya olanak tanır.
Özelleştirilebilir işlem sınıfları. Temel işlemler (etkinlik kaydetme, eski kayıtları temizleme) artık yapılandırma ile değiştirebileceğiniz işlem sınıflarıdır. Bu, bir isteğin sırasında etkinlik kayıtlarını kuyruklama veya hassas alanlara veritabanına ulaşmadan önce maskeleme gibi uygulamalar yapmak için pratik hale getirir.
Kurulum
PHP 8.4+ ve Laravel 12 veya 13 gerektirir. Paketi yüklemek için:
composer require spatie/laravel-activitylogYayınlayın ve geçişleri çalıştırın:
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-migrations"
php artisan migrateBu, yeni v5 şemasıyla activity_log tablosunu oluşturur. Mevcut artisan komutlarının tam listesini bulmak için Laravel Artisan Komutları referansına göz atabilirsiniz.
İsteğe bağlı olarak yapılandırma dosyasını yayınlayın:
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-config"Yapılandırma dosyası config/activitylog.php, etkinlik model sınıfını, varsayılan günlük adını, eski kayıtların kaldırılacağı gün sayısını ve dahili olarak kullanılan işlem sınıflarını kontrol eder.
Manuel Etkinlik Günlüğü Kaydı
En basit kullanım, rastgele olayları kaydetmektir:
activity()->log('Kullanıcı raporları CSV\'yi dışa aktardı');Daha faydalısı, bağlam eklemektir. Ne etkilendiğini ve kimin yaptığını bilmek istersiniz:
activity()
->performedOn($project)
->causedBy($user)
->withProperties(['plan' => 'pro', 'via' => 'settings-page'])
->log('plan yükseltildi');Kaydedilen etkinlikleri almak için Activity modelini ve bir dizi yerleşik sorgu alanını kullanın:
use Spatie\Activitylog\Models\Activity;
// Belirli bir nesne için tüm etkinlik
Activity::forSubject($project)->get();
// Bir kullanıcı tarafından tetiklenen tüm etkinlikler
Activity::causedBy($user)->get();
// Olay türüne göre filtreleme
Activity::forEvent('updated')->get();
// Günlük adıyla filtreleme (günlükleri alanlarına göre gruplamak için yararlıdır)
Activity::inLog('billing')->get();
// Kapsamları birleştirme
Activity::forSubject($project)
->causedBy($user)
->latest()
->get();Her Activity kaydında description, subject, causer, event, properties ve attribute_changes bilgileri bulunmaktadır. getProperty() yardımcı fonksiyonu eklediğiniz özel özelliklerden okuyacaktır.
Otomatik Model Olay Günlüğü Kaydı
Paketin gerçekten yerini kazandığı yer burasıdır. Herhangi bir Eloquent modeline LogsActivity trait’ini eklediğinizde, otomatik olarak oluşturulan, güncellenen ve silinen olayları kaydeder:
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
class Project extends Model
{
use LogsActivity;
}Bu, temel kayıt için gereken her şeydir. Bu model üzerindeki her oluşturma, güncelleme veya silme işlemi artık bir etkinlik kaydı oluşturur.
Hangi özelliklerin izleneceğini kontrol etmek için getActivitylogOptions()‘u geçersiz kılın:
use Spatie\Activitylog\Support\LogOptions;
class Project extends Model
{
use LogsActivity;
protected $fillable = ['name', 'description', 'status', 'owner_id'];
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['name', 'status', 'owner_id'])
->logOnlyDirty()
->dontSubmitEmptyLogs();
}
}logOnly() yalnızca belirli özelliklerin izlenmesine izin verir. logOnlyDirty() yalnızca gerçekten değişen özelliklerin kaydedileceği anlamına gelir, her şey değil, updated_at gürültüsü de değil. dontSubmitEmptyLogs() ise anlamlı bir değişiklik olmadan bir kaydın kaydedilmesini atlar.
Bir proje güncellendiğinde, etkinlik kaydının attribute_changes şöyle görünür:
$activity->attribute_changes;
// [
// 'attributes' => [
// 'status' => 'aktif',
// 'owner_id' => 42,
// ],
// 'old' => [
// 'status' => 'taslak',
// 'owner_id' => 7,
// ],
// ]Ayrıca logAll() kullanarak ve logExcept() ile belirli alanlar haricindekilerin izlenmesini sağlayabilirsiniz:
return LogOptions::defaults()
->logAll()
->logExcept(['remember_token', 'updated_at']);Ve logFillable() ile $fillable dizisinde bulunan her şeyi otomatik olarak izleyebilirsiniz. Bu, doldurulabilir listenizin kullanıcıların değiştirebileceği alanların resmi kaydı olduğu durumda yararlıdır:
return LogOptions::defaults()
->logFillable()
->logOnlyDirty();Tipik bir SaaS’da bu davranışı birçok modele aynı anda uygulayabilirsiniz. Bir proje yönetim uygulaması, Project, Team, Invitation ve Role modellerinde yer alan değişiklikleri kaydedebilir, her biri farklı özellikleri izleyebilir:
class Team extends Model
{
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['name', 'owner_id', 'plan'])
->logOnlyDirty()
->setDescriptionForEvent(fn (string $event) => "Takım ${event}");
}
}
class Invitation extends Model
{
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['email', 'role', 'accepted_at'])
->logOnlyDirty()
->useLogName('invitations');
}
}setDescriptionForEvent() metodu, kaydedilen insan okumaya uygun açıklamanın kontrol edilmesini sağlar. Varsayılan olarak tek başına olay adıdır (“güncellendi”, “oluşturuldu”), ancak daha açıklayıcı bir dize, admin panelinde okuyabilmeyi kolaylaştırır.
Adlandırılmış Günlüklerle Etkinlik Gruplama
Varsayılan olarak her şey default günlüğüne kaydedilir. Farklı alanlara sahip bir SaaS’da (faturalandırma, güvenlik, içerik) ayrı günlüklerde tutmak sorguları odaklı hale getirir ve doğru aktiviteleri doğru UI bağlamında sağlamak için pratik hale getirir.
Model üzerindeki bir günlük adını ayarlayın:
class SubscriptionChange extends Model
{
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->useLogName('billing')
->logOnly(['plan', 'status', 'cancelled_at']);
}
}Ya da manuel günlük çağrısında bir günlük adı ayarlayın:
activity('security')
->causedBy($user)
->withProperties(['ip' => request()->ip()])
->log('İki faktörlü kimlik doğrulamayı devre dışı bıraktı');Sonrasında her günlüğü bağımsız olarak sorgulayabilirsiniz:
// Yalnızca faturalandırma olayları
Activity::inLog('billing')->latest()->get();
// Belirli bir kullanıcı için güvenlik olayları
Activity::inLog('security')->causedBy($user)->latest()->get();Filament’te, yöneticilerin günlük kanallarını geçiştirmesine olanak tanıyan bir filtre ekleyin:
Tables\Filters\SelectFilter::make('log_name')
->label('Günlük')
->options([
'default' => 'Genel',
'billing' => 'Faturalama',
'security' => 'Güvenlik',
]);Bu desen, tek bir büyük activity_log tablosunu nesne türüne göre filtrelemenin getirdiği sorgu performansı sorunlarından kaçınır. Adlandırılmış günlükler, ayrı veritabanı tabloları oluşturmadan mantıksal bölümlendirme sağlar.
Kaydetmeden Önce Günlükleri Zenginleştirme
Bazen bir etkinlik kalıcı hale gelmeden önce ekstra bağlam eklemek gerekir. Modelinizdeki beforeActivityLogged() metodu, o anda çalışır:
class Project extends Model
{
use LogsActivity;
public function beforeActivityLogged(Activity $activity, string $eventName): void
{
$activity->properties = $activity->properties->merge([
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
]);
}
}Bu, modelin kendisinde yer almayan herhangi bir şey, istek bağlamı, oturum verisi eklemek için doğru yerdir. Bunu model gözlemcileri için kullanmayın. beforeActivityLogged kancası, etkinlik yaşam döngüsündeki doğru konumda çalışır.
Hassas Alanların Kaldırılması
Varsayılan olarak, bir User modelini güncellediğinizde, öznitelik değişiklikleri izlediğiniz tüm alanlar dahil olur. Eğer bu hassas bir şey içeriyorsa, veritabanına girmeden önce onu silmek istersiniz.
Özel bir işlem sınıfı oluşturun:
namespace App\ActivityLog;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Spatie\Activitylog\Actions\LogActivityAction;
class RedactSensitiveFieldsAction extends LogActivityAction
{
protected function transformChanges(Model $activity): void
{
$changes = $activity->attribute_changes?->toArray() ?? [];
Arr::forget($changes, [
'attributes.password',
'old.password',
'attributes.two_factor_secret',
'old.two_factor_secret',
]);
$activity->attribute_changes = $changes;
}
}Bunu config/activitylog.php‘de kaydedin:
'actions' => [
'log_activity' => \App\ActivityLog\RedactSensitiveFieldsAction::class,
],Şimdi şifre değişiklikleri, hangi modelin tetiklediğine bakılmaksızın günlük kaydında asla görünmeyecek. Ayrıca, eylem sınıfında save()‘i geçersiz kılarak bir kuyruklama işini tetiklemeyi de sağlayabilirsiniz; bu, yüksek trafikte, etkinlik kaydı yazmanın gecikme eklediğinden endişe duyuyorsanız yardımcı olur. kuyruk işleri rehberi burada uygulanan desenleri kapsar.
Filament’te Aktivite Günlüğünü Gösterme
Bir denetim kaydı yalnızca okunabiliyorsa yararlıdır. Filament kullanıyorsanız, en hızlı yol, özel bir kaynağa sahip olmaktır. Eğer bir SaaS admin paneli inşa ediyorsanız, Filament yöneticisi rehberi genel yapılandırmayı kapsar.
Kaynağı oluşturun:
php artisan make:filament-resource ActivityLog --viewSonrasında liste tablosunu ActivityLogResource.php‘de yapılandırın:
use Spatie\Activitylog\Models\Activity;
public static function getModel(): string
{
return Activity::class;
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('causer.name')
->label('Kullanıcı')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('description')
->label('Eylem')
->searchable(),
Tables\Columns\TextColumn::make('subject_type')
->label('Nesne')
->formatStateUsing(fn ($state) => class_basename($state))
->sortable(),
Tables\Columns\TextColumn::make('event')
->badge()
->color(fn (string $state): string => match ($state) {
'created' => 'success',
'updated' => 'warning',
'deleted' => 'danger',
default => 'gray',
}),
Tables\Columns\TextColumn::make('created_at')
->label('Ne Zaman')
->dateTime()
->sortable(),
])
->defaultSort('created_at', 'desc')
->filters([
Tables\Filters\SelectFilter::make('event')
->options([
'created' => 'Oluşturuldu',
'updated' => 'Güncellendi',
'deleted' => 'Silindi',
]),
])
->actions([
Tables\Actions\ViewAction::make(),
]);
}Görünüm sayfası için, attribute_changes‘ı biçimlendirilmiş bir farklılık olarak gösterin, böylece yöneticiler tam olarak neyin değiştiğini görebilsin:
public static function infolist(Infolist $infolist): Infolist
{
return $infolist
->schema([
Infolists\Components\TextEntry::make('causer.name')->label('Kullanıcı'),
Infolists\Components\TextEntry::make('description')->label('Eylem'),
Infolists\Components\TextEntry::make('created_at')->label('Ne Zaman')->dateTime(),
Infolists\Components\KeyValueEntry::make('attribute_changes.attributes')->label('Yeni değerler'),
Infolists\Components\KeyValueEntry::make('attribute_changes.old')->label('Önceki değerler'),
]);
}Bu, yöneticilere herhangi bir güncelleme olayında okunabilir bir önceki/sonraki karşılaştırma sunar. Daha büyük ekipler için, konuya özgü filtreler ekleyebilir ve rol bazında erişimi sınırlamak amacıyla Filament’in politika entegrasyonunu kullanarak sınırlama getirebilirsiniz; bu, tam SaaS rehberinde ele alınmıştır.
Eski Kayıtların Temizlenmesi
Etkinlik günlükleri hızla büyür. Paket, yapılandırmadaki gün sayısından daha eski kayıtları kaldıran yerleşik bir komutla birlikte gelir:
php artisan activitylog:cleanRetention süresini config/activitylog.php‘de ayarlayın:
'delete_records_older_than_days' => 90,Bunu routes/console.php‘te zamanlayın:
Schedule::command('activitylog:clean')->daily();90 gün, çoğu SaaS ürünü için makul bir varsayılandır. Eğer düzenlenmiş bir sektördeyseniz (sağlık, finans), uyum gereksinimlerinizi kontrol etmelisiniz. Bazı endüstriler, 12+ ay denetim geçmişi gerektirebilir.
v4’ten Geçiş
Eğer mevcut bir projeyi yükseltiyorsanız, kırıcı değişikliklerin dikkat gerektirdiğini bilmelisiniz. Bunlar gerçekten kodunuzu etkileyecek olanlardır:
PHP ve Laravel sürüm gereksinimleri. V5, PHP 8.4+ ve Laravel 12+ gerektirir. Eğer eski sürümlerdeyseniz, yükseltme yapana kadar V4’te kalın.
Yeni veritabanı sütunu. V5, eski changes sütununun yerine attribute_changes sütunu getirmiştir. Bir geçiş oluşturun:
Schema::table('activity_log', function (Blueprint $table) {
$table->json('attribute_changes')->nullable()->after('properties');
});Mevcut changes verisi ile ne yapacağınıza karar vermeniz gerekecektir. Birçok ekip için, eski sütunu arşivlemek ve yeni kayıtların attribute_changes kullanmasını sağlamak, veri formatını taşımaktan daha basittir.
İlişkilerin yeniden adlandırılması. İki ilişki adı değişti:
// V4
$user->activity; // bu kullanıcı tarafından tetiklenen etkinlikler ilişkisi
$model->activity; // bu model üzerindeki etkinlikler ilişkisi
// V5
$user->actions; // yeniden adlandırıldı
$model->activities; // yeniden adlandırıldı
Kod tabanınızda ->activity aramanız ve buna göre güncellemeniz gerekir.
Değişikliklere erişim. changes() yöntemi bir özellik haline geldi:
// V4
$activity->changes();
// V5
$activity->changes; // veya $activity->attribute_changes
Kaldırılan yapılandırma seçenekleri. table_name ve database_connection yapılandırma dosyasından kaldırıldı. Özel bir tablo veya bağlantıya ihtiyacınız varsa, bir özel Activity modeli oluşturun ve $table ve $connection özelliklerini belirtin, ardından yapılandırmadaki activity_model ‘ni o sınıfa yönlendirin.
Kaldırılan yöntemler: addLogChange(), LoggablePipe ve EventLogBag yok. Bunları değişiklikler dizisini işlemek için kullanıyorsanız, yukarıda gösterilen redaksiyon bölümünde özel bir LogActivityAction içinde transformChanges() ‘i geçersiz kılın.
Yükseltmeden önce, spatie/laravel-activitylog‘a bağlı diğer ikinci paketlere composer.json dosyanızda göz atın. Bu, büyük sürüm yükseltmelerinde iyi bir uygulamadır. Composer bağımlılıklarını denetleme yazısı bu akışı kapsıyor.
Etkinlik Kaydınızı Test Etme
Paket, kayıtların etkileyici olmadığı testler için kullanışlı olan withoutLogs() yardımcı fonksiyonuyla birlikte gelir:
it('updates a project', function () {
$project = Project::factory()->create();
// Bu test için günlüğü devre dışı bırak
activity()->disableLogging();
$project->update(['name' => 'Updated Name']);
activity()->enableLogging();
expect($project->fresh()->name)->toBe('Updated Name');
expect(Activity::count())->toBe(0);
});Aslında, etkinliğin doğru bir şekilde kaydedildiğini doğrulamak isterseniz, bunu açıkça test edin:
it('logs when a project status changes', function () {
$user = User::factory()->create();
$project = Project::factory()->create(['status' => 'draft']);
actingAs($user);
$project->update(['status' => 'active']);
$activity = Activity::latest()->first();
expect($activity->causer->id)->toBe($user->id)
->and($activity->event)->toBe('updated')
->and($activity->attribute_changes['attributes']['status'])->toBe('active')
->and($activity->attribute_changes['old']['status'])->toBe('draft');
});beforeActivityLogged kancasını test etmek de aynı şekilde çalışır: modeli güncelleyin ve etkinlik kaydı üzerine birleştirilen özel özelliği doğrulayın. Kanca, model kaydederken senkronize bir biçimde çalışır, bu nedenle testlerde yönetilecek hiçbir asenkron karmaşıklık yoktur.
Bilinmesi gereken bir şey: Eğer etkinlik kaydını bir kuyruklayarak tetiklerseniz (özelleştirilmiş işlem sınıfı düzenini kullanarak), testlerde Queue::fake() kullanın ve doğrudan etkinliğin kaydedildiğini değil, işlemlerin tetiklendiğini doğrulayın.
SSS
v5, Laravel 12 ve 13 ile çalışıyor mu?
Evet. Paket illuminate/support: ^12.0 || ^13.0 gerektiriyor, dolayısıyla her ikisi de destekleniyor. Laravel 11 ve daha eski sürümler v5’te desteklenmiyor. Yükseltme yapmadıysanız v4’te kalın.
Oturum açmamış kullanıcılarla etkinlik kaydı tutabilir miyim?
Evet, kimlik doğrulaması yapılmamışsa causer null olur ve log yine de kaydedilir. Bu, arka plan işlerini veya sistem tarafından tetiklenen olayları kaydetmek için yararlıdır. Ayrıca causedBy($model) ile bir etkileyen belirtebilirsiniz.
Sertifikalı modellerin kaydını nasıl tutabilirim?
LogsActivity trait’i Eloquent model olaylarını, SoftDeleting dahil olmak üzere yakalar. Modeliniz SoftDeletes kullanıyorsa, etkinlik kaydı deleted olaylarını otomatik olarak kaydeder. Geri yüklemeler de kaydedilir.
Birden fazla günlük kanalı kullanabilir miyim?
Evet. getActivitylogOptions() içinde useLogName() kullanarak etkinliği farklı adlandırılmış günlüklerde yönlendirebilirsiniz. Ardından Activity::inLog('billing') veya Activity::inLog('security') ile sorgulayın. Uygulamanızın farklı bölümleri için ayrı denetim izleri sağlamak için yararlıdır.
İçe aktarmalar veya tohumlar sırasında kaydı nasıl önlerim?
activity()->disableLogging() çağrısını önceden yapın ve activity()->enableLogging() çağrısını sonrasında yapın. Bu, testlerde, tohumlayıcılarda ve gürültü dolu etkinlik kaydı olmadan temiz bir çalışmaya ihtiyaç duyduğunuz tüm alanlarda çalışır.
Bir Kez İnşa Et, Sonra Kendine Teşekkür Et
Denetim günlükleri her zaman isteğe bağlı hissi oluşturur, ta ki bunun ihtiyaç duyulduğu ana kadar. Bir ekip üyesi beklenmedik bir değişiklik yapar, bir kullanıcı hesap geçmişini tartışır, uyum gereksinimi ortaya çıkabilir. O sırada logu geriye dönük olarak eklemek çok geçtir.
İyi bir haber, v5’in kurulumu oldukça hafif hale getirmesidir. Trait’i ekleyin, izleyeceğiniz öğeleri yapılandırın, temizleme komutunu zamanlayın. İşte bu kadar basit. Filament gösterimi, hassas alanların maskelemesi ve kuyruklu kaydetmeler gibi ihtiyaçlarınız arttıkça ekleyebileceğiniz tüm unsurlar bunlardır.
Üretim uygulamanızda etkinlik kaydını kuruyorsanız ve uygulamanızın belirlenmesi hakkında ikinci bir göz isterseniz, bana ulaşın.
Kaynak: Orijinal Makale
- v5’te Neler Değişti
- Kurulum
- Manuel Etkinlik Günlüğü Kaydı
- Otomatik Model Olay Günlüğü Kaydı
- Adlandırılmış Günlüklerle Etkinlik Gruplama
- Kaydetmeden Önce Günlükleri Zenginleştirme
- Hassas Alanların Kaldırılması
- Filament’te Aktivite Günlüğünü Gösterme
- Eski Kayıtların Temizlenmesi
- v4’ten Geçiş
- Etkinlik Kaydınızı Test Etme
- SSS
- Bir Kez İnşa Et, Sonra Kendine Teşekkür Et


