Bir sistemin içini görmek, ne olup bittiğini anlamak için loglar her zaman en temel aracım oldu. Fakat yıllar içinde, özellikle dağıtık sistemlerin ve mikroservis mimarilerinin yaygınlaşmasıyla, “unstructured logging” adı verilen düz metin tabanlı loglamanın ne kadar yetersiz kaldığını defalarca tecrübe ettim. Eski alışkanlıklarla print() ya da logger.info() ile serbest metin basmak, ilk başta kolay gibi görünse de, işler büyüdüğünde bir kabusa dönüşebiliyor.
Bu yazıda, unstructured logging’in bana ne gibi zorluklar çıkardığını, neden yetersiz kaldığını ve bu durumdan nasıl kurtulmaya çalıştığımı anlatacağım. Kendi yan ürünümün backend’inde veya bir müşteri projesinde karşılaştığım somut durumlarla konuyu açmak istiyorum.
Geleneksel Loglamanın Sınırlamaları
Geleneksel olarak, geliştiriciler ve sistem yöneticileri, uygulamaların ve sistemlerin durumunu anlamak için düz metin formatında loglar üretirlerdi. Syslog formatı buna iyi bir örnektir; belirli bir düzeni olsa da, içerik kısmı çoğunlukla serbest metindir. Ben de kariyerimin başlarında, özellikle küçük ölçekli projelerde veya tek bir monolitik uygulamada bu yöntemi sıkça kullandım. Nginx erişim logları ya da PostgreSQL hata logları gibi sistem bileşenleri de genellikle benzer düzende çalışır.
Ancak, bir üretim ERP’sinde çalışırken, yüzlerce farklı işlemin olduğu ve her birinin farklı mikroservisler arasında gezindiği bir ortamda, bu yaklaşım hızla duvara to çarptı. Bir sevk fişinin neden onaylanmadığını anlamak için ilgili logları bulmak, samanlıkta iğne aramaktan farksız hale gelmişti. Her geliştirici farklı formatta, farklı detay seviyesinde log attığı için, ortada tek bir standart kalmamıştı.
May 28 09:34:12 webserver appname[1234]: User 'alice' failed login from 192.168.1.100: Invalid credentials.
May 28 09:34:13 dbserver postgres[5678]: [2345] ERROR: duplicate key value violates unique constraint "users_pkey"
May 28 09:34:14 appserver appname[1235]: Order 789 processed successfully.
Yukarıdaki gibi log satırları, tek başına anlamlı görünse de, farklı servislerden geldiğinde ve binlerce satır arasında kaybolduğunda, bir olayın bütün resmini çıkarmak neredeyse imkansız hale geliyor. Eskiden, daha küçük sunucu altyapılarında logları grep ile taramak bir derece işe yarıyordu. Ancak bugün, kısa sürede gigabaytlarca log üreten sistemlerde bu yöntem kesinlikle sürdürülebilir değil.
Parsalama Kabusu ve Maliyetleri
Unstructured logların en büyük sorunlarından biri, anlamlı veri çıkarabilmek için sürekli regex tabanlı parsalama kuralları yazmak zorunda kalmamızdır. Bir üretim sisteminde, farklı ekiplerin yazdığı yüzlerce servis düşünün. Her servisin log formatı biraz farklıdır ve zamanla bu formatlar değişebilir. Bu durumda, her log formatı için ayrı bir parsalama kuralı yazmak ve bunları sürekli güncel tutmak tam bir kabusa dönüşür.
Benim bir projemde, Logstash üzerinde bu parsalama kurallarını yönetmek için sadece grok filtrelerine ayrılmış çok sayıda dosya vardı. Yeni bir log formatı geldiğinde, mevcut kuralların bozulmadığından emin olmak için uzun süren testler yapmak zorunda kalıyorduk. Bu durum, yeni özellik geliştirmeye ayrılan zamanın önemli bir kısmını tüketiyordu. Bir log formatı değişikliği sonrası yanlış yazılan bir grok kuralı, kritik bir servisin hata loglarının bir süre doğru analiz edilememesine yol açabilir. Bu da, potansiyel bir siber saldırı girişiminin veya kritik bir performans sorununun geç fark edilmesine neden olabilir.
%{TIMESTAMP_ISO8601:timestamp} %{HOSTNAME:hostname} %{WORD:app_name}\[%{INT:pid}\]: User '%{WORD:username}' failed login from %{IP:client_ip}: %{GREEDYDATA:message}
Yukarıdaki gibi bir grok kuralı, basit bir log satırını parse edebilir. Ancak, log formatı biraz değiştiğinde (örneğin appname yerine service_name gelirse), bu kural anında işlevsiz hale gelir. Bu tür sürekli güncellemeler, operasyonel maliyetleri ciddi şekilde artırır. Ayrıca, log toplama araçlarının (Fluentd, Logstash vb.) bu regexleri çalıştırmak için harcadığı CPU ve bellek, büyük log hacimlerinde ciddi bir maliyet kalemi oluşturur. Yoğun log akışı olan bir sistemde, parsalama hataları yüzünden Logstash kuyruğunun şiştiğini ve logların düşmeye başladığını gördüm. Bu tarz sorunlar, logların güvenilirliğini zedeler ve gerçek zamanlı izleme yeteneğimizi kısıtlar.
Korelasyon ve Bağlam Kaybı
Dağıtık sistemlerde, bir kullanıcı isteği birden fazla mikroservis üzerinden geçebilir. Örneğin, bir sipariş oluşturma işlemi, önce sipariş servisine, oradan stok servisine, sonra ödeme servisine ve en sonunda bildirim servisine uğrayabilir. Unstructured logging kullanıldığında, bu farklı servislerin her biri kendi loglarını kendi formatında üretir. Bir sorunu tespit etmek istediğinizde, “bu sipariş işlemi hangi servislerden geçti, her adımda ne oldu?” sorusunun cevabını bulmak neredeyse imkansızdır.
Bir müşteri projesinde, karmaşık bir tedarik zinciri entegrasyonu üzerinde çalışırken, bir siparişin neden “işleniyor” durumunda takılı kaldığını anlamak ciddi zaman aldı. Farklı servislerin loglarını manuel olarak karşılaştırmak, zaman damgalarına bakarak olayı yeniden yapılandırmaya çalışmak tam bir işkenceydi. Nihayetinde, trace_id ve span_id gibi değerlerin loglara eklenmesinin ne kadar kritik olduğunu anladım. Ancak unstructured loglarda bu ID’leri tutarlı bir şekilde parse edip ilişkilendirmek de ayrı bir zorluktu.
# Service A Log
2026-06-01T10:00:01Z serviceA[100]: Request received for order 123.
# Service B Log
2026-06-01T10:00:02Z serviceB[200]: Checking stock for item X. Order ID: 123.
# Service C Log
2026-06-01T10:00:03Z serviceC[300]: Payment processed for order 123.
Yukarıdaki gibi, sadece Order ID ile logları birleştirmeye çalışmak, her zaman yeterli olmaz. Özellikle eşzamansız (asynchronous) işlemlerin yoğun olduğu sistemlerde, aynı Order ID’ye sahip farklı işlemlerin logları birbirine karışabilir. trace_id gibi benzersiz bir istek kimliği olmadan, bir işlemin tüm yaşam döngüsünü anlamak ve bottleneck’leri tespit etmek çok zordur. Bu durum, özellikle “gerçek-zamanlı dashboard tasarımı” yapmaya çalıştığımda büyük bir engel teşkil etti. Hangi servisin ne kadar gecikmeye yol açtığını anlamak için sadece düz metin loglara güvenmek, bana göre tam bir kumar. Dağıtık Sistemlerde İzlenebilirlik Teknikleri başlıklı yazımda bu konuya daha detaylı değinmiştim.
Metrik Üretme ve Uyarı Mekanizmalarının Zayıflığı
Loglar sadece hata ayıklamak için değil, aynı zamanda sistemin genel sağlığını izlemek ve performans metrikleri çıkarmak için de çok önemlidir. Unstructured loglar, bu konuda da ciddi sınırlamalar getirir. Örneğin, belirli bir endpoint’in hata oranını veya ortalama yanıt süresini loglardan çıkarmak istediğinizde, yine karmaşık parsalama kurallarına ihtiyaç duyarsınız.
Kendi bir yan ürünümün backend’inde, API’lerin yanıt sürelerini ve hata kodlarını loglardan izlemek istedim. İlk başta, Nginx erişim loglarından status code ve request time alanlarını grep ile çekip basit bir script ile saymaya çalıştım. Ancak bu, sadece Nginx katmanındaki durumu gösteriyordu ve uygulama içindeki gerçek gecikmeleri veya hataları yansıtmıyordu. Uygulama loglarında ise bu bilgiler serbest metin olarak yazıldığı için, güvenilir metrikler elde etmek çok zordu.
Bir keresinde, PostgreSQL bağlantı havuzu tükenmesi nedeniyle yaşanan bir performans düşüşünü, loglarda “connection timeout” şeklinde belirsiz bir mesaj olarak gördüm. Bu mesajı otomatik olarak sayıp bir uyarı tetiklemek için özel bir fail2ban benzeri kural yazmak zorunda kaldım. Bu tür manuel müdahaleler, operasyonel yükü artırır ve sistemin proaktif izlenebilirliğini azaltır. Eğer loglar yapısal olsaydı, connection_pool_error_count gibi bir metrik anında elde edilebilir ve bir threshold aşıldığında otomatik olarak uyarı tetiklenebilirdi. PostgreSQL Performans Tuning Rehberi yazımda bu tip durumları nasıl ele aldığımı anlatmıştım.
Güvenlik ve Uyumluluk Zorlukları
Loglar, güvenlik açısından da kritik bir rol oynar. Bir saldırı anında veya bir veri sızıntısı durumunda, loglar olayın nasıl geliştiğini anlamak için ilk başvurulacak kaynaktır. Ancak unstructured loglar, bu konuda da ciddi zaaflara sahiptir. Özellikle hassas verilerin (PII - Personally Identifiable Information) yanlışlıkla loglara yazılması, büyük bir risk teşkil eder.
Bir üretim ERP’sinde, kullanıcı şifrelerinin veya kredi kartı numaralarının yanlışlıkla debug loglarına yazıldığını gördüğümde tüylerim diken diken olmuştu. Bu loglar düz metin olduğu için, bunları otomatik olarak tespit edip maskelemek veya silmek çok zordu. Manuel olarak logları taramak ve bu tür verileri temizlemek, hem zaman alıcı hem de hataya açık bir süreçti. GDPR veya KVKK gibi regülasyonlara uyumluluk açısından bu durum kabul edilemezdi.
# Hassas veri içeren bir unstructured log örneği
2026-06-01T10:05:10Z auth_service[500]: User login failed for '[email protected]' with password 'MySecretPassword123'.
Bu tür bir log satırı, düz metin olarak kaydedildiğinde, log erişimi olan herkesin hassas verilere ulaşmasına neden olabilir. Structured logging ile, log alanlarını daha granular bir şekilde kontrol edebilir ve hassas verileri içeren alanları otomatik olarak maskeleyebilir veya hiç loglamayabiliriz. Ayrıca, auditd gibi sistem araçlarıyla bile, serbest metin logların denetlenebilirliğini sağlamak çok zordur. Belirli bir kullanıcının veya işlemin sistem üzerindeki tüm etkileşimlerini izlemek, ancak yapısal ve bağlam açısından zengin loglarla mümkün olur. Bu, SELinux veya AppArmor profillerini yönetmek kadar önemlidir.
Yapısal Loglamaya Geçiş ve Faydaları
Tüm bu zorluklar beni yapısal loglamaya yöneltti. Structured logging, log mesajlarını önceden tanımlanmış bir yapıya (genellikle JSON veya key-value çiftleri) göre formatlama pratiğidir. Bu sayede, loglar makine tarafından kolayca okunabilir, parse edilebilir ve sorgulanabilir hale gelir.
Benim tercihim genellikle JSON formatında loglama oldu. Özellikle FastAPI ve Python projelerimde, logging modülünü JSON formatında çıktı verecek şekilde yapılandırmak, hem geliştirme hem de operasyonel süreçlerimi inanılmaz derecede hızlandırdı. Bir trace_id’yi log context’ine eklemek ve her log satırına otomatik olarak yansıtmak sadece birkaç satır kodla mümkün hale geldi.
import logging
import json
import uuid
class JsonFormatter(logging.Formatter):
def format(self, record):
log_entry = {
"timestamp": self.formatTime(record, self.datefmt),
"level": record.levelname,
"message": record.getMessage(),
"service": getattr(record, 'service', 'unknown'),
"trace_id": getattr(record, 'trace_id', str(uuid.uuid4())),
"extra_data": getattr(record, 'extra_data', {})
}
return json.dumps(log_entry)
# Logger kurulumu
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logger.addHandler(handler)
# Örnek kullanım
logger.info("User logged in successfully", extra={'service': 'auth_service', 'username': 'mustafa'})
Yukarıdaki gibi bir JSON formatter kullanarak, loglarım artık şu şekilde görünüyor:
{"timestamp": "2026-06-01T10:15:00,123", "level": "INFO", "message": "User logged in successfully", "service": "auth_service", "trace_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef", "extra_data": {"username": "mustafa"}}
Bu format, log toplama sistemlerinin (Elasticsearch, Loki, Splunk vb.) doğrudan parse edebileceği, indeksleyebileceği ve sorgulayabileceği bir yapıdır. Artık bir hata durumunda, belirli bir trace_id’ye sahip tüm logları saniyeler içinde filtreleyebilir, belirli bir service’e ait INFO veya ERROR seviyesindeki logları sayabilirim. Bu, observability (izlenebilirlik) açısından bana çok büyük bir güç katıyor.
Elbette, structured logging’in de kendi trade-off’ları var. Log boyutları biraz artabilir, çünkü her anahtar ve değer tekrar tekrar yazılır. Ancak, bu küçük boyut artışı, elde edilen analiz ve hata ayıklama kolaylığının yanında devede kulak kalır. Ayrıca, log toplama sistemleri bu tür yapısal veriyi çok daha verimli bir şekilde sıkıştırabilir ve indeksleyebilir. Bu sayede, toplam maliyetler unstructured loglara göre daha düşük bile olabilir. Özellikle PostgreSQL’de GIN indeksleri veya Redis’te JSON veri tipleri ile çalışırken, structured logların sorgulama performansında ne kadar fark yarattığını bizzat gözlemledim.
Sonuç: Yapısal Loglama Bir Lüks Değil, Bir Zorunluluk
Unstructured logging, küçük projelerde veya ilk başta kolay gibi görünse de, sistemleriniz büyüdükçe, dağıtık hale geldikçe ve operasyonel karmaşıklık arttıkça sizi hızla bir çıkmaza sokar. Saha tecrübemde, loglama pratiklerini ciddiye almayan sistemlerin nasıl felaketlerle sonuçlandığını defalarca gördüm.
Yapısal loglamaya geçiş yapmak, sadece bir geliştirme tercihi değil, aynı zamanda operasyonel verimlilik, güvenlik ve uyumluluk açısından da bir zorunluluktur. Bu, sistemlerimizin iç işleyişini daha iyi anlamamızı, sorunları daha hızlı tespit etmemizi ve proaktif müdahalelerde bulunmamızı sağlar. Bir sonraki projenizde veya mevcut sistemlerinizi iyileştirirken, loglama stratejinizi yeniden gözden geçirmenizi şiddetle tavsiye ederim. Başlangıçta biraz çaba gerektirse de, uzun vadede size katacağı değer paha biçilemez olacaktır.