PostgreSQL ile Merkezi Audit Log: Kim, Ne Zaman, Neyi Değiştirdi?
Uygulamanızda yapılan tüm değişiklikleri otomatik olarak kaydetmek ister misiniz? PostgreSQL trigger ile tek bir fonksiyonla tüm tablolarınız için audit log kurma rehberi.
Sorun: “Bu Veriyi Kim Değiştirdi?”
Kurumsal bir uygulamada kaçınılmaz sorular:
- “Bu siparişin durumunu kim değiştirdi?”
- “Stok miktarı ne zaman güncellendi?”
- “Kullanıcı rolü kim tarafından değiştirildi?”
Loglama yoksa cevap: “Bilmiyoruz.”
Audit log, uygulamanızdaki her değişikliğin kaydını tutar. Kim, ne zaman, neyi, nasıl değiştirdi — hepsi kayıt altında.
Çözüm: Tek Bir Trigger Fonksiyonu
Her tablo için ayrı ayrı log yazmak yerine, tek bir generic fonksiyon yazıyoruz ve istediğimiz tablolara bağlıyoruz:
1. Log Tablosu
CREATE TABLE audit_log (
id BIGSERIAL PRIMARY KEY,
user_email TEXT, -- Kim yaptı?
table_name TEXT NOT NULL, -- Hangi tablo?
action TEXT NOT NULL, -- INSERT / UPDATE / DELETE
record_id TEXT, -- Hangi kaydı?
old_data JSONB, -- Eski değerler
new_data JSONB, -- Yeni değerler
changed_fields TEXT[], -- Sadece değişen alanlar
created_at TIMESTAMPTZ DEFAULT NOW()
);
2. Trigger Fonksiyonu
Bu fonksiyon herhangi bir tablodaki değişikliği otomatik olarak audit_log’a yazar:
CREATE OR REPLACE FUNCTION audit_trigger_fn()
RETURNS TRIGGER AS $$
DECLARE
v_changed TEXT[];
v_key TEXT;
BEGIN
-- UPDATE ise sadece değişen alanları bul
IF TG_OP = 'UPDATE' THEN
FOR v_key IN SELECT jsonb_object_keys(to_jsonb(NEW))
LOOP
IF to_jsonb(OLD) ->> v_key IS DISTINCT FROM to_jsonb(NEW) ->> v_key THEN
v_changed := array_append(v_changed, v_key);
END IF;
END LOOP;
-- Hiçbir şey değişmediyse log oluşturma
v_changed := array_remove(v_changed, 'updated_at');
IF v_changed IS NULL OR array_length(v_changed, 1) IS NULL THEN
RETURN NEW;
END IF;
END IF;
-- Audit kaydı yaz
INSERT INTO audit_log (
user_email, table_name, action, record_id,
old_data, new_data, changed_fields
) VALUES (
COALESCE(auth.jwt() ->> 'email', 'system'),
TG_TABLE_NAME,
TG_OP,
CASE WHEN TG_OP = 'DELETE' THEN OLD.id::TEXT ELSE NEW.id::TEXT END,
CASE WHEN TG_OP = 'INSERT' THEN NULL ELSE to_jsonb(OLD) END,
CASE WHEN TG_OP = 'DELETE' THEN NULL ELSE to_jsonb(NEW) END,
v_changed
);
RETURN CASE WHEN TG_OP = 'DELETE' THEN OLD ELSE NEW END;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
3. Tablolara Bağlama
-- İstediğiniz tablolara tek satırla bağlayın
CREATE TRIGGER audit_siparisler
AFTER INSERT OR UPDATE OR DELETE ON orders
FOR EACH ROW EXECUTE FUNCTION audit_trigger_fn();
CREATE TRIGGER audit_stok
AFTER INSERT OR UPDATE OR DELETE ON inventory
FOR EACH ROW EXECUTE FUNCTION audit_trigger_fn();
-- İstediğiniz kadar tablo ekleyebilirsiniz
Ne Görürsünüz?
Audit log’a baktığınızda şöyle bir tablo görürsünüz:
Tarih │ Kullanıcı │ Tablo │ İşlem │ Değişen
───────────────────┼─────────────────┼──────────┼────────┼──────────
12 Nis 14:30 │ ahmet@firma.com │ orders │ UPDATE │ status
12 Nis 14:25 │ mehmet@firma.com│ inventory│ UPDATE │ miktar
12 Nis 14:20 │ ayse@firma.com │ orders │ INSERT │ —
Artık “kim değiştirdi?” sorusunun cevabı var.
Sorgulama Örnekleri
-- "Bu siparişe ne oldu?"
SELECT created_at, user_email, action, changed_fields
FROM audit_log
WHERE table_name = 'orders' AND record_id = '1234'
ORDER BY created_at DESC;
-- "Bugün kim ne yaptı?"
SELECT user_email, table_name, action, COUNT(*)
FROM audit_log
WHERE created_at >= CURRENT_DATE
GROUP BY user_email, table_name, action;
Bu Fonksiyonun Güçlü Yanları
- Tek fonksiyon — istediğiniz tabloya bağlayın, hepsinde çalışır
- Akıllı: UPDATE’lerde sadece değişen alanları kaydeder
- Sessiz: Hiçbir şey değişmediyse log oluşturmaz (gereksiz gürültü yok)
Dikkat Edilecekler
- Toplu işlemler: 5000 satır güncellediğinizde 5000 log satırı oluşur. Toplu işlemler için özet log fonksiyonu kullanın.
- Tablo büyümesi: Audit tablosu zamanla büyür. 6 aydan eski logları temizleyin.
- Performans: Her INSERT/UPDATE/DELETE’de 1-2ms ek süre ekler.
Bu Yaklaşım Temel Senaryolar İçin Yeter
5-10 tablo için bu yapı mükemmel çalışır. Ama 30+ tablo, toplu senkronizasyon (batch) operasyonları, ve admin panel entegrasyonu (filtreleme, diff viewer, export) gerektiğinde daha ileri çözümler gerekir.
Biz 30+ tabloluk bir yapıda bu sistemi çalıştırıyoruz. İhtiyacınız olursa deneyimimizden faydalanabilirsiniz.
Bu yazı AstaFlow Case Study serisinin bir parçasıdır.