Mert Özen Her Satırda Daha İleri
Backend .NET Core Web API — #14

Loglama: ILogger, Serilog ve yapılandırılmış (structured) loglama

Mert Özen 8 Ağu 2026 12 dk 80 görüntülenme
Loglama: ILogger, Serilog ve yapılandırılmış (structured) loglama

Loglamayı işliyoruz: ILogger'ın temelleri, log seviyeleri, düz metin ile yapılandırılmış loglama arasındaki fark ve Serilog'un neden bu işi ciddi projelerde çok daha güçlü hale getirdiği.

Önceki yazıda hataları merkezi bir yerde yakaladık ve içeride _logger.LogError(...) diye bir satır kullandık ama üstünde durmadan geçtik. Bugün o satırın arkasındaki dünyayı açıyoruz. Çünkü loglama, ilk bakışta önemsiz görünen ama bir uygulama canlıya çıktığında hayatını kurtaran şeydir. Bir sorun olduğunda "acaba ne oldu?" sorusunun tek dürüst cevabı, çoğu zaman logların içinde saklıdır.

Log Neden Bu Kadar Önemli?

Kendi bilgisayarında geliştirme yaparken bir şey ters gittiğinde, hata mesajını ekranında görürsün, kodun içinde adım adım ilerlersin. Ama uygulaman bir sunucuda, senden uzakta çalışırken bunların hiçbirini yapamazsın. O anda elindeki tek pencere loglardır. Kullanıcı "sistem çöktü" dediğinde, ne olduğunu ancak logların anlatabilir.

Logu, bir uçağın kara kutusu gibi düşün. Her şey yolundayken kimse ona bakmaz. Ama bir sorun olduğunda, geriye dönüp neyin nasıl olduğunu anlamanın tek yolu odur. İyi bir loglama altyapısı, "bir şey oldu ama ne olduğunu bilmiyorum" ile "tam olarak şu saatte, şu kullanıcıda, şu işlemde patladı" arasındaki farktır.

ILogger: .NET'in Hazır Loglama Aracı

Güzel haber: .NET, loglama için hazır bir altyapıyla gelir. ILogger arayüzünü herhangi bir sınıfın constructor'ında istersen, dependency injection onu sana verir; ekstra bir kurulum gerekmez. Bir servis içinde şöyle kullanırsın:

public class UserService : IUserService
{
    private readonly ILogger _logger;

    public UserService(ILogger logger)
    {
        _logger = logger;
    }

    public async Task GetByIdAsync(int id)
    {
        _logger.LogInformation("Kullanıcı aranıyor. Id: {UserId}", id);
        // ... veritabanı işlemi
        return null;
    }
}

Dikkat edilmesi gereken bir ayrıntı var: ILogger<UserService> şeklinde, sınıf adını tip parametresi olarak veriyoruz. Bu, log kaydına "bu mesaj UserService'ten geldi" bilgisini otomatik ekler; böylece log'a baktığında hangi mesajın nereden geldiğini anında görürsün.

Log Seviyeleri: Her Mesaj Aynı Değil

Her log kaydı aynı öneme sahip değildir. Bir "kullanıcı giriş yaptı" mesajı ile bir "veritabanı çöktü" mesajı aynı raftaymış gibi davranılmamalı. Bu yüzden loglar seviyelere ayrılır. En sık kullanacakların şunlar:

LogTrace ve LogDebug — En düşük seviyeler; geliştirme sırasında ayrıntıyı takip etmek için. Canlıda genelde kapatılır.

LogInformation — Normal akışı kaydeder. "Şu işlem yapıldı, şu istek geldi" gibi. Uygulamanın nefes alışını görürsün.

LogWarning — Bir sorun değil ama dikkat çekici bir durum. "Beklenenden yavaş cevap" ya da "kullanılmayan bir yol denendi" gibi.

LogError — Bir işlem başarısız oldu, bir hata oluştu. Önceki yazıda merkezi yakalayıcıda kullandığımız buydu.

LogCritical — En üst seviye. Uygulamanın tamamını tehdit eden felaketler için; mesela veritabanına hiç ulaşılamıyorsa.

Seviyeler işine iki türlü yarar. Birincisi, canlı ortamda "sadece Warning ve üstünü kaydet" diyerek log gürültüsünü azaltabilirsin. İkincisi, bir sorunu ararken doğrudan Error seviyesine filtreleyip önemli olanı gürültüden ayırabilirsin.

Düz Metin mi, Yapılandırılmış Log mu?

Şimdi işin en kritik kavramına geliyoruz. Yukarıdaki örnekte log mesajını nasıl yazdığımıza bir daha bak:

_logger.LogInformation("Kullanıcı aranıyor. Id: {UserId}", id);

Burada id değerini metnin içine string olarak yapıştırmadık; onun yerine {UserId} adında bir yer tutucu kullanıp değeri ayrı verdik. Bu küçük fark, düz metin loglama ile yapılandırılmış (structured) loglama arasındaki bütün farkı yaratır.

Düz metinde log, sadece bir cümledir: "Kullanıcı aranıyor. Id: 42". İnsan okur, anlar, biter. Ama yapılandırılmış loglamada bu kayıt, içinde UserId = 42 gibi ayrı bir alan taşır. Yani log artık sadece bir cümle değil, aranabilir ve filtrelenebilir veridir. Bir gün "42 numaralı kullanıcıyla ilgili tüm logları getir" demek istediğinde, yapılandırılmış logda bu tek bir sorgu; düz metinde ise binlerce satır arasında elle arama demektir.

Serilog: İşi Ciddiye Alınca

.NET'in yerleşik ILogger'ı öğrenmek ve küçük projeler için gayet yeterli. Ama iş büyüdüğünde, logları düzgün biçimlendirmek, dosyaya yazmak, hatta farklı yerlere (dosya, veritabanı, harici bir sistem) aynı anda göndermek istersin. İşte burada Serilog devreye girer; .NET dünyasının en sevilen loglama kütüphanesidir ve yapılandırılmış loglamayı çok güçlü biçimde destekler.

Serilog'un güzelliği, ILogger arayüzünü değiştirmemesi. Yani kodunun içinde yine tanıdık _logger.LogInformation(...) çağrılarını kullanmaya devam edersin; sadece arka planda logların nereye ve nasıl yazılacağını Serilog yönetir. Paketleri ekledikten sonra Program.cs'te kurulumu aşağı yukarı şöyle görünür:

builder.Host.UseSerilog((context, config) =>
{
    config.WriteTo.Console();
    config.WriteTo.File("logs/app.txt", rollingInterval: RollingInterval.Day);
});

Bu kısacık ayar iki hedef tanımlıyor: loglar hem konsola yazılsın, hem de logs klasöründe her gün ayrı bir dosyaya kaydedilsin. rollingInterval sayesinde her günün logu ayrı bir dosyada tutulur; böylece dosyalar devasa boyutlara ulaşmaz ve belirli bir güne bakmak kolaylaşır. Serilog'un onlarca farklı hedefi (sink dediği çıkışları) var; ihtiyacına göre logları farklı yerlere yönlendirebilirsin.

İyi Log Yazmanın Birkaç Kuralı

Araç önemli ama nasıl yazdığın daha önemli. Birkaç pratik ilke: Değerleri her zaman yer tutucuyla ver, string birleştirmeyle değil; yapılandırılmış loglamanın gücü buradan gelir. Hassas bilgileri (şifre, token, kişisel veri) asla loglama; log dosyaları da sızabilir. Log mesajını, sonradan okuyacak kişinin (yani gelecekteki sen) anlayacağı netlikte yaz. Ve gürültüyü dengede tut: her şeyi loglarsan önemli mesaj kaybolur, hiçbir şeyi loglamazsan sorun anında elin boş kalır.

Küçük Bir Deneme

Bir servisinin içine farklı seviyelerde birkaç log ekle: normal bir işlem için LogInformation, dikkat çekici bir durum için LogWarning, bir hata için LogError. Değerleri mutlaka {Placeholder} biçiminde ver. Uygulamayı çalıştırıp konsoldaki log çıktısına bak; her mesajın hangi seviyede ve hangi sınıftan geldiğini gör. İstersen bir adım ileri git, Serilog'u kurup logların bir de dosyaya yazıldığını gözlemle. Bu deney, loglamanın soyut bir kavram olmaktan çıkıp elindeki somut bir araca dönüşmesini sağlar.

Bir sonraki yazıda configuration ve Options pattern konusuna geçeceğiz: bu yazıda koda gömdüğümüz log dosyası yolu gibi ayarları, appsettings dosyalarında nasıl düzgün yöneteceğimizi konuşacağız. Bağlantı dizeleri, dosya yolları ve ortama göre değişen değerleri koddan ayırmanın zamanı geliyor.