Ana Sayfa / Blog / Audit log mimarisi: immutable trail kurarken dikkat edilmesi gerekenler

Audit log mimarisi: immutable trail kurarken dikkat edilmesi gerekenler

Regülasyon, güvenlik ve forensic için audit log şart. Append-only store, tamper detection, retention policy, search performansı üzerine üretim deneyimleri.

Audit log: kim, ne zaman, neyi değiştirdi. Finans, sağlık, güvenlik gerektiren her domain’de zorunlu. Hatta olmayan yerlerde bile “production’da bu kayıt niye değişti?” sorusunun cevabı audit log.

2 projede audit log mimarisi tasarladım: bir fintech compliance için, bir SaaS admin action’ları için. Temel mimari ortak, detaylar farklı. Bu yazıda pratik notlar.

Audit log nedir, log’dan farkı ne

Application log ve audit log aynı şey değil:

Application log: debug, info, warn, error. Geliştirici için. Genelde mutable (rotated, archived). Structured ama öncelik hız, comprehensiveness değil.

Audit log: business event’lerin kaydı. “User X 14:32’de order Y’yi cancel etti”. Immutable. Legal-grade kayıt.

App log’u audit için kullanmak yanlış. Rotation ile kayboluyor, yanlış retention, sorgulanabilir değil.

Temel pattern: append-only

Audit log append-only olmalı. Insert var, update/delete yok. Kayıt sonradan değiştirilirse veya silinirse güvenilirlik kaybolur.

CREATE TABLE audit_log (
    id BIGSERIAL PRIMARY KEY,
    timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    actor_id UUID NOT NULL,
    actor_type VARCHAR(50) NOT NULL,  -- 'user', 'system', 'api_key'
    action VARCHAR(100) NOT NULL,
    resource_type VARCHAR(50),
    resource_id VARCHAR(100),
    before_state JSONB,
    after_state JSONB,
    metadata JSONB,
    ip_address INET,
    user_agent TEXT
);

-- No UPDATE, no DELETE permission
REVOKE UPDATE, DELETE ON audit_log FROM application_user;

DB user’ın update/delete yetkisi yok. Sadece insert. Bu application bug’ı yüzünden log’un bozulmasını önlüyor.

Tamper detection

DB admin delete edebiliyor, backup restore log’u geriye alabiliyor. Tam güvence için hash chain:

def compute_hash(entry, previous_hash):
    data = f"{entry.timestamp}|{entry.actor_id}|{entry.action}|{entry.resource_id}|{previous_hash}"
    return hashlib.sha256(data.encode()).hexdigest()

Her yeni kayıt önceki kaydın hash’ini içeriyor. Zincir kırılırsa (kayıt silindi/değiştirildi) hash mismatch.

Blockchain’e benzer ama DB tablosu. Düzenli tamper check job’ı zinciri validate ediyor.

Çok hassas durumda external system’e de hash push edin (AWS QLDB, WORM storage, immutable external log service).

Ne loglanacak

Everyone different. Minimum set:

  • Authentication events (login, logout, 2FA, password change)
  • Authorization changes (role grant/revoke, permission change)
  • Resource access (sensitive data read)
  • Resource modification (CRUD on key entities)
  • Configuration changes (feature flag toggle, API key generate)
  • Admin actions (impersonation, bulk operations)
  • Export/download actions (GDPR data access)

Loglamayacağınız şeyler:
– Password plaintext
– PII beyond minimum (credit card full number, TCKN full)
– Session tokens
– Sensitive response bodies

PII varsa tokenize edin. user_email yerine user_id. Audit log kompromize olursa PII leak olmasın.

Write path: async ama güvenli

Audit log write sync yapılırsa request latency’yi arttırır. Async yapılırsa kayıp riski var.

Hybrid:

  1. In-memory queue’ya push (sync, hızlı)
  2. Background worker batch insert (DB’ye async)
  3. Queue overflow’da block write veya fail-open
def log_event(event):
    try:
        audit_queue.put_nowait(event)
    except queue.Full:
        # Critical: audit log kritikse sync fallback
        audit_db.insert(event)
        alert.send("Audit queue overflow")

Kritik sistemlerde audit write başarısız olursa action block edin. Bankacılıkta “transfer yaptık ama audit yazamadık” kabul edilmez.

Search performansı

Audit log büyüyor. 1 yılda 100M+ row kolay. Search yavaşlıyor.

Index stratejisi:

CREATE INDEX idx_audit_actor ON audit_log(actor_id, timestamp DESC);
CREATE INDEX idx_audit_resource ON audit_log(resource_type, resource_id, timestamp DESC);
CREATE INDEX idx_audit_action_time ON audit_log(action, timestamp DESC);
CREATE INDEX idx_audit_time_brin ON audit_log USING BRIN(timestamp);

BRIN index zaman-sıralı büyük tablolar için ideal. B-tree’den 10x küçük, sequential access için hızlı.

JSONB field’ları GIN index ile:

CREATE INDEX idx_audit_metadata ON audit_log USING GIN(metadata);

Query: “son 30 günde user X’in yaptığı tüm action’lar”:

SELECT * FROM audit_log 
WHERE actor_id = 'xxx' 
  AND timestamp > NOW() - INTERVAL '30 days'
ORDER BY timestamp DESC;

Index varsa millisecond.

Partitioning: 1M+ row için

Audit log 10M row’u geçiyorsa partitioning şart. PostgreSQL native partitioning:

CREATE TABLE audit_log (
    ...
) PARTITION BY RANGE (timestamp);

CREATE TABLE audit_log_2026_01 PARTITION OF audit_log
    FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE audit_log_2026_02 PARTITION OF audit_log
    FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');

Her ay ayrı partition. Eski partition’lar cold storage’a taşınıyor, yeni partition hızlı yazılıyor.

pg_partman extension partition management otomatize ediyor.

Retention ve archive

Ne kadar tutuyorsunuz? Regulation’a göre:

  • GDPR: kişisel veri minimal
  • KVKK: 10 yıl (işleme amacına göre değişiyor)
  • PCI-DSS: 1 yıl (payment-related)
  • SOX: 7 yıl (financial-related)

Hot storage (DB’de active): son 90 gün
Warm storage (S3 Glacier / cold DB): 2 yıl
Cold storage (tape, archive): 7-10 yıl

Archiving pipeline:

  1. Monthly partition hot’ta yaşıyor
  2. 90 gün sonra warm’a export (Parquet format S3’e)
  3. 2 yıl sonra Glacier’a
  4. 10 yıl sonra silme (regulation’a göre)

Admin UI: kullanılmayan audit log kötü

Audit log kurulmuş ama kimse okumuyor = israf. Admin UI şart:

  • Timeline view: user’ın veya resource’un history
  • Filter: action type, actor, date range
  • Export to CSV
  • Diff view: before vs after state side-by-side
  • Alerting: suspicious pattern (çoklu başarısız login, hassas data export)

Built-in log viewer yoksa sistem audit’sizdir pratikte. Admin 500K kayıt içinde grep yapmaz.

Forensic patterns

Incident investigation:

“Account compromised”: user’ın login history, IP değişimleri, unusual action’lar.

“Data leak”: son 90 günde kim export etti, kim bulk read yaptı.

“Settings changed maliciously”: config change history, kim yaptı.

“Shadow IT”: sistem account’ları dışında kimler yazma yaptı.

Query template’leri hazırda olsun. Crisis anında ilk 10 dakika “nereye bakayım” dönemi olmasın.

Immutability ile ilgili kritik not

Audit log immutable ama kişisel veri silme hakkı var (GDPR right to erasure). Nasıl uzlaşılır?

Çözüm: audit log’da user’ın PII’si doğrudan tutulmuyor. Reference ID var. User hesap silindiğinde reference hala geçerli ama PII linkleri boşa çıkıyor.

before_state: {"user_id": "12345", "action": "role_changed"}
-- user_id 12345 sonradan kullanıcı 'deleted-user-12345' olarak anonymize edildi

Audit trail bozulmuyor ama PII silinmiş.

Cost optimization

Audit log büyük yer tutuyor. Cost optimization:

  • JSONB yerine compressed text (eğer search yapmayacaksan)
  • Duplicate action’ları dedupe etmeyin (audit trail integrity bozulur, ama identical request ID aynı oluyor, application-level dedupe OK)
  • Regular partitioning + archiving
  • Cold storage cheap (S3 Glacier)

Bir fintech’te audit log hot storage 50 GB, yıllık arşiv 800 GB, toplam maliyet aylık $40. Regulation’a göre şart olduğu için maliyet kabul.

Son tavsiye

Audit log günü kurtaran feature. İlk incident’e kadar “niye bunu yapıyoruz” diyorsunuz, ilk incident’te “iyi ki yapmışız”.

Minimum viable audit log:
– Append-only table
– Hash chain tamper detection
– Actor + resource + timestamp + before/after JSONB
– Admin viewer UI
– Retention policy

Bu 5 madde olmadan production’a kompleks bir sistem çıkarmak risk. Compliance gerekmezse bile incident investigation için değerli.

Bu konuda bir projeniz mi var?

Kısa bir özet bırakın, 24 saat içinde size dönüş yapayım.

İletişime Geç