İçeriğe geç
academia.sh

Ders 16 / 17

Hata Mesajları ve Günlük Sızıntısı

Hata yanıtı ve günlük satırındaki sızıntının kalem olarak sayılması: sekiz ucun doğrudan kipte kırk kalem-yanıt çifti taşıması, merkezî hata işleyicinin altı ucu örtüp iki ucu dışarıda bırakması, on bir günlük kaleminden üst düzey ad listesinin üçünü, her düzeyde çalışan listenin sekizini, biçime göre maskelemenin dokuzunu kapatması ve serbest metin ile bağlantı sorgusundaki iki kalemin hiçbir listeyle kapanmaması.

İçindekiler

Önceki ders sırrın kaynak ağacında ve depo geçmişinde bıraktığı izi saydı. Değer kaynaktan çıkarılıp bir depoya taşındığında bu iz kapanır — ama değer çalışma anında hâlâ süreçte durur ve süreç konuşur. Bir istek hata verdiğinde geriye dönen yanıt, bir işlem yapıldığında yazılan günlük satırı, bir çökme sonrası dosyaya düşen yığıt izi: hepsi sürecin içindeki değerlerin dışarı çıktığı noktalardır.

Bu noktaların ortak yanı, hiçbirinin bir güvenlik kararı olarak yazılmamış olmasıdır. Yığıt izini yanıta koyan satır hata ayıklamak için oradadır, günlüğe isteği bütün alanlarıyla yazan satır sorunu bulmak için. Ölçü bu yüzden “hangi satır yanlış” değil, kaç kalem dışarı çıkıyor ve merkezî bir katman bunların kaçını kapatıyor sorusudur.

Hata Yanıtındaki Kalemler

KI10 — bir hata yanıtı, uygulama içi bilgi taşıyan kalemlerin toplamıdır; her kalem ayrı sayılır. Yığıt izi, iç dosya yolu, iç bileşen adı, hata sınıfının adı ve ham isteğin yankısı beş ayrı kalemdir ve bir yanıt bunların hepsini birden taşıyabilir. KI11 — merkezî bir hata işleyici yalnızca kendisine ulaşan hatayı örter; hatasını kendi yakalayıp kendi yanıtını yazan uç kapsamanın dışındadır.

// gunluk/yanit.mjs — gercek bir yerel sunucu ayaga kaldirilir, sekiz uca istek atilir ve
// donen yanit govdesinde ic bilgi tasiyan kalemler sayilir. Govdeler basilmaz (mutlak yol
// icerir); yalnizca kalem sayilari yazilir. "kendi" isaretli iki uc hatasini kendi yakalayip
// kendi yanitini yaziyor, yani merkezi isleyiciye hic ulasmiyor. Kurgu olcum agi uclari.
import { createServer } from 'node:http';
import { randomUUID } from 'node:crypto';

// [yol, hatayi atan islev, kendi yanitini mi yaziyor]
const UCLAR = [
  ['/abone/ara?ad=kurgu-abone-118', () => { throw new Error('abone-dizini/arama.mjs: dizin yaniti bos'); }, false],
  ['/fatura/hesap?no=118', () => { throw new RangeError('fatura-hesap/donem.mjs: donem araligi disi'); }, false],
  ['/olcum/al?sayac=SAY-4417', () => { throw new TypeError('olcum-alim/paket.mjs: alan bekleniyordu'); }, true],
  ['/rapor/uret?tur=aylik', () => { throw new Error('rapor-uretici/sablon.mjs: sablon bulunamadi'); }, false],
  ['/is-emri/ata?ekip=3', () => { throw new Error('is-emri/atama.mjs: ekip kapasitesi asildi'); }, false],
  ['/saha/yukle?ad=olcum.csv', () => { throw new Error('saha-yukleme/depo.mjs: depo yaniti gecersiz'); }, true],
  ['/portal/giris?kullanici=kurgu-abone-118', () => { throw new Error('portal-kimlik/dogrula.mjs: karsilastirma basarisiz'); }, false],
  ['/bildirim/gonder?kanal=kisa', () => { throw new Error('bildirim-kuyruk/gonder.mjs: kuyruk dolu'); }, false],
];

let kip = 'dogrudan';
const sunucu = createServer((istek, yanit) => {
  const uc = UCLAR.find(([u]) => u === istek.url);
  try {
    uc[1]();
  } catch (hata) {
    const acik = kip === 'dogrudan' || (kip === 'merkezi' && uc[2]);
    const govde = acik
      ? { hata: hata.message, tur: hata.constructor.name, iz: hata.stack, istek: istek.url }
      : { hata: 'islem tamamlanamadi', kimlik: randomUUID() };
    yanit.writeHead(500, { 'content-type': 'application/json' });
    yanit.end(JSON.stringify(govde));
  }
});
sunucu.listen(8794, '127.0.0.1');

const ISARETLER = [
  ['yigit izi', (g) => /\bat\s+\S+\s*\(/.test(g)],
  ['ic dosya yolu', (g) => /\.mjs/.test(g)],
  ['ic bilesen adi', (g) => /(abone-dizini|fatura-hesap|olcum-alim|rapor-uretici|is-emri|saha-yukleme|portal-kimlik|bildirim-kuyruk)/.test(g)],
  ['hata sinifi', (g) => /"tur":"(Error|RangeError|TypeError)"/.test(g)],
  ['istek yankisi', (g) => /"istek":/.test(g)],
  ['korelasyon kimligi', (g) => /"kimlik":/.test(g)],
];

const yaz = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(''));
const D = [-32, 11, 15, 16, 12, 15, 20];
yaz(D, 'kip', ...ISARETLER.map(([a]) => a));

for (const k of ['dogrudan', 'merkezi', 'kendi yaniti kapatilmis']) {
  kip = k === 'kendi yaniti kapatilmis' ? 'kapali' : k;
  const sayac = ISARETLER.map(() => 0);
  for (const [yol] of UCLAR) {
    const govde = await (await fetch(`http://127.0.0.1:8794${yol}`)).text();
    ISARETLER.forEach(([, kural], i) => { if (kural(govde)) sayac[i] += 1; });
  }
  yaz(D, k, ...sayac);
}
sunucu.close();

const kalem = ISARETLER.length - 1;
const kendi = UCLAR.filter((u) => u[2]).length;
console.log(`\nuc ${UCLAR.length}, yanit basina olculen kalem ${kalem}, `
  + `dogrudan kipte kalem-yanit cifti ${UCLAR.length * kalem}`);
console.log(`merkezi isleyici ${UCLAR.length - kendi} ucu ortuyor, ${kendi} uc disarida kaliyor; `
  + `disarida kalan kalem-yanit cifti ${kendi * kalem}`);
kip                               yigit izi  ic dosya yolu  ic bilesen adi hata sinifi  istek yankisi  korelasyon kimligi
dogrudan                                  8              8               8           8              8                   0
merkezi                                   2              2               2           2              2                   6
kendi yaniti kapatilmis                   0              0               0           0              0                   8

uc 8, yanit basina olculen kalem 5, dogrudan kipte kalem-yanit cifti 40
merkezi isleyici 6 ucu ortuyor, 2 uc disarida kaliyor; disarida kalan kalem-yanit cifti 10

Merkezden Geçmeyen Uç

Birinci satır ölçünün başlangıç noktasıdır: sekiz ucun sekizi de beş kalemin beşini birden taşıyor, yani 40 kalem-yanıt çifti. Bu, kimsenin bir karar vermediği durumdur — hata nesnesi olduğu gibi yanıta yazılmıştır ve yığıt izi sunucunun dosya düzenini, hata sınıfı iç mantığı, ham istek yankısı ise gönderilen değerin geri döndüğünü söyler.

İkinci satır merkezî işleyicinin kapsamasını veriyor ve kursun kuralının en açık örneğidir. Sekiz ucun altısı örtülüyor, kalem sayısı sıfıra iniyor ve yerine bir korelasyon kimliği geliyor. Kalan iki uç — ölçüm alım ucu ile saha yükleme ucu — hatasını kendi yakalayıp kendi yanıtını yazdığı için merkezî işleyiciye hiç ulaşmıyor. Bu iki uçta beş kalemin beşi de duruyor: dışarıda kalan çift sayısı 10. Kapsama 40’tan 10’a inmiştir, sıfıra değil.

Buradaki ayrım önemlidir: merkezî işleyici kusurlu değildir, örttüğü altı uçta kusursuz çalışır. Kapsamayı düşüren şey işleyicinin kalitesi değil, onu atlamanın mümkün olmasıdır. Kendi hata yanıtını yazan bir uç, gözden geçirmede çoğunlukla iyi bir alışkanlık gibi görünür — hatayı yerinde ele almak. Üçüncü satır ise kuralı yazıyor: yerel yanıt yazımı kapatıldığında sekiz ucun sekizi de korelasyon kimliğine iniyor ve dışarıda kalan kalem sıfır oluyor.

Korelasyon kimliği sütunu bu düzenin bedelini de taşıyor. Yanıttan çıkarılan beş kalem yok olmuyor; bir kimliğe bağlanıp içeri taşınıyor. Bir arıza aramasında o kimlikle iç kayda gidilir ve yığıt izi orada bulunur. Ölçünün söylediği şey, sızıntının kapatılmadığı, yalnızca yer değiştirdiğidir: yanıt sekiz istemciye açıktır, iç kayıt belirli bir okuyucu kümesine. İkinci ölçüm tam da bu taşınan yığının kendisini sayıyor, çünkü günlük yanıttan daha uzun süre saklanır ve daha çok yere kopyalanır.

Günlük Satırındaki Kalemler

Yanıt dışarıya gider, günlük içeride kalır — ama günlük de bir yere yazılır, bir yere taşınır ve çoğunlukla yanıttan daha uzun süre saklanır. KI12 — maskeleme katmanı alan adına bakarak çalışır; adı olmayan bir yerde duran değer hiçbir ad listesiyle kapatılmaz.

// gunluk/maskeleme.mjs — kurgu gunluk kayitlarinda maskeleme katmaninin kapsamasi (model).
// Degerler kurgudur; hicbiri gercek bir sir bicimi ya da gercek bir kisiye ait veri degildir.
const T = 'kurgubelirtec7f2a9c41d8b3e6';
const IMZA = 'kurguimza4b7d2e9a1c8f5306';
const P = 'kurgu-parola-9c1x', AD = 'A. Yilmaz', ADRES = 'Kurgu Mah. 12/4';
const TEL = '05000000000', KN = '10000000146';

// [kayit, o kayitta gecen duyarli deger listesi, sizinti turu]
const kayitlar = [
  [{ olay: 'giris', kullanici: 'kurgu-abone-118', belirtec: T }, [T], 'ust duzey alan'],
  [{ olay: 'kayit', abone: { aboneAdi: AD, adres: ADRES } }, [AD, ADRES], 'ic ice alan'],
  [{ olay: 'hata', mesaj: `dogrulama basarisiz: ${P}` }, [P], 'serbest metin'],
  [{ olay: 'istek', hedef: `/portal/giris?belirtec=${T}` }, [T], 'baglanti sorgusu'],
  [{ olay: 'toplu', kayitlar: [{ telefon: TEL }, { kimlikNo: KN }] }, [TEL, KN], 'dizi ogesi'],
  [{ olay: 'esitleme', ust: T }, [T], 'listede olmayan ad'],
  [{ olay: 'yenile', baglam: { oturum: { imza: IMZA } } }, [IMZA], 'ucuncu duzey'],
  [{ olay: 'bildirim', aboneAdi: AD, telefon: TEL }, [AD, TEL], 'ust duzey alan'],
];
const ZARARSIZ = ['a1b2c3d4e5f60718293a4b5c6d7e8f90', '9f3c2a1b8d7e6540c9b2a1f0e3d4c5b6'];
const zararsizKayitlar = [{ olay: 'izleme', istekKimligi: ZARARSIZ[0] }, { olay: 'yapi', surum: ZARARSIZ[1] }];

const ADLAR = ['belirtec', 'parola', 'imza', 'kimlikNo', 'telefon', 'aboneAdi', 'adres'];
const BICIM = (d) => typeof d === 'string' && /^[A-Za-z0-9]{20,}$/.test(d);

function maskele(deger, { derin, bicim }, duzey = 0) {
  if (Array.isArray(deger)) return derin ? deger.map((x) => maskele(x, { derin, bicim }, duzey + 1)) : deger;
  if (deger && typeof deger === 'object') {
    if (!derin && duzey > 0) return deger;
    return Object.fromEntries(Object.entries(deger).map(([k, v]) =>
      [k, ADLAR.includes(k) ? '***' : maskele(v, { derin, bicim }, duzey + 1)]));
  }
  return bicim && BICIM(deger) ? '***' : deger;
}

const yaz = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(''));
const KALEM = kayitlar.reduce((a, [, d]) => a + d.length, 0);
const D = [-34, 8, 10, 12, 14];
yaz(D, 'katman', 'kalem', 'kapanan', 'disarida', 'yanlis alarm');

const sonuc = {};
for (const [ad, ayar] of [
  ['katman yok', null],
  ['ad listesi, yalniz ust duzey', { derin: false, bicim: false }],
  ['ad listesi, her duzeyde', { derin: true, bicim: false }],
  ['ad listesi + bicime gore', { derin: true, bicim: true }],
]) {
  let kalan = 0;
  const kalanTur = {};
  for (const [kayit, duyarli, tur] of kayitlar) {
    const metin = JSON.stringify(ayar ? maskele(kayit, ayar) : kayit);
    const k = duyarli.filter((d) => metin.includes(d)).length;
    kalan += k;
    if (k) kalanTur[tur] = (kalanTur[tur] ?? 0) + k;
  }
  const alarm = ayar ? zararsizKayitlar.filter((k) =>
    !JSON.stringify(maskele(k, ayar)).includes(ZARARSIZ[zararsizKayitlar.indexOf(k)])).length : 0;
  sonuc[ad] = kalanTur;
  yaz(D, ad, KALEM, KALEM - kalan, kalan, alarm);
}

console.log('\ndisarida kalan kalemlerin turu:');
for (const [ad, tur] of Object.entries(sonuc)) {
  if (ad === 'katman yok') continue;
  console.log(`  ${ad.padEnd(30)}${Object.entries(tur).map(([t, n]) => `${t} ${n}`).join(', ') || '-'}`);
}
katman                               kalem   kapanan    disarida  yanlis alarm
katman yok                              11         0          11             0
ad listesi, yalniz ust duzey            11         3           8             0
ad listesi, her duzeyde                 11         8           3             0
ad listesi + bicime gore                11         9           2             2

disarida kalan kalemlerin turu:
  ad listesi, yalniz ust duzey  ic ice alan 2, serbest metin 1, baglanti sorgusu 1, dizi ogesi 2, listede olmayan ad 1, ucuncu duzey 1
  ad listesi, her duzeyde       serbest metin 1, baglanti sorgusu 1, listede olmayan ad 1
  ad listesi + bicime gore      serbest metin 1, baglanti sorgusu 1

Adı Olmayan Değer

Sekiz günlük kaydında on bir duyarlı kalem var ve bunlar iki ayrı sınıftan geliyor: bir bölümü sır niteliğinde değerdir — belirteç, parola, imza — bir bölümü kişisel veridir. İki sınıfın kapatma gerekçesi farklıdır. Sır sızıntısı bir yetkiyi devreder ve döndürmeyle kapanır; kişisel veri sızıntısı devredilebilir bir şey vermez ama geri alınamaz, çünkü döndürülecek bir karşılığı yoktur. Aynı maskeleme katmanı ikisini birden örter, dolayısıyla kalem sayımı ikisini ayırmadan yapılabilir; dışarıda kalan kalemin hangi sınıftan olduğu ise önceliklendirmeyi belirler.

İkinci satır çoğu kurulumun başlangıç noktasıdır — bir ad listesi vardır ve yalnızca üst düzey alanlara bakar. On bir kalemin üçünü kapatıyor, sekizi dışarıda kalıyor. Dışarıda kalanların türleri alt tabloda: iç içe alan iki, dizi öğesi iki, üçüncü düzey bir, serbest metin bir, bağlantı sorgusu bir, listede olmayan ad bir.

Üçüncü satır tek bir değişikliğin ne kadar getirdiğini gösteriyor: aynı ad listesi her düzeyde ve dizi içinde de çalıştığında kapanan kalem üçten sekize çıkıyor. Yapısal bir düzeltmedir, yeni bir kural gerektirmez ve tek başına kapsamanın yarısından fazlasını kazandırır. Geriye üç kalem kalıyor ve üçü de aynı sebepten kalıyor: değerin durduğu yerin bir alan adı yok.

Dördüncü satır biçime göre maskelemeyi ekliyor — yirmi karakterden uzun, yalnız harf ve rakamdan oluşan değerler maskeleniyor. Kapanan kalem dokuza çıkıyor; kazanılan tek kalem “listede olmayan ad” satırıdır, çünkü değerin adı bilinmese de biçimi tanınıyor. Bedeli aynı satırda yazılı: iki yanlış alarm. İzleme kimliği ve yapı sürüm damgası da aynı biçime uyduğu için maskeleniyor ve bunlar tam olarak arıza aramak için okunan alanlardır. Maskeleme burada bir güvenlik kazancı karşılığında bir teşhis kaybı satın alıyor.

Son iki kalem hiçbir düzende kapanmıyor. Biri serbest metin bir hata mesajının içine gömülü bir parola, öteki bir bağlantının sorgu bölümündeki belirteçtir. İkisinin de bir alan adı yoktur; biri bir cümlenin parçası, öteki bir dizgenin parçasıdır. Maskeleme katmanı bunları ancak dizge içeriğini ayrıştırarak bulabilir ve o noktada artık bir maskeleme katmanı değil, bir metin taramasıdır — kapsaması belirsiz, yanlış alarmı ölçülmemiş bir savunma.

Yeni Nokta Eklendiğinde

Üçüncü sütun iki ölçümde iki ayrı yanıt veriyor. Hata yanıtı tarafında kalıp kendiliğinden kapsıyor: yerel yanıt yazımı kapatıldığında yarın eklenecek dokuzuncu uç da merkezî işleyiciden geçmek zorundadır ve hiçbir karar gerektirmez. Kapsama uç sayısından bağımsız olarak tamdır.

Günlük tarafında ise kalıp kendiliğinden kapsamıyor. Her düzeyde çalışan ad listesi yeni bir kaydı kapsar, ama yeni bir alan adını kapsamaz: yarın günlüğe yazılacak duyarlı bir alan, adı listede yoksa görünmeye devam eder. Bu dersin ölçümünde bunun karşılığı “listede olmayan ad” satırıdır ve biçime göre maskeleme onu ancak biçim tanınırsa yakalar. İki savunmanın farkı buradadır: biri bir yolu kapatır, öteki bir listeyi günceller. Yol kapatan savunma yeni noktayı kendiliğinden kapsar, liste güncelleyen savunma her yeni noktada bir kez daha hatırlanmayı bekler.

Özet

  • Sekiz ucun her biri beş iç bilgi kalemi taşıyor; hata nesnesi doğrudan yanıta yazıldığında dışarı çıkan kalem-yanıt çifti 40.
  • Merkezî hata işleyici altı ucu örtüyor, kendi yanıtını yazan iki uç dışarıda kalıyor ve on çift ayakta duruyor; yerel yanıt yazımı kapatıldığında sayı sıfıra iniyor.
  • Sekiz günlük kaydında on bir duyarlı kalem var; yalnız üst düzeye bakan ad listesi üçünü, her düzeyde ve dizi içinde çalışan aynı liste sekizini kapatıyor.
  • Biçime göre maskeleme kapsamayı dokuza çıkarıyor ve iki yanlış alarm üretiyor; maskelenen iki değer arıza aramak için okunan izleme kimliği ile sürüm damgasıdır.
  • Serbest metne gömülü değer ile bağlantı sorgusundaki değer hiçbir düzende kapanmıyor, çünkü ikisinin de bir alan adı yok.
  • Yeni uç merkezî işleyici zorunluyken kendiliğinden kapsanıyor; yeni günlük alanı ise ad listesi güncellenene kadar kapsanmıyor.

Sonraki Adım

Bu ders sürecin dışarı konuştuğu noktaları saydı ve hepsinde ölçülen şey kendi yazdığımız kodun davranışıydı: hangi satır yığıt izini yanıta koyuyor, hangi alan maskeleniyor. Oysa bir isteği karşılayan kodun büyük bölümü ekibin yazdığı satırlar değildir. Aynı süreçte onlarca bileşen yüklüdür, her biri başka bileşenleri çeker ve hiçbirinin adı bizim yazdığımız bir yerde geçmez. Sonraki ders bu kümeyi bir çizge olarak ele alır: bağımlılıkların geçişli derinliği ne, tek bir bileşen kaç paketi etkiliyor ve hiç çağrılmayan bir bileşendeki açık neden başka türlü önceliklendirilir.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat