Üretim ortamında hata ayıklama: bakmadan tahmin etmemek
Üretimde hata bildirimi geldiğinde kodu açmadan önce yapılması gerekenler: korelasyon kimliği, yapılandırılmış log, istisna zinciri, süre ölçümü ve dağıtım kaydı ile arama alanını daraltmak.
Üretimde bir hata bildirimi geldiğinde ilk refleks genelde aynı: kodu açıp "burası olabilir" demek. Bu refleks zaman zaman tutuyor, ve tuttuğu her seferde bir sonraki sefer için yanlış bir alışkanlığı pekiştiriyor. Çünkü tahmin doğru çıktığında ölçüm yapmadan kazandığınızı düşünüyorsunuz; yanlış çıktığında ise saatler gidiyor ve genellikle işe yaramayan bir savunma katmanı ekleniyor.
Bu yazının tek bir iddiası var: üretim hatasında ilk iş kod okumak değil, hatanın hangi isteğe ait olduğunu bulmak.
Önce olayı tekilleştirin
"Ödeme sayfası hata veriyor" bir hata raporu değil, bir şikâyet. Üzerinde çalışılabilir hâle gelmesi için tek bir isteğe indirgenmesi gerekiyor: hangi kullanıcı, hangi saatte, hangi uç noktaya, hangi yanıtı aldı.
Bu bilgiyi sonradan aramak yerine baştan üretmek daha ucuz. Her isteğe bir korelasyon kimliği verip hem yanıt başlığında hem her log satırında taşımak, çoğu ekipte tek seferlik bir işten ibaret:
import { AsyncLocalStorage } from "node:async_hooks";
import { randomUUID } from "node:crypto";
const ctx = new AsyncLocalStorage();
export function withRequestId(req, res, next) {
const id = req.headers["x-request-id"] ?? randomUUID();
res.setHeader("x-request-id", id);
ctx.run({ id }, next);
}
export function log(seviye, mesaj, alanlar = {}) {
process.stdout.write(
JSON.stringify({
ts: new Date().toISOString(),
seviye,
mesaj,
requestId: ctx.getStore()?.id ?? null,
...alanlar,
}) + "\n"
);
}
Kullanıcı ekrandaki kimliği size ilettiği anda, tahmin aşaması tamamen ortadan kalkıyor. Log toplayıcıda tek bir filtre, isteğin tüm yaşam döngüsünü veriyor.
Log satırı cümle değil, kayıt olsun
Serbest metin loglar okunabilir görünüyor ama aranabilir değil. console.log("kullanıcı bulunamadı: " + id) satırı, üç ay sonra "bu hata kaç kullanıcıda oluyor" sorusuna cevap veremiyor. Aynı bilgi yapılandırılmış yazıldığında hem filtreleniyor hem sayılıyor:
log("warn", "kullanici_bulunamadi", { userId, kaynak: "odeme_akisi" });
Kural basit: değişken değeri mesaj metnine gömülmesin, alan olarak yazılsın. Mesaj sabit kalsın ki gruplanabilsin.
Hata yakalamayı bilgi kaybetmek için kullanmayın
Üretimde en çok karşılaşılan sessiz hata kaynağı, geniş kapsamlı yakalama blokları. Aşağıdaki iki blok aynı işi yapıyor görünüyor, ama ikincisi hata ayıklamayı imkânsız hâle getiriyor:
# hatayı yutuyor: neyin başarısız olduğu kayboluyor
try:
sonuc = servis.calistir(veri)
except Exception:
sonuc = None
# bağlamı koruyor
try:
sonuc = servis.calistir(veri)
except ServisZamanAsimi as e:
log.warning("servis_zaman_asimi", extra={"kayit_id": veri.id})
raise IsleyisHatasi("servis yanıt vermedi") from e
from e zincirinin korunması, üretimde köke inebilmenin en ucuz yolu. Yakalanan istisna türünün daraltılması ise hangi hataların beklenen, hangilerinin beklenmedik olduğunu ayırıyor.
Zamanı tahmin etmeyin, ölçün
"Yavaşlık" şikâyetlerinde en sık yapılan hata, darboğazın nerede olduğunu okuyarak bulmaya çalışmak. Ölçmek çoğu durumda birkaç satır iş:
import time, contextlib
@contextlib.contextmanager
def sure(ad, **alanlar):
t0 = time.perf_counter()
try:
yield
finally:
log.info("adim_suresi", extra={"adim": ad, "ms": round((time.perf_counter()-t0)*1000), **alanlar})
with sure("urun_sorgusu", magaza_id=magaza.id):
urunler = repo.listele(magaza.id)
Bu ölçümü koyduktan sonra çoğu ekipte iki şey oluyor: darboğaz beklenen yerde çıkmıyor, ve toplam sürenin büyük kısmı tek bir çağrıda toplanıyor. Optimizasyon kararı da ancak bu tablo çıktıktan sonra anlamlı hâle geliyor.
Ortamı taklit edin, kopyalamaya çalışmayın
Yerelde tekrar üretilemeyen hataların çoğu koddan değil, ortam farkından geliyor: farklı saat dilimi, farklı karakter kümesi, farklı bağlantı havuzu boyutu, üretimde dolu olan bir önbellek. Tüm üretim verisini yerele çekmek hem gereksiz hem riskli; bunun yerine farkı doğrudan sorgulamak daha hızlı sonuç veriyor.
Uygulamanın kendi durumunu raporlayan küçük bir uç nokta bu işi görüyor: çalışan sürüm etiketi, veritabanı sürümü, geçerli saat dilimi, açık bağlantı sayısı, kritik ayarların değeri. Bu uç nokta üretime çıkarıldığında, "bende çalışıyor" tartışmaları genellikle ilk beş dakikada bitiyor.
Hata ayıklamayı üretimde açık bırakın
Üretime ayrıntılı log açmak maliyetli olduğu için genelde tamamen kapatılıyor. Ara yol var: log seviyesini çalışma anında değiştirilebilir bir ayara bağlamak ve ayrıntılı kaydı yalnız belirli bir korelasyon kimliği ya da belirli bir kullanıcı için açmak. Böylece hacim artmıyor, ama sorunu yaşayan istek tam kayıt bırakıyor.
Değişikliği değil, zamanı sorun
Bir hata "dün yoktu, bugün var" diye tarif ediliyorsa, ilk bakılacak yer kod değil zaman çizelgesi. Son dağıtım ne zaman yapıldı, o dağıtımda hangi ortam değişkeni değişti, bağımlılıklardan hangisi otomatik güncellendi, veritabanında hangi göç koştu.
Bu dört başlığı tek bir yerde toplayan bir dağıtım kaydı tutmak, çoğu ekipte hata ayıklama süresini doğrudan kısaltıyor. Kayıt karmaşık olmak zorunda değil; sürüm etiketi, zaman damgası, koşan göçlerin listesi ve değişen ayarların adları yeterli. Hata bildirimindeki saat ile bu kayıttaki saatler yan yana konduğunda, aday değişiklik sayısı genellikle bire düşüyor.
Aynı mantık tersten de işliyor: hatanın belirli bir saatten sonra başladığı ama o saatte hiçbir dağıtım yapılmadığı görülüyorsa, şüphe kodun dışına — dış servis, sertifika süresi, disk doluluğu, zamanlanmış bir işin çakışması — kayıyor. Bu ayrımı yapmadan kod okumak, aramayı yanlış dosyada başlatmak anlamına geliyor.
Uygulama sırası
Bir hata bildirimi geldiğinde işleyen sıra şu:
- Korelasyon kimliğini alın; yoksa isteği zaman ve uç noktayla daraltın.
- O isteğin loglarını baştan sona okuyun; kod dosyasını henüz açmayın.
- Hatanın hangi adımda çıktığını ve hangi girdiyle çıktığını yazın.
- O girdiyle bir test yazın; test kırmızı olmadan düzeltmeye başlamayın.
- Düzeltmeyi yaptıktan sonra, aynı hatayı bir dahaki sefere daha hızlı bulacak ölçümü de bırakın.
Beşinci madde atlandığında aynı iş bir sonraki hatada baştan yapılıyor. Hata ayıklama süresinin kalıcı olarak kısalması, tek tek düzeltmelerden değil, her düzeltmeden geriye kalan ölçümden geliyor.
- hata ayıklama
- loglama
- gözlemlenebilirlik
- üretim