---
title: 'Denetim Kaydı'
source: 'https://academia.sh/tr/kurslar/backend-uretim/denetim-kaydi'
course: 'Sunucu Güvenliği ve Üretime Alma'
language: tr
updated: '2026-08-17T18:06:49+00:00'
license: 'CC BY-SA 4.0'
---

# Denetim Kaydı

İzlenebilirlik gereksiniminin dört ölçüsü: hangi karar noktasının kaydedildiği ve buna bağlı olarak kaç sorunun yanıtlanabildiği, zincirleme özetin yakaladığı değişiklik ile yakalayamadığı kuyruk kesme, kaydın kendi taşıdığı kişisel veri ve saklama süresinin hacme etkisi.

Önceki iki ders iki yapılandırma kararını ölçtü ve ikisi de bir soruyu ortada bıraktı: kim, ne
zaman. Uygulamanın kendi kayıtları isteği ve hatayı yazar; hangi görevlinin hangi üyenin cezasını
sildiğini yazmaz. **Denetim kaydı** bu boşluğu doldurur ve Vaka Çalışmaları kursundaki
**denetim izinden** amacıyla ayrılır: orada iz, durumu yeniden kurmaya yarayan bir veri yapısı
kararıydı; burada kayıt, bir kararın sorumlusunu **sonradan gösterebilmek** için tutulur ve bu
yüzden kendisinin de değiştirilmemiş olması gerekir.

İzlenebilirlik dört soruyla ölçülür. Hangi olay kaydediliyor — yani sistemin karar noktalarının
kaçı yazılıyor. Kayıt sonradan değiştirilmemiş olduğunu kanıtlayabiliyor mu. Kaydın kendisi ne
taşıyor. Ve ne kadar süre saklanıyor. Dördü de yapılandırma kararıdır ve yanlış ayarlandıklarında
sistem hata vermez.

## Karar Noktaları

**BG16 — ödünç sisteminin on iki karar noktası vardır** ve günlük hacimleri aşağıdaki tabloda
durur; toplamı 3.012 olaydır. **BG17 — her kayıt dört alan taşır:** tür, aktör, hedef, sonuç. Depo
`node:sqlite` üzerindedir ve her kaydın özeti bir öncekinin özetini kapsar.

```js
// kayit.mjs — denetim kaydi deposu: karar noktalari, zincirleme ozet, dogrulama
import { createHash } from 'node:crypto';
import { DatabaseSync } from 'node:sqlite';

export const TOHUM = 4231;
let durum = TOHUM;
const rast = () => (durum = (durum * 1103515245 + 12345) % 2147483648) / 2147483648;
const sec = (n) => Math.floor(rast() * n);

// Karar noktalari: [tur, gunluk sayi, durum degistirir mi, aktor sinifi, hedef sinifi]
export const TURLER = [
  ['GIRIS', 1200, false, 'uye', 'kendi'], ['GIRIS_RET', 60, false, 'uye', 'kendi'],
  ['GORUNTULE', 900, false, 'gorevli', 'uye'], ['ODUNC', 400, true, 'uye', 'katalog'],
  ['IADE', 380, true, 'uye', 'katalog'], ['KATALOG_DUZENLE', 45, true, 'gorevli', 'katalog'],
  ['CEZA_SIL', 12, true, 'gorevli', 'uye'], ['DISA_AKTARIM', 4, false, 'gorevli', 'uye'],
  ['YETKI_DEGISIKLIGI', 5, true, 'gorevli', 'uye'], ['UYE_SIL', 3, true, 'gorevli', 'uye'],
  ['DAGITIM', 2, true, 'gorevli', 'sistem'], ['DONDURME', 1, true, 'gorevli', 'sistem'],
];
export const UYE = 40, GOREVLI = 6, KATALOG = 60;

export function gunuUret() {
  const olaylar = [];
  for (const [tur, sayi, yazar, aktorSinifi, hedefSinifi] of TURLER) {
    for (let i = 0; i < sayi; i++) {
      const aktor = aktorSinifi === 'uye' ? `U-${1 + sec(UYE)}` : `G-${1 + sec(GOREVLI)}`;
      const hedef = hedefSinifi === 'kendi' ? aktor
        : hedefSinifi === 'uye' ? `U-${1 + sec(UYE)}`
          : hedefSinifi === 'katalog' ? `K-${800 + sec(KATALOG)}` : 'sistem';
      olaylar.push({ tur, aktor, hedef, yazar, sira: rast(), sonuc: tur === 'GIRIS_RET' ? 'ret' : 'kabul' });
    }
  }
  return olaylar.sort((a, b) => a.sira - b.sira);
}

// Zincirleme ozet: her kaydin ozeti bir oncekinin ozetini de kapsar.
export const ozetle = (sira, once, govde) =>
  createHash('sha256').update(`${sira}|${once}|${govde}`).digest('hex').slice(0, 16);

export function depoAc(olaylar, takma = null) {
  const db = new DatabaseSync(':memory:');
  db.exec('create table kayit (sira integer primary key, tur text, aktor text, hedef text, sonuc text, govde text, ozet text)');
  const ekle = db.prepare('insert into kayit values (?, ?, ?, ?, ?, ?, ?)');
  let once = 'baslangic';
  olaylar.forEach((o, i) => {
    const [aktor, hedef] = takma ? [takma(o.aktor), takma(o.hedef)] : [o.aktor, o.hedef];
    const govde = JSON.stringify({ tur: o.tur, aktor, hedef, sonuc: o.sonuc });
    once = ozetle(i + 1, once, govde);
    ekle.run(i + 1, o.tur, aktor, hedef, o.sonuc, govde, once);
  });
  return db;
}

// Dogrulama zinciri bastan yurur; ilk uyusmazligin sirasini dondurur, yoksa 0.
export function dogrula(db) {
  let once = 'baslangic';
  for (const k of db.prepare('select * from kayit order by sira').all()) {
    once = ozetle(k.sira, once, k.govde);
    if (once !== k.ozet) return k.sira;
  }
  return 0;
}
```

## Üç Kayıt Politikası

Kaydedilecek olayları seçmenin üç yaygın biçimi vardır ve üçü de savunulabilir görünür: yalnız
durumu değiştiren işlemleri yazmak, yalnız reddedilen yetki isteklerini yazmak, bütün karar
noktalarını yazmak. Aralarındaki farkı ölçmenin yolu hacim değil, **yanıtlanabilen soru
sayısıdır**. Aşağıdaki koşum sekiz hesap sorabilme sorusunu her üç kayda da gerçek sorgu olarak
sorar; soruların hedefleri kaydın kendisinden okunur.

```js
// olcum.mjs — uc kayit politikasi, zincirin yakaladigi ve yakalamadigi, kaydin kendi gizliligi
import { createHash } from 'node:crypto';
import { gunuUret, depoAc, dogrula, ozetle, TURLER, TOHUM } from './kayit.mjs';

const olaylar = gunuUret();
const tam = depoAc(olaylar);
const tek = (s) => tam.prepare(s).get();
const uyeCeza = tek("select hedef from kayit where tur='CEZA_SIL'").hedef;
const silinen = tek("select hedef from kayit where tur='UYE_SIL'").hedef;
const katalog = tek("select hedef from kayit where tur='KATALOG_DUZENLE'").hedef;
const gorevli = tek("select aktor from kayit where tur='GORUNTULE' group by aktor order by count(*) desc").aktor;

const sorular = [
  [`${uyeCeza} uyesinin cezasini kim sildi`, `select aktor from kayit where tur='CEZA_SIL' and hedef='${uyeCeza}'`],
  [`${gorevli} kac ayri uyenin kaydina bakti`, `select distinct hedef from kayit where tur='GORUNTULE' and aktor='${gorevli}'`],
  [`${katalog} kaydini kim duzenledi`, `select aktor from kayit where tur='KATALOG_DUZENLE' and hedef='${katalog}'`],
  ['hangi hesap dortten cok basarisiz giris aldi', "select hedef from kayit where tur='GIRIS_RET' group by hedef having count(*) > 4"],
  ['imza anahtarini kim dondurdu', "select aktor from kayit where tur='DONDURME'"],
  ['dagitimi kim baslatti', "select aktor from kayit where tur='DAGITIM'"],
  [`${uyeCeza} uyesinin verisine kimler bakti`, `select distinct aktor from kayit where hedef='${uyeCeza}' and tur in ('GORUNTULE','DISA_AKTARIM')`],
  [`silinen ${silinen} uyesinin kaydina kim eristi`, `select distinct aktor from kayit where hedef='${silinen}' and tur='GORUNTULE'`],
];

const politikalar = {
  'yalniz yazma': (o) => o.yazar,
  'yalniz yetki reddi': (o) => o.sonuc === 'ret',
  'butun kararlar': () => true,
};

const s = (x, n) => String(x).padStart(n);
console.log(`tohum ${TOHUM}: ${TURLER.length} karar noktasi, ${olaylar.length} olay/gun`);
console.log(`${'politika'.padEnd(20)}${s('kayit', 7)}${s('bayt', 8)}${s('yanitlanan soru', 17)}${s('yanitsiz soru', 18)}`);
const yanit = {};
for (const [ad, suzgec] of Object.entries(politikalar)) {
  const db = depoAc(olaylar.filter(suzgec));
  const bayt = db.prepare('select sum(length(govde)) as b, count(*) as n from kayit').get();
  yanit[ad] = sorular.map(([, sql]) => db.prepare(sql).all().length > 0);
  const say = yanit[ad].filter(Boolean).length;
  console.log(`${ad.padEnd(20)}${s(bayt.n, 7)}${s(bayt.b, 8)}${s(`${say}/8`, 17)}`
    + `${s(yanit[ad].map((v, i) => (v ? '' : i + 1)).filter(Boolean).join(',') || '-', 18)}`);
}
console.log(sorular.map(([ad], i) => `  ${i + 1}. ${ad}`).join('\n'));

// Zincir: govdeyi degistirmek, ozeti de tazelemek, kuyrugu kesmek.
const bozuk = depoAc(olaylar);
const kurban = 500;
const eski = bozuk.prepare('select govde, ozet from kayit where sira=?').get(kurban);
const yeni = eski.govde.replace('"sonuc":"kabul"', '"sonuc":"ret"');
bozuk.prepare('update kayit set govde=? where sira=?').run(yeni, kurban);
console.log(`\nzincir temizken dogrulama: ${dogrula(tam)}`);
console.log(`${kurban}. kaydin govdesi degistirildi -> ilk uyusmazlik ${dogrula(bozuk)}. sirada`);
const onceki = bozuk.prepare('select ozet from kayit where sira=?').get(kurban - 1).ozet;
bozuk.prepare('update kayit set ozet=? where sira=?').run(ozetle(kurban, onceki, yeni), kurban);
console.log(`ozet de tazelendi -> ilk uyusmazlik ${dogrula(bozuk)}. sirada`);

const kesik = depoAc(olaylar);
kesik.prepare('delete from kayit where sira > ?').run(olaylar.length - 25);
console.log(`son 25 kayit silindi -> dogrulama ${dogrula(kesik)} (0 = uyusmazlik yok)`);
for (const capa of [Infinity, 500, 100, 10]) {
  const sessiz = capa === Infinity ? olaylar.length : olaylar.length % capa;
  const ad = capa === Infinity ? 'capa yok       ' : `capa her ${s(capa, 3)} kayitta`;
  console.log(`${ad} -> bu kosumda ${s(sessiz, 4)} kayit sessizce silinebilir, en kotu ${capa === Infinity ? sessiz : capa - 1}`);
}

// Kaydin kendi gizliligi.
const kisisel = tam.prepare("select count(*) as n from kayit where aktor like 'U-%' or hedef like 'U-%'").get().n;
const gecmis = tam.prepare("select aktor, count(*) as n from kayit where tur='ODUNC' group by aktor order by n desc").all();
const tuzla = (tuz) => (d) => (d.startsWith('U-') ? `T-${createHash('sha256').update(tuz + d).digest('hex').slice(0, 8)}` : d);
const takma = depoAc(olaylar, tuzla('donem-1'));
const takmaGecmis = takma.prepare("select aktor, count(*) as n from kayit where tur='ODUNC' group by aktor order by n desc").all();
const donem2 = new Set(gecmis.map((g) => tuzla('donem-2')(g.aktor)));
const ortak = takmaGecmis.filter((g) => donem2.has(g.aktor)).length;
console.log(`\nkisi tanimlayan kayit ${kisisel}/${olaylar.length} (%${(100 * kisisel / olaylar.length).toFixed(1)})`);
console.log(`odunc gecmisi kurulabilen uye: acik kimlikle ${gecmis.length}, en uzun liste ${gecmis[0].n} kayit`);
console.log(`ayni olcum takma adli kayitta: ${takmaGecmis.length} uye, en uzun liste ${takmaGecmis[0].n} kayit`);
console.log(`donem tuzu degistiginde iki donemin ortak takma adi: ${ortak}`);

const bayt = tam.prepare('select sum(length(govde)) as b from kayit').get().b;
for (const gun of [30, 180, 3650]) {
  console.log(`saklama ${s(gun, 4)} gun -> ${s(olaylar.length * gun, 9)} kayit, `
    + `${s((bayt * gun / 1048576).toFixed(1), 7)} MiB, kisi-gun ${gecmis.length * gun}`);
}
```

```
tohum 4231: 12 karar noktasi, 3012 olay/gun
politika              kayit    bayt  yanitlanan soru     yanitsiz soru
yalniz yazma            848   52494              4/8           2,4,7,8
yalniz yetki reddi       60    3752              1/8     1,2,3,5,6,7,8
butun kararlar         3012  186509              8/8                 -
  1. U-12 uyesinin cezasini kim sildi
  2. G-1 kac ayri uyenin kaydina bakti
  3. K-834 kaydini kim duzenledi
  4. hangi hesap dortten cok basarisiz giris aldi
  5. imza anahtarini kim dondurdu
  6. dagitimi kim baslatti
  7. U-12 uyesinin verisine kimler bakti
  8. silinen U-27 uyesinin kaydina kim eristi

zincir temizken dogrulama: 0
500. kaydin govdesi degistirildi -> ilk uyusmazlik 500. sirada
ozet de tazelendi -> ilk uyusmazlik 501. sirada
son 25 kayit silindi -> dogrulama 0 (0 = uyusmazlik yok)
capa yok        -> bu kosumda 3012 kayit sessizce silinebilir, en kotu 3012
capa her 500 kayitta -> bu kosumda   12 kayit sessizce silinebilir, en kotu 499
capa her 100 kayitta -> bu kosumda   12 kayit sessizce silinebilir, en kotu 99
capa her  10 kayitta -> bu kosumda    2 kayit sessizce silinebilir, en kotu 9

kisi tanimlayan kayit 2964/3012 (%98.4)
odunc gecmisi kurulabilen uye: acik kimlikle 40, en uzun liste 18 kayit
ayni olcum takma adli kayitta: 40 uye, en uzun liste 18 kayit
donem tuzu degistiginde iki donemin ortak takma adi: 0
saklama   30 gun ->     90360 kayit,     5.3 MiB, kisi-gun 1200
saklama  180 gun ->    542160 kayit,    32.0 MiB, kisi-gun 7200
saklama 3650 gun ->  10993800 kayit,   649.2 MiB, kisi-gun 146000
```

## Sekiz Sorunun Yanıtı

Yalnız yazma işlemlerini kaydeden politika günlük 3.012 olayın 848'ini tutar ve sekiz sorunun
dördünü yanıtlar. Yanıtsız kalan dördü aynı sınıftandır: hepsi bir **okumayı** sorar. Bir görevlinin
kaç üyenin kaydına baktığı, bir üyenin verisine kimlerin eriştiği, silinen bir üyenin kaydına
silinmeden önce kimin girdiği — bunların hiçbiri durumu değiştirmez, dolayısıyla hiçbiri yazılmaz.
Kişisel veriye erişimin izlenebilirliği sorulduğunda bu politika yapısal olarak yanıtsızdır ve
yapılandırmasında bunu belirten bir işaret yoktur.

Yalnız reddedilen istekleri kaydeden politika 60 kayıtla en ucuzudur ve sekizde bir yanıt verir.
Yanıtladığı tek soru başarısız girişlerle ilgilidir. Bu politikanın sessiz varsayımı şudur:
kötüye kullanım reddedilmiş isteklerde görünür. Oysa yetkisi olan bir görevlinin dokuz yüz kayıt
görüntülemesi hiç reddedilmez ve bu kayıtta hiç görünmez.

Bütün karar noktalarını yazmak sekizde sekiz verir ve bedeli yaklaşık dört katı hacimdir (186.509
bayta karşılık 52.494). Hacim farkının nereden geldiği önemlidir: 3.012 olayın 2.100'ü giriş ve
görüntülemedir; yani izlenebilirliğin pahalı kısmı tam olarak yalnız yazma politikasının atladığı
kısımdır. Karar noktası saymak bu yüzden bir hacim tartışmasından önce gelir — hangi soruların
yanıtlanabilir olacağı, hangi olayın yazıldığıyla belirlenir.

## Zincirin Yakalamadığı

Kayıt, sorumluluğu göstermek için tutuluyorsa sonradan değiştirilmediğinin gösterilebilmesi
gerekir. Zincirleme özet bunu kısmen verir. Beş yüzüncü kaydın gövdesindeki sonuç alanı `kabul`
yerine `ret` yapıldığında doğrulama tam o sırada durur. Değiştiren kişi o kaydın özetini de
yeniden hesaplarsa uyuşmazlık bir sonraki kayda kayar (501): her özet bir öncekini kapsadığı için
tek bir kaydı düzeltmek ondan sonraki bütün kayıtları yeniden yazmayı gerektirir.

Kuyruk kesme farklıdır. Son yirmi beş kayıt silindiğinde doğrulama **sıfır** döndürür — yani hiçbir
uyuşmazlık yoktur. Kalan zincir kusursuzdur, çünkü zincir yalnızca kendisinin **iç tutarlılığını**
gösterir; kaç kaydın var olması gerektiğini bilmez. Bir kaydın silindiğini anlamanın tek yolu,
zincirin bir noktadaki özetini kaydın dışına, başka bir sorumluluğun altına yazmaktır. Çapa
dönemi bu boşluğun ölçüsüdür: çapa yoksa günün 3.012 kaydının tamamı sessizce silinebilir, beş
yüzde bir çapayla en kötü 499, onda bir çapayla en kötü 9 kayıt. Yanlış yapılandırma burada
kaydın **olmaması**dır ve olmayan kayıt hiçbir doğrulamayı kırmaz.

## Kaydın Kendi Riski

Bu günün kayıtlarının **%98,4'ü** bir kişiyi tanımlıyor. Denetim kaydı, tanımı gereği kimin ne
yaptığını yazar; dolayısıyla toplandığı anda sistemin en yoğun kişisel veri kümesi hâline gelir.
Ödünç kayıtlarından kırk üyenin tamamının okuma geçmişi kurulabiliyor, en uzun liste on sekiz
kayıt. Kütüphane sisteminde bu, üyelerin ne okuduğunun listesidir ve ödünç servisinin kendi
veritabanında iade sonrası silinmiş olsa bile denetim kaydında durmaya devam eder.

Üye kimliğini tuzlu bir özetle takma adlandırmak bu ölçüyü **hiç değiştirmiyor**: yine kırk üye,
yine on sekiz kayıtlık en uzun liste. Takma ad kimliği gizler, **bağlantıyı** gizlemez; aynı takma
ad aynı kişinin bütün kayıtlarını birbirine bağlamaya devam eder. Bağlantıyı kesen tek ayar tuzun
dönem başına değişmesidir ve ölçüsü son satırdadır: iki dönemin ortak takma adı sıfırdır. Bedeli de
oradadır — dönem tuzu atıldıktan sonra yedinci soru eski dönemler için sorulamaz, çünkü sorunun
hedefi olan üyenin o dönemdeki takma adı yeniden hesaplanamaz.

Saklama süresi son ayardır ve iki ölçüyü birden büyütür. Otuz gün 5,3 MiB ve 1.200 kişi-gün eder;
on yıl 649,2 MiB ve 146.000 kişi-gün. Hacim tarafı ucuzdur — on yıllık kayıt bir gigabaytın
altındadır ve bu yüzden saklama süresini uzatmak hiçbir yerde bir uyarı üretmez. Artan şey
maliyetin öteki yarısıdır: on yıl saklanan bir denetim kaydı, on yıllık okuma geçmişini bir arada
tutan bir veri kümesidir ve sızdığında sızan şey isteklerin listesi değil, kişilerin listesidir.

## Özet

- Denetim kaydı, denetim izinden amacıyla ayrılır: iz durumu yeniden kurar, kayıt bir kararın
  sorumlusunu gösterir ve bu yüzden kendi değiştirilemezliğini de kanıtlamak zorundadır.
- Yalnız yazma işlemlerini kaydetmek sekiz sorunun dördünü yanıtsız bıraktı ve yanıtsız kalanların
  hepsi bir okumayı soruyordu; yalnız reddedilen istekleri kaydetmek sekizde bir verdi.
- Bütün karar noktalarını yazmak sekizde sekiz verdi ve hacmi dört katına çıkardı; farkın tamamı
  giriş ile görüntüleme kayıtlarından geliyor.
- Zincirleme özet gövde değişikliğini 500. sırada, özeti tazelenmiş değişikliği 501. sırada
  yakaladı; son yirmi beş kaydın silinmesini hiç yakalamadı. Çapa dönemi bu sessiz boşluğun
  ölçüsüdür: çapasız 3.012, beş yüzde bir çapayla en kötü 499 kayıt.
- Kayıtların %98,4'ü bir kişiyi tanımlıyor ve kırk üyenin tamamının okuma geçmişi kurulabiliyor;
  takma adlandırma bu sayıyı değiştirmedi, yalnız dönemsel tuz iki dönem arasındaki bağı sıfırladı.
- Saklama süresini on yıla çıkarmak 649,2 MiB ve 146.000 kişi-gün eder; hacim uyarı üretmeyecek
  kadar küçüktür, kişi-gün ise saklama kararının asıl bedelidir.

## Sonraki Adım

Denetim kaydı sonradan sorar ve iyi yanıt verir, ama hiçbir şeyi durdurmaz. Bu günün kaydında bin
iki yüz giriş, altmış başarısız giriş ve dokuz yüz görüntüleme var; kayıt bunların kimden geldiğini
yazar, birinin bu sayıya **ulaşmaması gerektiğini** söylemez. Kırk üyelik bir sistemde altmış
başarısız girişin tek bir hesapta toplanması ile kırk hesaba dağılması aynı kayıt hacmini üretir ve
ikisi bambaşka şeylerdir. Sonraki ders bu ayrımı ele alır: sınırın kime uygulandığını, yanıtın
kendisinin bir hesabın var olup olmadığını nasıl ele verdiğini ve kilitlemenin doğru hesabı ne
kadar dışarıda bıraktığını sayar.
