Teknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor HaberleriTeknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor HaberleriTeknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor Haberleri
Yazı Tipi BoyutlandırıcıAa
  • Anasayfa
  • Teknoloji
    • Siber Güvenlik
    • Yapay Zeka
    • Donanım
    • Bilim
  • Yazılım
  • Savunma & İstihbarat
  • Oyun
  • Yaşam
    • Finans
    • Sinema
    • Dünyadan Haberler
  • İş Birliği
Okuma: Denetim Kaydı: Hatırlayan Bir Sistem Oluşturma
Paylaş
Yazı Tipi BoyutlandırıcıAa
Teknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor HaberleriTeknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor Haberleri
Ara
Bizi Takip Et
  • Hakkımızda
  • Gizlilik politikası
  • Tanıtım Yazısı ve Backlink Hizmeti
© 2026 Teknomers. All Rights Reserved.

Anasayfa » Denetim Kaydı: Hatırlayan Bir Sistem Oluşturma

Yazılım

Denetim Kaydı: Hatırlayan Bir Sistem Oluşturma

teknomers
Son güncelleme: 17 Nisan 2026 22:18
teknomers
Paylaş
Paylaş

Bölüm 1 – Üretim için Laravel Mimari Modelleri
~10 dk okumalı · Uygunluk · Model günlüğü · İstek izleme


Bir işlem kaydı değiştirilmişti.

Tutar, kullanıcının gönderdiğinden farklıydı. Destek durumu yükseltti. Kullanıcı hiçbir şeyi değiştirdiğini yalanladı. Kodu en son dokunan geliştirici izinde idi. Veritabanına baktık — kaydın bir updated_at değeri vardı ve beklenenden farklı bir değerden oluşuyordu. Elimizdeki tek şey buydu.

Kesin kim? Hangi alanlar? Hangi isteğin neden olduğu hakkında hiçbir bağlam yoktu.

Çalışan bir uygulamamız vardı. Ancak hiçbir şeyi hatırlayan bir sistemimiz yoktu.

Bu olay, bunun yapılmasına neden oldu.




Ne Yapacaksınız

  • Her istek için otomatik olarak her log satırında görünen bir istek ID’si — manuel işlem gerektirmeden
  • Alan bazında model değişiklikleri, sadece güncellenmenin ötesinde, neyin ve nasıl değiştiğini yakalayacak
  • Milyonlarca kayıtla hızlı kalmak için segmentlere ayrılmış yalnızca ekleme günlük dosyaları
  • Bir güvenlik kapısı bağlamı — izin kontrol hatalarını, olaylar haline gelmeden kaydeden bir rapor



Bir denetim izi gerçekten neyi kanıtlamalı

Kod yazmadan önce, ne yaptığınızı tam olarak belirlemek önemlidir — zira “denetim izi” farklı bağlamlarda farklı şeyler ifade eder ve gereksinimler mimariyi belirler.

Eğer sorgulanabilir, veritabanı destekli bir denetim günlüğüne ve hazır bir API’ye ihtiyacınız varsa, spatie/laravel-activitylog standart bir tercihtir ve iyi inşa edilmiştir. Aşağıda sunulanlar ise gereksinimleriniz bir adım daha ileri gittiğinde — ekleme garantileri, alan bazında farklar ve gerçekten değiştirilmesi zor denetim kayıtları oluştururken gereklidir.

Uyum gereksinimlerinin yoğun olduğu ortamlarda — fintech, sağlık hizmetleri veya herhangi bir düzenlenmiş alanda — bir denetim izi herhangi bir veri değişikliği hakkında dört soruyu yanıtlamalıdır:

  • Kim değişikliği yaptı (kullanıcı ID’si, IP adresi, kullanıcı aracısı)
  • Ne değişti — sadece “kayıt güncellendi” değil, hangi alanlar, hangi değerlerden hangi değerlere
  • Ne zaman değişti
  • Neden güvenilirdir — denetim kaydı kendiliğinden değiştirilemez olmalıdır

Çoğu uygulama ilk üç soruyu karşılar. Dördüncü ise başarısızdır ve gerçek bir denetim için en önemli olanıdır.

Bir veritabanı denetim tablosuyla ilgili problem şudur: uygulama kodunuz ona yazıyor. Bu, uygulama kodunuzun onu UPDATE edebileceği anlamına gelir. Uygulama kodunun değiştirebileceği bir tablo değiştirilebilir bir kayıt değildir — değişen bir geçmiştir. Bir denetçi bunu anladığında, düzenleme yapılmasını nasıl önlediğinizi soracaktır ve “kendi kodumuza güveniyoruz” tatmin edici bir yanıt değildir.

Bunu dosya tabanlı günlüklemenin kararını verme şekli olarak etkiliyor. Ancak önce, daha temel bir problem var: ilişki.




İstek ID’si: Her günlüğe bir ipucu

Bir model değişikliği yalnızca bir istek sırasında gerçekleşmez — belirli bir kullanıcıdan gelen belirli bir HTTP çağrısında ve belirli bir anda oluşur. Model değişikliğini bu isteğe bağlamadan, zaman damgalı gerçekler arasında hiçbir hikaye olmadan duruyorsunuz.

Çözüm, her istek başında üretilen ve her log satırında yazılan bir istek ID’sidir.

Middleware sınıfını oluşturmadan önce, request ve query kanallarını config/logging.php‘ye ekleyin:

// config/logging.php
    'channels' => [
    <span class="c1">// ... mevcut kanallarınız ...</span>

    <span class="s1">'request'</span> <span class="o">=&gt;</span> <span class="p">[</span>
        <span class="s1">'driver'</span> <span class="o">=&gt;</span> <span class="s1">'daily'</span><span class="p">,</span>
        <span class="s1">'path'</span>   <span class="o">=&gt;</span> <span class="nf">storage_path</span><span class="p">(</span><span class="s1>'logs/request.log'</span><span class="p">),</span>
        <span class="s1">'level'</span>  <span class="o">=&gt;</span> <span class="s1>'debug'</span><span class="p">,</span>
        <span class="s1">'days'</span>   <span class="o">=&gt;</span> <span class="mi">90</span><span class="p">,</span>        <span class="c1">// 90 gün sakla — uyum gereksiniminize ayarlayın</span>
    <span class="p">],</span>

    <span class="s1">'query'</span> <span class="o">=&gt;</span> <span class="p">[</span>
        <span class="s1">'driver'</span> <span class="o">=&gt;</span> <span class="s1>'daily'</span><span class="p">,</span>
        <span class="s1">'path'</span>   <span class="o">=&gt;</span> <span class="nf">storage_path</span><span class="p">(</span><span class="s1>'logs/query.log'</span><span class="p">),</span>
        <span class="s1">'level'</span>  <span class="o">=&gt;</span> <span class="s1>'debug'</span><span class="p">,</span>
        <span class="s1">'days'</span>   <span class="o">=&gt;</span> <span class="mi">30</span><span class="p">,</span>
    <span class="p">],</span>

<span class="p">],</span>
</code></pre>

Ardından middleware:

class RequestLogger
    {
        public const HEADER_NAME          = ;
        public const REQUEST_ID_ATTRIBUTE = ;
    <span class="k">public</span> <span class="k">function</span> <span class="n">handle</span><span class="p">(</span><span class="kt">Request</span> <span class="nv">$request</span><span class="p">,</span> <span class="kt">Closure</span> <span class="nv">$next</span><span class="p">):</span> <span class="kt">Response</span>
    <span class="p">{</span>
        <span class="nv">$requestId</span> <span class="o">=</span> <span class="p">(</span><span class="n">string</span><span class="p">)</span> <span class="nc">Str</span><span class="o">::</span><span class="nf">uuid</span><span class="p">();</span>

        <span class="c1">// İstek içinde iç erişim için isteğe özelliklerde saklayın</span>
        <span class="nv">$request</span><span class="o">-&gt;</span><span class="n">attributes</span><span class="o">-&gt;</span><span class="nf">set</span><span class="p">(</span><span class="k">self</span><span class="o">::</span><span class="no">REQUEST_ID_ATTRIBUTE</span><span class="p">,</span> <span class="nv">$requestId</span><span class="p">);</span>
        <span class="c1">// Ayrıca bir header olarak ayarlayın — aşağı sistemler ve tarayıcı okuyabilir</span>
        <span class="nv">$request</span><span class="o">-&gt;</span><span class="n">headers</span><span class="o">-&gt;</span><span class="nf">set</span><span class="p">(</span><span class="k">self</span><span class="o">::</span><span class="no">HEADER_NAME</span><span class="p">,</span> <span class="nv">$requestId</span><span class="p">);</span>

        <span class="c1">// shareContext, her Log:: çağrısını otomatik olarak makes context'e enjekte eder</span>
        <span class="c1">// bu isteğe özgü herhangi bir logda işlenir — manuel işlem gerektirmeden</span>
        <span class="nc">Log</span><span class="o">::</span><span class="nf">shareContext</span><span class="p">([</span><span class="s1>'request_id'</span> <span class="o">=&gt;</span> <span class="nv">$requestId</span><span class="p">]);</span>

        <span class="nv">$startedAt</span> <span class="o">=</span> <span class="nb">hrtime</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span> <span class="c1">// Monotonik saat — microtime()'dan daha doğru</span>
        <span class="nv">$response</span>  <span class="o">=</span> <span class="nv">$next</span><span class="p">(</span><span class="nv">$request</span><span class="p">);</span>
        <span class="nv">$response</span><span class="o">-&gt;</span><span class="n">headers</span><span class="o">-&gt;</span><span class="nf">set</span><span class="p">(</span><span class="k">self</span><span class="o">::</span><span class="no">HEADER_NAME</span><span class="p">,</span> <span class="nv">$requestId</span><span class="p">);</span>

        <span class="nv">$this</span><span class="o">-&gt;</span><span class="nf">logRequest</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$response</span><span class="p">,</span> <span class="nv">$requestId</span><span class="p">,</span> <span class="nv">$startedAt</span><span class="p">);</span>

        <span class="k">return</span> <span class="nv">$response</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">function</span> <span class="n">logRequest</span><span class="p">(</span>
        <span class="kt">Request</span> <span class="nv">$request</span><span class="p">,</span>
        <span class="kt">Response</span> <span class="nv">$response</span><span class="p">,</span>
        <span class="kt">string</span> <span class="nv">$requestId</span><span class="p">,</span>
        <span class="kt">int</span> <span class="nv">$startedAt</span>
    <span class="p">):</span> <span class="kt">void</span> <span class="p">{</span>
        <span class="nc">Log</span><span class="o">::</span><span class="nf">channel</span><span class="p">(</span><span class="s1>'request'</span><span class="p">)</span><span class="o">-&gt;</span><span class="nf">info</span><span class="p">(</span><span class="s1>'request.completed'</span><span class="p">,</span> <span class="p">[</span>
            <span class="s1>'request_id'</span>  <span class="o">=&gt;</span> <span class="nv">$requestId</span><span class="p">,</span>
            <span class="s1>'method'</span>      <span class="o">=&gt;</span> <span class="nv">$request</span><span class="o">-&gt;</span><span class="nf">method</span><span class="p">(),</span>
            <span class="s1>'path'</span>        <span class="o">=&gt;</span> <span class="nv">$request</span><span class="o">-&gt;</span><span class="nf">getPathInfo</span><span class="p">(),</span>
            <span class="s1>'status'</span>      <span class="o">=&gt;</span> <span class="nv">$response</span><span class="o">-&gt;</span><span class="nf">getStatusCode</span><span class="p">(),</span>
            <span class="s1>'duration_ms'</span> <span class="o">=&gt;</span> <span class="nb">round</span><span class="p">((</span><span class="nb">hrtime</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span> <span class="o">-</span> <span class="nv">$startedAt</span><span class="p">)</span> <span class="o">/</span> <span class="mi">1_000_000</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span>
            <span class="s1>'user_id'</span>     <span class="o">=&gt;</span> <span class="nv">$request</span><span class="o">-&gt;</span><span class="nf">user</span><span class="p">()</span><span class="o">?-&gt;</span><span class="nf">getAuthIdentifier</span><span class="p">(),</span>
            <span class="s1>'ip'</span>          <span class="o">=&gt;</span> <span class="nv">$request</span><span class="o">-&gt;</span><span class="nf">ip</span><span class="p">(),</span>
        <span class="p">]);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>

Küresel middleware olarak kaydedin:

// Laravel 11 — bootstrap/app.php
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->append(\App\Http\Middleware\RequestLogger::class);
    })
<span class="c1">// Laravel 10 ve öncesi — app/Http/Kernel.php</span>
<span class="k">protected</span> <span class="nv">$middleware</span> <span class="o">=</span> <span class="p">[</span>
    <span class="c1">// ...</span>
    <span class="nc">\App\Http\Middleware\RequestLogger</span><span class="o">::</span><span class="n">class</span><span class="p">,</span>
<span class="p">];</span>
</code></pre>

Bu noktadan itibaren uygulamanızdan yazılan her günlük girişi artık istek ID'sini otomatik olarak içerecektir. İşte storage/logs/request.log'de bir istek günlüğü girişi:

{
      "message": "request.completed",
      "request_id": "9d4e2f1a-83bc-4a7c-b291-7e5f3d9a1c84",
      "method": "PATCH",
      "path": "/transactions/1101",
      "status": 200,
      "duration_ms": 43.2,
      "user_id": 42,
      "ip": "192.168.1.10",
      "level": "info",
      "level_name": "INFO"
    }
    

Ve bir model değişikliği günlüğü girişi storage/logs/activities/transactions/1100/transaction_1101.log:

{
      "event": "updated",
      "request_id": "9d4e2f1a-83bc-4a7c-b291-7e5f3d9a1c84",
      "time": "2024-03-15 14:32:07",
      "model_id": 1101,
      "changes": { "amount": { "old": "5000.00", "new": "500.00" }
      },
      "user_id": 42,
      "ip": "192.168.1.10",
      "user_agent": "Mozilla/5.0 ..."
    }
    

Aynı request_id her iki dosyada da görünür. Bu, ilişkiyi sağlar. Bir destek bileti "Transaction 1101'de bir şey değişti" dediğinde, o ID'yi her iki kayıt arasında inceleyerek tüm resme hızla ulaşabilirsiniz.

Neden Log::shareContext() yerine ID'yi her yere geçmek yerine? shareContext() verilen veriyi, isteğin geri kalanı boyunca her Log:: çağrısına otomatik olarak enjekte eder. İstek ID'sini hizmetlerinize, model gözlemcilerinize veya Gate kancalarınıza geçmeniz gerekmez. Neyi kaydederseniz kaydedin, istek ID'si zaten orada olacaktır.

Neden rastgele bir tamsayı yerine UUID? Altı haneli rastgele bir tam sayı, eşzamanlı yük altında gerçek çakışma olasılığı taşır. UUID ise 128 bitlik bir rastgelelik sunar — çakışma gerçek bir endişe değildir. Ayrıca, ID yanıt başlığına (X-Request-Id) de eklenir, böylece bir destek bileti açan kullanıcı bunu dahil edebilir ve siz onu birkaç saniye içinde bulabilirsiniz.

Neden cevapsız bir girişi tek bir yapılandırılmış giriş yerine ayrı ayrı istek-in ve yanıt-out girdileri yerine kullanıyoruz? Bir isteğe bir giriş, yöntemi, yolu, durum kodunu ve süresini — bir satırda — çapraz referans olmadan görmenizi sağlar. hrtime(true) monotonik saati, microtime()dan daha doğru bir süre ölçüsü sağlar çünkü sistem saatinin ayarlamalarından etkilenmez.

Bir not: X-Forwarded-For: Uygulamanız bir yük dengeleyici arkasında olduğunda, request()->ip() proxy'nin IP'sini döndürür, kullanıcıya değil. TrustProxies middleware bunu çözer — yapılandırıldığında, request()->ip() gerçek istemci IP'sini döndürür. Bunu harici API çağrılarına geçirin, böylece alt sistem logları da gerçek kullanıcıyı kaydetsin, sunucunuzun adresini değil.




Yavaş sorgular: burada bulunduğunuzda ekleyin

Bu durum denetim izi ile ilgili ayrı bir endişe — ama zaten günlükleme altyapısını kurarken, eklemek neredeyse hiçbir maliyet gerektirmiyor ve bir performans sorunu üretime düştüğünde çok şey kazandırıyor.

Fikir: yavaş sorguları, yavaşlığın ciddiyetine göre eşleşen önem seviyeleriyle kaydedin.

// AppServiceProvider.php
    // Bunu üretim dışı ortamlara veya bir yapılandırma bayrağı arkasına işle
    // yüksek trafikli sistemlerde — DB::listen her sorguda ateş eder.
    if (config() || config()) {
        DB::listen(function ($query) {
            $time = $query->time;
        <span class="k">match</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span> <span class="p">{</span>
            <span class="nv">$time</span> <span class="o">&gt;</span> <span class="mi">10000</span> <span class="o">=&gt;</span> <span class="nc">Log</span><span class="o">::</span><span class="nf">channel</span><span class="p">(</span><span class="s1>'query'</span><span class="p">)</span><span class="o">-&gt;</span><span class="nf">critical</span><span class="p">(</span><span class="s2>"Aşırı yavaş (</span><span class="si">{</span><span class="nv">$time</span><span class="si">}</span><span class="s2>ms)"</span><span class="p">),</span>
            <span class="nv">$time</span> <span class="o">&gt;</span> <span class="mi">1000</span>  <span class="o">=&gt;</span> <span class="nc">Log</span><span class="o">::</span><span class="nf">channel</span><span class="p">(</span><span class="s1>'query'</span><span class="p">)</span><span class="o">-&gt;</span><span class="nf">error</span><span class="p">(</span><span class="s2>"Çok yavaş (</span><span class="si">{</span><span class="nv">$time</span><span class="si">}</span><span class="s2>ms)"</span><span class="p">),</span>
            <span class="nv">$time</span> <span class="o">&gt;</span> <span class="mi">100</span>   <span class="o">=&gt;</span> <span class="nc">Log</span><span class="o">::</span><span class="nf">channel</span><span class="p">(</span><span class="s1>'query'</span><span class="p">)</span><span class="o">-&gt;</span><span class="nf">warning</span><span class="p">(</span><span class="s2>"Yavaş (</span><span class="si">{</span><span class="nv">$time</span><span class="si">}</span><span class="s2>ms)"</span><span class="p">),</span>
            <span class="k">default</span>       <span class="o">=&gt;</span> <span class="kc">null</span><span class="p">,</span> <span class="c1">// Hızlı sorgular gürültü ekler, ancak değer katmaz</span>
        <span class="p">};</span>
    <span class="p">});</span>
<span class="p">}</span>
</code></pre>

Neden süreyi günlük seviyesine eşlemek gerekiyor? Çünkü log seviyeleri bir anlam taşır — ya da taşımalıdır. Eğer her şey info ise, hiçbiri öne çıkmaz. 12 saniyelik bir sorgu critical olarak kaydedilmişse, bu, kritik günlük girdileri için tetiklenen herhangi bir uyarı kuralını harekete geçirir, ek bir izleme kodu yazmadan.

Neden yapılandırma kapısı? DB::listen her bir sorguda ateş eder. Yüksek trafikli bir üretim ortamında, ek yük gerçektir. Bunu app.debug veya özel bir yapılandırma bayrağının arkasına alarak kontrol edebilirsiniz, böylece ne zaman çalıştığını yönetebilirsiniz.

100ms'lik uyarı eşiği, kayıp indeksler ve N+1 problemlerini görselleştirmenin en hızlı yoludur. Loglarda uyarıyı görüyorsunuz, indeksi ekliyorsunuz, uyarı duruyor. Hiçbir kullanıcı şikayet etmedi.

Bu durumda — opsiyoneldir. Eğer bu bölgeden başka bir şey eklemezseniz, denetim izi yine de çalışır. Sorgu dinleyici, kazancını hak eden düşük maliyetli bir ekleme olmuştur, gereksinim değildir.




Model değişiklik günlüğü

Bir istek ID'si sistemi boyunca geçip gittiğinde, model düzeyindeki değişiklikler izlenebilir bir hikayede anlamlı girişler haline gelir, yalıtılmış veritabanı gerçekleri değil.

Uygulama, denetim gerektiren herhangi bir Eloquent model üzerinde bir trait olarak şöyle olmalı:

trait ModelChangeLogger
    {
        public static function bootModelChangeLogger(): void
        {
            static::updating(function ($model) {
                $changed = array_diff_assoc(
                    $model->getAttributes(),
                    $model->getOriginal()
                );
            <span class="c1">// updated_at her güncellemede bulunur. O her zaman ilginç değildir.</span>
            <span class="k">unset</span><span class="p">(</span><span class="nv">$changed</span><span class="p">[</span><span class="s1>'updated_at'</span><span class="p">]);</span>

            <span class="k">if</span> <span class="p">(</span><span class="k">empty</span><span class="p">(</span><span class="nv">$changed</span><span class="p">))</span> <span class="p">{</span>
                <span class="k">return</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="c1">// Alan bazında bir fark oluşturun — yalnızca yeni değerleri değil,</span>
            <span class="c1">// her alan *nereden* neye değişti</span>
            <span class="nv">$diff</span> <span class="o">=</span> <span class="p">[];</span>
            <span class="k">foreach</span><span class="p">(</span><span class="nv">$changed</span> <span class="k">as</span> <span class="nv">$key</span> <span class="o">=&gt;</span> <span class="nv">$newValue</span><span class="p">)</span> <span class="p">{</span>
                <span class="nv">$diff</span><span class="p">[</span><span class="nv">$key</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
                    <span class="s1>'old'</span> <span class="o">=&gt;</span> <span class="nv">$model</span><span class="o">-&gt;</span><span class="nf">getOriginal</span><span class="p">(</span><span class="nv">$key</span><span class="p">)</span> <span class="o">??</span> <span class="s1>'N/A'</span><span class="p">,</span>
                    <span class="s1>'new'</span> <span class="o">=&gt;</span> <span class="nv">$newValue</span><span class="p">,</span>
                <span class="p">];</span>
            <span class="p">}</span>

            <span class="nv">$model</span><span class="o">-&gt;</span><span class="nf">logChanges</span><span class="p">(</span><span class="nv">$diff</span><span class="p">,</span> <span class="s1>'updated'</span><span class="p">);</span>
        <span class="p">});</span>

        <span class="k">static</span><span class="o">::</span><span class="nf">created</span><span class="p">(</span><span class="k">function</span> <span class="p">(</span><span class="nv">$model</span><span class="p">)</span> <span class="p">{</span>
            <span class="nv">$model</span><span class="o">-&gt;</span><span class="nf">logChanges</span><span class="p">(</span><span class="nv">$model</span><span class="o">-&gt;</span><span class="nf">getAttributes</span><span class="p">(),</span> <span class="s1>'created'</span><span class="p">);</span>
        <span class="p">});</span>

        <span class="k">static</span><span class="o">::</span><span class="nf">deleting</span><span class="p">(</span><span class="k">function</span> <span class="p">(</span><span class="nv">$model</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// Silinmeden önce tam durumu yakalayın — silindikten sonra,</span>
            <span class="c1">// getAttributes() hiçbir yararlı şey döndürmez</span>
            <span class="nv">$model</span><span class="o">-&gt;</span><span class="nf">logChanges</span><span class="p">(</span><span class="nv">$model</span><span class="o">-&gt;</span><span class="nf">getAttributes</span><span class="p">(),</span> <span class="s1>'deleted'</span><span class="p">);</span>
        <span class="p">});</span>
    <span class="p">}</span>

    <span class="k">protected</span> <span class="k">function</span> <span class="n">prepareLogData</span><span class="p">(</span><span class="kt">array</span> <span class="nv">$changes</span><span class="p">,</span> <span class="kt">string</span> <span class="nv">$event</span><span class="p">):</span> <span class="kt">array</span>
    <span class="p">{</span>
        <span class="c1">// Hassas alanları yazmadan önce gizleyin — neyin değiştiğini değil,</span>
        <span class="c1">// neyin değiştiğini kaydedin</span>
        <span class="k">foreach</span><span class="p">(</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="n">maskedAttributes</span> <span class="o">??</span> <span class="p">[]</span> <span class="k">as</span> <span class="nv">$attr</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="k">isset</span><span class="p">(</span><span class="nv">$changes</span><span class="p">[</span><span class="nv">$attr</span><span class="p">]))</span> <span class="p">{</span>
                <span class="nv">$changes</span><span class="p">[</span><span class="nv">$attr</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
                    <span class="s1>'old'</span> <span class="o">=&gt;</span> <span class="s1>'[REDACTED]'</span><span class="p">,</span>
                    <span class="s1>'new'</span> <span class="o">=&gt;</span> <span class="s1>'[REDACTED]'</span><span class="p">];</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="p">[</span>
            <span class="s1>'event'</span>      <span class="o">=&gt;</span> <span class="nv">$event</span><span class="p">,</span>
            <span class="s1>'request_id'</span> <span class="o">=&gt;</span> <span class="nf">request</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">attributes</span><span class="o">-&gt;</span><span class="nf">get</span><span class="p">(</span><span class="nc">RequestLogger</span><span class="o">::</span><span class="no">REQUEST_ID_ATTRIBUTE</span><span class="p">,</span> <span class="s1>'N/A'</span><span class="p">),</span>
            <span class="s1>'time'</span>       <span class="o">=&gt;</span> <span class="nf">now</span><span class="p">()</span><span class="o">-&gt;</span><span class="nf">toDateTimeString</span><span class="p">(),</span>
            <span class="s1>'model_id'</span>   <span class="o">=&gt;</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="nf">getKey</span><span class="p">(),</span>
            <span class="s1>'changes'</span>    <span class="o">=&gt;</span> <span class="nv">$changes</span><span class="p">,</span>
            <span class="s1>'user_id'</span>    <span class="o">=&gt;</span> <span class="nf">auth</span><span class="p">()</span><span class="o">-&gt;</span><span class="nf">id</span><span class="p">(),</span>
            <span class="s1>'ip'</span>         <span class="o">=&gt;</span> <span class="nf">request</span><span class="p">()</span><span class="o">-&gt;</span><span class="nf">ip</span><span class="p">(),</span>
            <span class="s1>'user_agent'</span> <span class="o">=&gt;</span> <span class="nf">request</span><span class="p">()</span><span class="o">-&gt;</span><span class="nf">userAgent</span><span class="p">(),</span>
        <span class="p">];</span>
    <span class="p">}</span>

    <span class="c1">// Her modelda, düz metin olarak görünmemesi gereken alanları listelemek için geçersiz kılınması gerekir</span>
    <span class="k">protected</span> <span class="kt">array</span> <span class="nv">$maskedAttributes</span> <span class="o">=</span> <span class="p">[];</span>
<span class="p">}</span>
</code></pre>

Bir not: konsol bağlamı: ModelChangeLogger bir kuyruk işi veya zamanlanmış bir komut içinde çalıştığında, request()->attributes->get(REQUEST_ID_ATTRIBUTE) 'N/A' döndürür — bu amaçlı bir geri dönüş, bir hata değildir. Arka plan işlerindeki korelasyonu tutmak istiyorsanız, iş oluşturuşunda bir iş düzeyinde UUID oluşturun ve bunu Log::shareContext() aracılığıyla, handle() başında enjekte edin; RequestLogger'ın HTTP istekleri için yaptığı gibi.

Neden getOriginal() yerine yalnızca yeni değerleri kullanıyoruz? Çünkü "amount alanı şimdi 500" ifadesi, hikayenin yalnızca yarısıdır. "amount alanı 5000'den 500'e değişti" ifadesi, kanıttır. Bir anlaşmazlık durumunda, fark kanıtı sağlar — yalnızca mevcut durumu değil.

Neden silinmeden önce deleting'i yakalamak gerekir, silindikten sonra değil?

Yaygın hata: static::deleted yerine static::deleting kullanmak. deleted satır gidildiğinde ateş eder — getAttributes() hiçbir şey döndürmez. Her zaman deleting kullanmalısınız.

deleting satır kaldırılmadan önce ateş eder — tam kayıt hala bellekte bulunur. deleted sonrasında ateş eder ve getAttributes() o noktada boş bir dizi döndürür.




Dosya tabanlı loglar ve klasör segmentasyon problemi

Kaydedilecek günlüğün nereye kaydedileceği yönündeki karar, günlüğün mimarisini şekillendirir.

Veritabanı seçeneği caziptir — sorgulanabilir, dizinlenebilir ve Laravel uygulamasına doğal olarak uyar. Sorun şu: uygulamanızın yazabileceği bir tablo, uygulamanızın onu da değiştirebileceği anlamına gelir. Bir UPDATE audit_logs geçerli SQL'dir. Düzenlenmiş bir ortamda, değiştirilebilir denetim kayıtları, bir uyum sorunudur.

Yalnızca eklemeli günlük dosyaları değiştirilmesi daha zor olan bir yapıdır. OS düzeyinde FILE_APPEND her yazmanın sona eklenmesi anlamına gelir — güncelleme operasyonu yoktur, sadece ekleme vardır. Web kullanıcısının yazabileceği ancak silemeyeceği dosya sistemi izinleri ile birleştirildiğinde, gerçekten değiştirilmesi zor loglarınız olur.

protected function logChanges(array $changes, string $event): void
    {
        $logPath = $this->buildLogPath();
    <span class="nb">file_put_contents</span><span class="p">(</span>
        <span class="nv">$logPath</span><span class="p">,</span>
        <span class="nb">json_encode</span><span class="p">(</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="nf">prepareLogData</span><span class="p>(</span><span class="nv">$changes</span><span class="p">,</span> <span class="nv">$event</span><span class="p">))</span> <span class="mf">.</span> <span class="kc">PHP_EOL</span><span class="p">,</span>
        <span class="no">FILE_APPEND</span> <span class="o">|</span> <span class="no">LOCK_EX</span>  <span class="c1">// LOCK_EX, dosya yazımının bozulmasını önler</span>
    <span class="p">);</span>
<span class="p">}</span>
</code></pre>

Yaygın hata: Yalnızca FILE_APPEND kullanmak. Yoğun bir sistemde, birden fazla istek kaydları aynı anda değiştirdiklerinde, iki işlem yazım işlemlerini iç içe geçirebilir ve bir günlüğü bozabilir. LOCK_EX özel bir kilit alır - bir yazım tamamlandığında, bir sonraki başlar. Her iki bayrak da gereklidir.

Klasör yapısı sorunu: Her bir kaydın günlüğünü tablo adını taşıyan bir klasörde depolarsanız, sonunda transactions/ içinde her işlem için bir dosya elde edersiniz. Bir milyon işlem, bir milyon dosya demektir. Çoğu dosya sistemi bunu teknik olarak yönetir, ancak ls, yedekleme araçları ve dizin listeleme işlemleri rahatsız edici bir şekilde yavaşlayabilir.

Çözüm segmentasyondur — kayıtları kimlik aralığına göre alt dizinlerde gruplandırın:

protected function buildLogPath(): string
    {
        $id      = $this->getKey();
        $table   = $this->getTable();
    <span class="c1">// Segment klasörü: en yakın 100'e yuvarla</span>
    <span class="c1">// 1–99 ID'leri → klasör "0", 100–199 ID'leri → klasör "100", 1100–1199 ID'leri → klasör "1100"</span>
    <span class="nv">$segment</span> <span class="o">=</span> <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="p">(</span><span class="nb">floor</span><span class="p">(</span><span class="nv">$id</span> <span class="o">/</span> <span class="mi">100</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="p">);</span>

    <span class="nv">$folder</span> <span class="o">=</span> <span class="nf">storage_path</span><span class="p>(</span><span class="s2>logs/activities/</span><span class="si">{</span><span class="nv">$table</span><span class="si">}</span><span class="s2>/{</span><span class="nv">$segment</span><span class="si">}</span><span class="s2>"</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">is_dir</span><span class="p">(</span><span class="nv">$folder</span><span class="p">))</span> <span class="p">{</span>
        <span class="nb">mkdir</span><span class="p">(</span><span class="nv">$folder</span><span class="p">,</span> <span class="mo">0755</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nv">$filename</span> <span class="o">=</span> <span class="nb">strtolower</span><span class="p">(</span><span class="nf">class_basename</span><span class="p>(</span><span class="nv">$this</span><span class="p">))</span><span class="mf">.</span><span class="s2>_"</span><span class="si">{</span><span class="nv">$id</span><span class="si">}</span><span class="s2>.log"</span><span class="p">;</span>

    <span class="k">return</span> <span class="s2>"</span><span class="si>{</span><span class="nv">$folder</span><span class="si">}</span><span class="s2>/{</span><span class="nv">$filename</span><span class="si">}</span><span class="s2>"</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>

Disk üzerindeki sonuç:

storage/logs/activities/
        transactions/
            0/
                transaction_1.log
                transaction_42.log
            1100/
                transaction_1101.log
                transaction_1102.log
            1200/
                transaction_1200.log
        users/
            0/
                user_1.log
    

Her bir klasör en çok 100 dosya içerir. Belirli bir kaydın geçmişini bulmak, kimliğini bilmekle — ki her zaman bilirsiniz — bir dosyayı okumakla mümkündür. Yapı, herhangi bir kayıt sayısına kadar ölçeklenebilir, böylece hiçbir klasör aşırı derecede ağırlaşmaz.




Hassas alanları gizlemek

Plaintext milli kimlik numaraları veya şifreler içeren bir denetim izi, kendi uyumsuzluk sorununu oluşturur. Birçok yetkili bölgede, günlüğü açmamış PII içeren bir dosya, herhangi bir diğer PII depolama alanı gibi kabul edilir — saklama süreleri, erişim kontrolleri ve silme haklarına tabidir.

Buradaki yaklaşım, alanın değiştiğini kaydetmekle birlikte, neye değiştiğini kaydetmeyerek iz bırakmaktır. Bir denetçi, national_id'nin 14:32'de Transaction 1101'de değiştirildiğini görebilir — bu, denetim gereksinimini karşılar — ancak günlük dosyası gerçek değerleri tutmaz.

Hassas verileri depolayan herhangi bir modelde $maskedAttributes'ı geçersiz kılın:

class Transaction extends Model
    {
        use ModelChangeLogger;
    <span class="k">protected</span> <span class="kt">array</span> <span class="nv">$maskedAttributes</span> <span class="o">=</span> <span class="p">[</span>
        <span class="s1>'national_id'</span><span class="p">,</span>
        <span class="s1>'card_number'</span><span class="p">,</span>
    <span class="p">];</span>
<span class="p">}</span>
</code></pre>

RBAC'in bu resimde nereye oturduğu

Erişim kontrolü ve denetim izleri ilişkili problemdir. Kayıtları kimlerin değiştirdiğini kaydediyorsanız, kimlerin denemesi gereken bir şey yapmadığını da kaydetmelisiniz.

Laravel'in Gate'i, bunun için tam bir kancadır:

Gate::after(function (User $user, string $ability, bool $result) {
        if (!$result) {
            // request_id, Log::shareContext() aracılığıyla otomatik olarak dahil edilir
            // set in RequestLogger — burada manuel olarak almaya gerek yok
            Log::channel()->warning(, [
                => $user->id,
                => $ability,
                => request()->ip(),
            ]);
        }
    });
    

Neden özellikle hataları kaydetmeliyiz? Başarılı bir yetkilendirme girişimi normal bir çalışmadır. Başarısız bir girişim sinyaldir — bir kullanıcı, yetkisi olmadığı uç noktalara inceleme yapıyor olabilir veya ihlal edilmiş bir hesap yetkileri artırmaya çalışıyordur. Birden fazla başarısız girişim üzerindeki desen, bunu yakalayamadığınız bir erken uyarı haline gelir.

Belirtmeye değer bir tasarım ilkesi: izinleri yetenekler etrafında tasarlayın, rol adları etrafında değil. $user->can('transaction.approve') ifadesi, kullanıcının ne yapabileceğini ifade eder; sahip olduğu rol etiketinden bağımsız. $user->role === 'admin' ifadesi, "admin" çeşitli bağlamlarda farklı şeyler ifade ettiğinde aniden kırılır — büyüyen sistemlerde bu her zaman sonunda olur.




Artık Ne Sahipsiniz

RequestLogger'ı küresel middleware olarak kaydettiniz. ModelChangeLogger traitini her hassas veya denetime tabi veriyi işleyen modelinize eklediniz. Gate::after kancasını kaydettiniz.

Artık: her istek bir ID taşır. Her model değişikliği, kimin yaptığını, hangi alanın hangi değerden hangi değere değiştiğini, ne zaman ve hangi isteğin neden olduğunu kaydediyor. Başarısız izin kontrolü kayıt altına alınıyor. Yığınınız boyunca her log satırı ortak bir ilişki ID'sini paylaşır.

Bir destek bileti geldiğinde "Transaction 1101'de bir şey değişti" dendiğinde, storage/logs/activities/transactions/1100/transaction_1101.log dosyasını açarsınız. Tam farkı, bunu yapan kullanıcıyı ve istek ID'sini görürsünüz. O istek ID'sini istek günlüklerinde greplerseniz, tüm resmi bir dakika içinde görebilirsiniz.

Bu, çalışan bir uygulama ile her şeyi hatırlayan bir uygulama arasındaki farktır.


Bu makaleden gelen ana içgörü: Veri tabanı denetim tablosu değiştirilebilir — uygulama kodunuz onu UPDATE edebilir. Her kayıdı işleyen, istek ID'sinin her girişe bağlı olduğu, değişikliklerin kim, ne ve neden sorularını cevapladığı günlüğü dosyası, size kırılmaya dayanak, iç içe geçmiş kayıtlar sunar.



Göndermeden Önce — Kontrol Listesi

  • [ ] RequestLogger bootstrap/app.php'de küresel middleware olarak kaydedildi
  • [ ] ModelChangeLogger traiti hassas veya denetime tabi veri işleyen her modele eklendi
  • [ ] $maskedAttributes PII (parolalar, kimlik numaraları, kart numaraları) saklayan modellerde tanımlandı
  • [ ] storage/logs/activities/ web kullanıcısı tarafından yazılabilir, ancak silinemezdir
  • [ ] DB::listen özel bir yapılandırma bayrağının veya app.debug'nin arkasında yer alıyor — üretimde koşmuyor
  • [ ] Gate::after kancası AuthServiceProvider'da kaydedildi
  • [ ] Bilinen bir istek ID'si için yapılan bir grep, hem istek günlüğü hem de model değişiklik günlükleri arasında sonuç döndürür

Sonraki: Bölüm 2 — Kuyruk Mimarisi: Geri Plan Çalışmalarını Tasarlamak

Kaynak: Orijinal Makale

Contents
  • Ne Yapacaksınız
  • Bir denetim izi gerçekten neyi kanıtlamalı
  • İstek ID’si: Her günlüğe bir ipucu
  • Yavaş sorgular: burada bulunduğunuzda ekleyin
  • Model değişiklik günlüğü
  • Dosya tabanlı loglar ve klasör segmentasyon problemi
  • Hassas alanları gizlemek
  • RBAC'in bu resimde nereye oturduğu
  • Artık Ne Sahipsiniz
  • Göndermeden Önce — Kontrol Listesi
Laravel’da Yapay Zeka Destekli Form Doğrulaması: Mantık Hatalarını Erken Yakala
Laravel’de Eloquent ORM’ye Giriş
Laravel Artık Yerel Passkey’lere Sahip: laravel/passkeys için Kapsamlı Bir Rehber
Deploynix ile Cloudflare Kullanımı: CDN, DDoS Koruması ve SSL Yapılandırması
function formatRupiah() fonksiyonu
Bu Makaleyi Paylaş
Facebook Bağlantıyı Kopyala Yazdır
Paylaş
Önceki Makale Overwatch Arena’da En Son Gelişmeler ve Tam Kadro Bilgileri
Sonraki Makale Cursor Şirketi 50 Milyar Dolar Değerlendirme İle 2 Milyar Dolar Yükseltmeye Hazırlanıyor

Sanal Medya

FacebookBeğen
452Takip Et
PinterestSabitle
237Takip Et

Son Eklenenler

Donanım Meraklıları: Computex 2026, 3. Gün – Taipei’de Sıcaklık Artıyor
Donanım
Kritik Uyarı: 2026 FIFA Dünya Kupası Dolandırıcılıklarına Dikkat!
Siber Güvenlik
Acil! Cisco, SD-WAN’da yamanmamış sıfır gün açığına dikkat çekti
Siber Güvenlik
Surface Laptop Ultra’nın RTX Spark Süper Çip için 110W TDP hedefi
Donanım
Acil: PCPJack 230 AWS ve Google Cloud Sunucusunu Ele Geçirdi!
Siber Güvenlik
Bellek Çökmesi Yaşamadan Milyonları İşleme: Laravel Lazy Collections
Yazılım
//

Siber güvenlik, yapay zeka ve savunma sanayiinden; finans ve sinema dünyasına uzanan geniş bir yelpaze. Teknomers; teknoloji, strateji ve yazılım dünyasını sade bir dille sizlerle buluşturuyor.

Kurumsal

  • Hakkımızda
  • Gizlilik politikası
  • Tanıtım Yazısı ve Backlink Hizmeti

Kategoriler

  • Teknoloji
  • Oyun
  • Sinema
  • Siber Güvenlik
  • Bilim
  • Finans
  • Dünyadan Güncel Haberler

Populer

  • TV'de Ücretsiz İzlenebilen Şifresiz Erotik Kanallar (2025 Güncel Frekans Listesi)

  • The Last of Us PC Kontrolleri: Hızlı Silah Değiştirme ve Tüm Tuşlar (2025)

  • Hogwarts Legacy'de Odaklanma İksiri Nasıl Yapılır?

Teknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor HaberleriTeknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor Haberleri
Bizi Takip Et
© 2026 Teknomers. All Rights Reserved.
Welcome Back!

Sign in to your account

Kullanıcı Adı veya E-posta Adresi
Şifre

Şifrenizi mi unuttunuz?