Ders 16 / 20
Günlükleme
Serbest metin yerine yapılandırılmış kayıt, düzey eşiği, isteğe bağlı alanların taşınması, kütüğün standart çıktıya yazılması ve kayıtların araçlarla incelenmesi.
İçindekiler
Hata Yönetimi Stratejisi dersinde arızaların kaydedilmesinden birkaç kez söz edildi
ama nereye ve hangi biçimde yazılacağı belirlenmedi. Serbest metin bir satır —
hata: dosya acilamadi — arızayı arayan kişiye yetmez: hangi dosya, hangi istek,
hangi süreç, kaç kez?
Bu ders kütük biçimini belirler. Terim seçimi şudur: kütük (log), sürecin ürettiği kayıt dizisidir; günlükleme (logging) bu kayıtları üretme işidir.
Yapılandırılmış Kayıt
Kütük satırı iki okuyucuya birden hitap eder: arıza sırasında bakan insan ve kayıtları süzen, sayan, uyarı üreten program. İkisini birden karşılayan biçim, her satırın kendi başına ayrıştırılabilir bir kayıt olmasıdır.
Ölçüm dosyasının biçimi burada da işe yarar: satır başına bir JSON nesnesi. Kabuk Programlama kursundaki satır tabanlı araçlar bu biçimle doğrudan çalışır.
// kutuk.mjs const DUZEYLER = { ayrinti: 20, bilgi: 30, uyari: 40, hata: 50 }; export function kutukOlustur({ duzey = 'bilgi', hizmet, cikti = process.stdout, saat = () => new Date().toISOString(), // sinamada sabitlenebilsin sabitAlanlar = {}, } = {}) { const esik = DUZEYLER[duzey] ?? DUZEYLER.bilgi; function yaz(duzeyAdi, ileti, alanlar = {}) { if (DUZEYLER[duzeyAdi] < esik) return; cikti.write(JSON.stringify({ zaman: saat(), duzey: duzeyAdi, hizmet, ileti, ...sabitAlanlar, ...alanlar, }) + '\n'); } const kutuk = {}; for (const ad of Object.keys(DUZEYLER)) { kutuk[ad] = (ileti, alanlar) => yaz(ad, ileti, alanlar); } // Her isteğe kendi alanlarını taşıyan bir alt kütük kutuk.alt = (ekAlanlar) => kutukOlustur({ duzey, hizmet, cikti, saat, sabitAlanlar: { ...sabitAlanlar, ...ekAlanlar }, }); return kutuk; }
// kutuk-dene.mjs import { kutukOlustur } from './kutuk.mjs'; const kutuk = kutukOlustur({ duzey: process.env.OLCUM_KUTUK_DUZEYI ?? 'bilgi', hizmet: 'olcum-toplayici', saat: () => '2024-02-07T09:12:44.000Z', // ornek ciktisi degismesin diye sabit }); kutuk.ayrinti('yapilandirma okundu', { dosya: '.env' }); kutuk.bilgi('istek alindi', { yol: '/olcum', yontem: 'POST', kayit: 12 }); kutuk.uyari('esik asildi', { dugum: 'kenar-03', deger: 24.6 }); kutuk.hata('olcum dosyasi acilamadi', { kod: 'ENOENT', yol: 'yok.ndjson' }); const istekKutugu = kutuk.alt({ istekKimligi: 'r-0007' }); istekKutugu.bilgi('yanit yazildi', { durum: 201, sure_ms: 3 });
node kutuk-dene.mjs
{"zaman":"2024-02-07T09:12:44.000Z","duzey":"bilgi","hizmet":"olcum-toplayici","ileti":"istek alindi","yol":"/olcum","yontem":"POST","kayit":12}
{"zaman":"2024-02-07T09:12:44.000Z","duzey":"uyari","hizmet":"olcum-toplayici","ileti":"esik asildi","dugum":"kenar-03","deger":24.6}
{"zaman":"2024-02-07T09:12:44.000Z","duzey":"hata","hizmet":"olcum-toplayici","ileti":"olcum dosyasi acilamadi","kod":"ENOENT","yol":"yok.ndjson"}
{"zaman":"2024-02-07T09:12:44.000Z","duzey":"bilgi","hizmet":"olcum-toplayici","ileti":"yanit yazildi","istekKimligi":"r-0007","durum":201,"sure_ms":3}
Zaman damgası örnekte sabitlendi; gerçek çalıştırmada her satır kendi anını taşır. Saatin dışarıdan verilebilmesi bir kolaylık değil, sınanabilirlik gereğidir: Test dersinde bu kanca kullanılacak.
İleti metni sabittir, değişken bilgi ayrı alanlara konur. Bu ayrım aramayı olanaklı
kılar: "ileti":"istek alindi" araması bütün istekleri bulur, dosya adı iletiye
gömülseydi her satır farklı olurdu.
Düzey Eşiği
Dört düzey sayısal ağırlıklarla sıralanır ve eşiğin altındaki kayıtlar hiç yazılmaz. Eşik yapılandırmadan gelir, yani kütük hacmi kod değiştirmeden ayarlanır.
OLCUM_KUTUK_DUZEYI=hata node kutuk-dene.mjs
{"zaman":"2024-02-07T09:12:44.000Z","duzey":"hata","hizmet":"olcum-toplayici","ileti":"olcum dosyasi acilamadi","kod":"ENOENT","yol":"yok.ndjson"}
Düzeylerin anlamı sözleşmeyle sabitlenir; anlamsız bir sıra, eşiği işe yaramaz kılar:
- hata — işlem tamamlanamadı, birinin bakması gerekiyor.
- uyari — işlem tamamlandı ama beklenmeyen bir durum var.
- bilgi — hizmetin olağan akışındaki kilometre taşları.
- ayrinti — tanı için gereken ayrıntı; olağan çalışmada kapalıdır.
En sık yapılan yanlış, her şeyi hata düzeyine yazmaktır. Gerçek arıza, gürültünün
içinde görünmez hâle gelir ve düzey ayrımı anlamını yitirir.
İsteğe Bağlı Alanları Taşımak
Eşzamanlı çalışan bir hizmette kütük satırları karışık gelir; bir isteğe ait
satırları toplayabilmek için ortak bir alan gerekir. alt yöntemi, verilen alanları
kendisinden üretilen bütün satırlara ekleyen bir kütük döndürür.
Yukarıdaki çıktının son satırında istekKimligi alanı bu yolla eklendi. İstek
başında bir kimlik üretilip alt kütük oluşturulduğunda, o isteğin bütün kayıtları
aynı değeri taşır ve tek bir aramayla toplanır.
Kimliğin istemciden gelen bir başlıkta taşınması, isteği birden çok hizmet üzerinden izlemeyi olanaklı kılar. Gelen kimliğe doğrudan güvenilmez: biçimi doğrulanır ya da üretilen kendi kimliğiyle birlikte iki alan olarak yazılır.
Hatayı Kütüğe Yazmak
Kütüğe yazılan en önemli kayıt hata kaydıdır ve en sık yanlış yazılan da odur. Hata
nesnesi doğrudan bir alana konursa iki sorun çıkar. Birincisi, Error nesnesinin
name, message ve stack alanları sayılabilir değildir; dizileştirme onları
atlar ve geriye boş bir nesne kalır. İkincisi, nesnenin tamamını yaymak
(...hata) Yapılandırma Yönetimi dersindeki maskelemeyi geçersiz kılar — bir
bağlantı hatası, bağlantı dizgisini kendi alanında taşıyor olabilir.
Doğru yol, yazılacak alanları açıkça seçmektir:
// kutuk-hata.mjs export function hataAlanlari(hata, { yigitIzi = false } = {}) { const alan = { hataAdi: hata.name, hataIletisi: hata.message }; if (hata.kod !== undefined) alan.hataKodu = hata.kod; else if (hata.code !== undefined) alan.hataKodu = hata.code; const zincir = []; let neden = hata.cause; while (neden instanceof Error && zincir.length < 5) { zincir.push(neden.code ?? neden.kod ?? neden.name); neden = neden.cause; } if (zincir.length > 0) alan.nedenZinciri = zincir; if (yigitIzi && typeof hata.stack === 'string') { alan.yigitIzi = hata.stack.split('\n')[0]; } return alan; }
Hata Yönetimi Stratejisi dersinde kurulan neden zinciri burada karşılığını buluyor: sarmalanmış hatanın altındaki kodlar tek bir dizi alanında toplanıyor.
// kutuk-hata-dene.mjs import { readFile } from 'node:fs/promises'; import { kutukOlustur } from './kutuk.mjs'; import { hataAlanlari } from './kutuk-hata.mjs'; import { IsletimselHata } from './hata-sinifi.mjs'; const kutuk = kutukOlustur({ hizmet: 'olcum-toplayici', saat: () => '2024-02-07T09:12:44.000Z', }); try { await readFile('yok.ndjson', 'utf8'); } catch (dusuk) { const hata = new IsletimselHata('olcum dosyasi acilamadi', { kod: 'OLCUM_DOSYASI_YOK', durum: 503, cause: dusuk, }); kutuk.hata('istek karsilanamadi', { ...hataAlanlari(hata), yol: '/ozet' }); } kutuk.hata('beklenmeyen tip', { ...hataAlanlari(new TypeError('deger sonlu olmali'), { yigitIzi: true }) });
node kutuk-hata-dene.mjs
{"zaman":"2024-02-07T09:12:44.000Z","duzey":"hata","hizmet":"olcum-toplayici","ileti":"istek karsilanamadi","hataAdi":"IsletimselHata","hataIletisi":"olcum dosyasi acilamadi","hataKodu":"OLCUM_DOSYASI_YOK","nedenZinciri":["ENOENT"],"yol":"/ozet"}
{"zaman":"2024-02-07T09:12:44.000Z","duzey":"hata","hizmet":"olcum-toplayici","ileti":"beklenmeyen tip","hataAdi":"TypeError","hataIletisi":"deger sonlu olmali","yigitIzi":"TypeError: deger sonlu olmali"}
Birinci kayıt zincirin en altındaki ENOENT kodunu taşıyor; arızayı arayan kişi,
dosya sistemine kadar inen nedeni tek satırdan okuyor.
Yığıt izi varsayılan olarak yazılmıyor. Gerekçesi iki yönlüdür: iz çok satırlıdır ve satır başına bir kayıt sözleşmesini bozar; ayrıca dosya yollarını ve dizin yapısını kütüğe taşır. Örnekte iznin yalnızca ilk satırı alınıyor. Tamamının gerektiği durumlarda satır sonları kaçırılarak tek bir JSON dizgi değeri olarak yazılır — ham metin olarak değil.
İşletimsel hata ile programcı hatası kütükte de ayrılır. İşletimsel hata beklenen bir
koşuldur ve uyari düzeyi çoğu zaman yeter; programcı hatası her zaman hata
düzeyine ve tam iziyle yazılır, çünkü birinin bakması gerekir.
Kütük Nereye Yazılır
Kütük standart çıktıya yazılır. Süreç kendi dosyasını açmaz, kendi dosya döndürmesini yapmaz, kendi arşivini tutmaz.
Gerekçe, Kabuk Programlama kursunda kurulan ilkeyle aynıdır: çıktının nereye gideceğine komut değil, komutu çağıran karar verir. Süreci başlatan denetçi çıktıyı bir dosyaya, bir toplayıcıya veya bir boru hattına yönlendirir; süreç bunu bilmez. Böylece aynı yapı, hiçbir değişiklik olmadan farklı işletim düzenlerinde çalışır.
Tanı iletilerinin standart hataya yazılması kuralı burada bir istisnaya uğrar: yapılandırılmış kütük veriyle karışmaz, çünkü hizmetin “verisi” HTTP yanıtlarıdır, standart çıktı değildir. Komut satırı araçlarında ise durum tersine döner — Komut Satırı Uygulamaları dersindeki araç, kütüğünü standart hataya yazar, çünkü standart çıktı raporun kendisidir.
Yazma davranışının bir ayrıntısı vardır: standart çıktı bir dosyaya bağlıyken yazma
eşzamanlı, bir boruya bağlıyken eşzamansız yürütülebilir. İkinci durumda process.exit
ile sert çıkış, yazılmamış satırları düşürebilir — Süreç Nesnesi dersindeki uyarının
somut karşılığı budur.
Kayıtları İncelemek
Yapılandırılmış biçimin kazancı, özel bir araç gerektirmemesidir:
node kutuk-dene.mjs > kutuk.ndjson grep -o '"duzey":"[a-z]*"' kutuk.ndjson | sort | uniq -c | sort -rn
2 "duzey":"bilgi" 1 "duzey":"uyari" 1 "duzey":"hata"
Alan çıkarmak gerektiğinde kısa bir süzgeç yeter:
grep '"duzey":"hata"' kutuk.ndjson | node -e " const satirlar = require('node:fs').readFileSync(0, 'utf8').trim().split('\n'); for (const s of satirlar) { const k = JSON.parse(s); console.log(k.kod, '|', k.ileti); } "
ENOENT | olcum dosyasi acilamadi
İkinci örnekte readFileSync(0, 'utf8') çağrısı, 0 numaralı dosya tanıtıcısını —
yani standart girdiyi — okur. Kabuk Programlama kursunda tanıtılan tanıtıcı
numaraları burada doğrudan kullanılabilir.
Son bir kural: kütüğe kişisel veri ve gizli değer yazılmaz. Yapılandırma Yönetimi dersinde maskelenen yönetim anahtarı, bir hata nesnesinin alanı olarak kütüğe sızabilir. Kayda yazılacak alanların açıkça seçilmesi — hata nesnesinin bütününü serpiştirmek yerine — bu sızıntıyı önler.
Özet
- Kütük satırı hem insana hem programa hitap eder; satır başına bir JSON nesnesi biçimi iki okuyucuyu birden karşılar.
- İleti metni sabit tutulur, değişken bilgi ayrı alanlara konur; aramayı olanaklı kılan budur.
- Düzeyler sayısal ağırlıkla sıralanır ve eşik yapılandırmadan gelir; her şeyi hata düzeyine yazmak ayrımı anlamsızlaştırır.
- Alt kütük, verilen alanları kendisinden üretilen bütün satırlara ekler ve bir isteğin kayıtlarının toplanmasını sağlar.
- Hata nesnesi doğrudan yazılmaz: alanları açıkça seçilir, neden zinciri kodlara indirgenir ve yığıt izi ancak istendiğinde eklenir.
- Kütük standart çıktıya yazılır; dosya, döndürme ve arşiv süreci başlatan denetçinin işidir.
Sonraki Adım
Ölçüm toplayıcının parçaları çoğaldı: özetleyici, yapılandırma, hata sınıfları, kütük ve sunucu. Bunların doğru çalıştığı şimdiye kadar elle, çıktıya bakarak denetlendi. Sonraki ders bu denetimi otomatikleştirir: çalışma zamanının yerleşik test koşucusu ile birim ve tümleşim testlerini yazar, başarısız bir testin nasıl göründüğünü ve kapsam raporunun ne söylediğini gösterir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.