- Modern dağıtık sistemlerde geleneksel loglama yaklaşımı, gerçeği aktaramayan yapısal sınırlara sahiptir
- Loglar hâlâ 2005 tarzı tek sunucu ortamı varsayımıyla tasarlanır; bu yüzden birden çok servis, veritabanı ve cache üzerinden geçen isteğin bağlamını kaybeder
- Basit metin araması yapıyı, ilişkileri ve korelasyonu anlayamaz; bu da sorunun kök nedenini bulmayı zorlaştırır
- Çözüm, her istek için tüm bağlamı içeren tek bir
Wide Event (veya Canonical Log Line) bırakmaktır
- Böylece loglar basit metin olmaktan çıkıp analiz edilebilir bir veri varlığına dönüşür
Loglamanın temel sorunu
- Mevcut loglar monolitik sunucu çağı varsayımıyla oluşturulduğundan, modern dağıtık servis mimarisini yansıtamaz
- Bir istek birden fazla servis, DB, cache ve kuyruktan geçse de loglar hâlâ tek sunucu ölçütüne göre tutulur
- Örnek loglarda istek başına 13 satır üretiliyor; bu da aynı anda 10.000 kullanıcı varken saniyede 130.000 satır demek, ama bunların çoğu anlamsız bilgi
- Sorun çıktığında gereken şey **bağlam (
context)**tır, ancak mevcut loglarda bu eksiktir
Metin aramasının sınırları
- Kullanıcı “ödeme çalışmıyor” diye bildirdiğinde, loglarda e-posta veya user_id ile arama yapsanız bile tutarlı bir yapı olmadığı için anlamlı sonuç almak zordur
- Aynı kullanıcı kimliği
user-123, user_id=user-123, {"userId":"user-123"} gibi onlarca farklı biçimde kaydedilir
- Servisler arasında log formatı değiştiğinden ilişkili olayları takip etmek imkânsızdır
- Temel sorun, logların yazma (
write) odaklı tasarlanmış olması ve sorgu (query) için optimize edilmemesidir
Temel kavramların tanımı
- Structured Logging: metin yerine anahtar-değer (JSON) biçiminde kayıt tutma yaklaşımı
- Cardinality: bir alanın benzersiz değer sayısı; örneğin
user_id çok yüksektir
- Dimensionality: bir log olayındaki alan sayısı; sayı arttıkça analiz imkânı da artar
- Wide Event / Canonical Log Line: istek başına bağlamı zengin tek bir log olayı
- Çoğu loglama sistemi yüksek cardinality'li veriyi maliyet nedeniyle sınırlar, oysa hata ayıklamada en faydalı olan tam da budur
OpenTelemetry'nin sınırları
- OpenTelemetry (OTel), yalnızca protokol ve SDK setidir; veri toplama ve iletme için standart sağlar
- Ancak OTel şunları yapmaz
- Neyin loglanacağına karar vermez
- İş bağlamını (ör. abonelik seviyesi, sepet tutarı vb.) otomatik olarak eklemez
- Geliştiricinin loglama düşünce biçimini değiştirmez
- Aynı kütüphane kullanılsa bile, bağlamı bilinçli biçimde ekleyen enstrümantasyon ile basit enstrümantasyon arasında hata ayıklama deneyimi dramatik biçimde farklıdır
- OTel sadece bir taşıma hattıdır (
plumbing); içinden neyin akacağını geliştirici belirlemelidir
Wide Event / Canonical Log Line yaklaşımı
- Geleneksel “kod ne yapıyor?” merkezli loglamadan çıkıp, “isteğe ne oldu?”yu kaydetmek gerekir
- Her istek için servis düzeyinde tek ve kapsamlı bir olay üretilir
- İstek, kullanıcı, ödeme, hata, ortam gibi 50'den fazla alan içerebilir
- Örnek JSON'da
user_id, subscription_tier, service_version, error_code gibi hata ayıklama için gereken tüm bağlam yer alır
- Bu sayede “premium kullanıcıların ödeme başarısızlığının nedeni” gibi sorular tek aramayla anında analiz edilebilir
Wide Event için sorgu kullanımı
Wide Event, basit metin aramasıyla değil yapılandırılmış veri sorguları ile ele alınır
- Yüksek cardinality ve yüksek boyutlu veri sayesinde gerçek zamanlı analiz seviyesinde hata ayıklama mümkün olur
- Örneğin “son 1 saatte premium kullanıcıların ödeme başarısızlık oranını hata koduna göre grupla” gibi bir sorgu anında çalıştırılabilir
Uygulama deseni
- Olay, isteğin tüm yaşam döngüsü boyunca oluşturulur ve yalnızca en sonda bir kez yazdırılır
- Middleware içinde
request_id, timestamp, method, path gibi temel alanlar başlatılır
- Handler içinde kullanıcı, sepet, ödeme ve hata bilgileri aşamalı olarak eklenir
- Sonunda
logger.info(event) ile tek bir JSON olay kaydedilir
Sampling ile maliyet kontrolü
- İstek başına 50'den fazla alan kaydedildiğinde maliyet hızla arttığından sampling gerekir
- Basit rastgele sampling, hataları kaçırma riski taşır
- Önerilen strateji: Tail Sampling
- Hatalar (500 vb.) her zaman saklanır
- Yavaş istekler (p99 ve üzeri) her zaman saklanır
- VIP kullanıcılar ve belirli flag'lere sahip oturumlar her zaman saklanır
- Geri kalanların yalnızca %1~5'i rastgele örneklenir
- Böylece maliyet azaltımı ile kritik olayların korunması aynı anda sağlanır
Yaygın yanlış anlamalar
- Structured Logging ≠ Wide Event: Sadece JSON formatı yeterli değildir; asıl önemli olan bağlamdır
- OpenTelemetry kullanmak ≠ tam gözlemlenebilirlik sağlamak: Sadece toplamayı standartlaştırır; neyin kaydedileceği geliştiriciye kalır
- Tracing ile aynı şey değildir: Tracing servisler arası akışı gösterir,
Wide Event ise servis içi bağlam sağlar
- “Loglar hata ayıklama için, metrikler dashboard için” ayrımı gereksizdir —
Wide Event her iki kullanım amacını da karşılar
- “Yüksek cardinality'li veri pahalıdır” düşüncesi artık eskidi; ClickHouse, BigQuery gibi modern veritabanları bunu verimli biçimde işleyebilir
Wide Event benimsemenin etkisi
- Hata ayıklama, kazı çalışmasından (
archaeology) analitiğe (analytics) dönüşür
- “Kullanıcının ödeme başarısızlığını” bulmak için 50 servisin loglarını
grep ile tarama yaklaşımından,
“premium kullanıcıların ödeme başarısızlık oranını hata koduna göre sorgulama” gibi tek sorgu tabanlı analize geçilir
- Sonuç olarak loglar, yalan söyleyen bir araçtan gerçeği anlatan bir veri varlığına dönüşür
1 yorum
Hacker News görüşleri
Yazının okunması zordu ve AI yardımıyla yazılmış gibi bir havası vardı. Yine de mesajı değerliydi; biraz daha özlü olsaydı daha iyi olurdu.
Son zamanlarda düşündüklerim şunlar.
Bu konuda Charity Majors’tan bahsetmemek olmaz. Kendisi 10 yıldan uzun süredir “wide events” ve “observability” kavramlarını yaygınlaştırıyor ve Honeycomb.io’yu bu felsefe üzerine kurdu.
Bugün bu yaklaşım farklı araçlarla uygulanabiliyor. Structured logs ya da traces ile wide event’leri yakalayıp zaman serisi, histogram gibi güçlü görselleştirmeleri olan araçlar kullanmak önemli
Yazının savlarının bazılarına katılıyorum ama yalnızca tek bir wide event bırakma yaklaşımının tuzakları var. İstek ortasında exception ya da timeout olursa geriye hiçbir şey kalmayabilir.
Dilin varsayılan logging framework’ünü veya bağımlılık loglarını da kaçırabilirsiniz.
Bu yüzden bunu mevcut logların üstüne gelen ek bir katman olarak kullanmak daha iyi. Request/session düzeyinde ID ekleyip ClickHouse gibi bir yerde toplulaştırabilirsiniz
log.error(data)ilewide_event.attach(error, data)arasında özde büyük fark yokSunum ve interaktif örnekler harikaydı. Ama sonuçta anlatılan şey “loglara yapılandırılmış etiketler ekleyin” noktasına geliyor.
Wide log’ların getirdiği karmaşıklık ve okunabilirlik kaybına kıyasla sağladığı faydanın çok büyük olmadığını düşünüyorum.
Sadece
grep "uid=user-123" application.logyeterliyken, kullanıcının teslimat yöntemini de eklemeye gerçekten gerek var mı emin değilim.(Bu arada Android’de Brave tarayıcısında checkbox’lar çalışmıyordu)
grep '"uid": "user-123"'ile arama yapılabilir.--contextseçeneğiyle çevredeki satırlar da görülebilirYarı iletken üretim ortamında binlerce message bus katılımcısı olan sistemlerle çalıştım. Saatte 300~400MB log çıkıyordu ama grep ve CLI araçlarıyla bile rahatça yönetiliyordu.
Loglar yalnızca olayların zaman serisi gibiydi; ayrıntılı analiz Oracle sorgularıyla yapılıyordu. Log, olayların nedensel ilişkisini anlamaya yarayan bir araçtır
Log “ne zaman, ne oldu”yu söyler; “neden” ise kod, veri ve olayların birleşiminde bulunur
Benim için ELK stack gibi arayüzler sezgisel keşif açısından pek rahat değil. Logları içgüdüsel olarak takip ederek okumak önemlidir
Yazının sonunda geçen “tüm hata, exception ve yavaş istekleri loglayın” tavsiyesi tehlikeli bir fikir.
Örneğin bir bağımlılık yavaşlarsa log hacmi 100 kat artabilir.
Arıza anında servisin daha az iş yapması gerekir ki toparlanabilsin; log patlaması ise tam tersine zincirleme arızalara yol açabilir
Log miktarı arttıkça örnekleme oranı otomatik ayarlanıyor ve sistem aşırı yüklenmiyor
Modern yazılımda tek bir log ile “ne oldu” sorusunu eksiksiz açıklamak zor.
Bu yüzden dikey korelasyon (Vertical correlation) ve yatay korelasyon (Horizontal correlation) gerekir.
Stack içindeki üst ve alt katmanlar aynı correlation değerini paylaşmalı; sistemler arası iletişimde de eşler arası korelasyon gerekir.
Bu tür değerleri API’ye ya da protokole eklemek zor olabilir ama transaction ID önceden tasarlanırsa uçtan uca izleme mümkün olur
Tek bir yazı için ayrı bir domain kaydetmek bana pek sürdürülebilir gelmiyor.
Her yıl yenileme ücreti ödemeniz gerekeceği için kişisel blog veya subdomain kullanmak daha iyi olabilir.
Örneğin logging-sucks.boristane.com gibi bir yapı daha uygun
“Loglar monolitik çağın kalıntısıdır” iddiasına karşı, ben yerel logların hâlâ geçerli olduğunu düşünüyorum.
Asıl rolleri yerel process’in konuşmalarını kaydetmektir; başka sunucularda neler olduğunu görmek için ise transaction tracing gerekir.
Uygun noktadaki loglara bakmak çoğu zaman kök nedene ulaşmak için yeterlidir
Zengin bağlam içeren loglar, analiz motorlarıyla birleşince ürün geliştirmede de kullanılabilir
“Kodun ne yaptığını değil, isteğe ne olduğunu loglayın” sözüne katılıyorum ama yazar biraz deneyimsiz göründü.
Ben buna “bug parts logging” diyorum ve işleme yolu, tekrar sayısı, süre gibi öncü sinyallerin de dahil edilmesi gerektiğini düşünüyorum.
Logging, metrik ya da audit ile aynı şey değildir. Logging başarısız olsa da işleme devam etmelidir; ama audit başarısızlığı kritik olabilir.
SCADA sistemlerindeki “historian” kavramında olduğu gibi, gözlemlenebilirler (observables) ile değerlendirmeler (evaluatives) ayrılmalıdır.
Örneğin yakıt sensörünün ayrıntılı olayları tanı için yararlıdır ama “hedefe ulaşılabilir mi” sorusu için gerekli değildir.
Sonuçta önemli olan, neyi gözlemleyeceğinizi ve neyi değerlendireceğinizi netleştirmektir
Depolama, dönüştürme ve sorgulama yöntemleri farklı olsa da, tüketim noktaları ve mekanizmaları aynı şekilde tasarlanabilir.
Bu sayede sistem tasarımı sadeleşir ve uzun süre saklanan loglar daha sonra yeniden işlenebilir de olabilir