Mikroservis Mimarisi & Observability 7 dk okuma 3 okunma

Microservices'te Log Aggregation ve Correlation ID

Mikroservislerde log'lar onlarca servise dağılır; merkezi toplama (Log Aggregation) ve Correlation ID olmadan sorun tespiti imkansızdır. Bu yazıda, ELK/EFK stack'i, yapılandırılmış loglama (Serilog), Correlation ID'nin HTTP header'ları ve mesaj kuyrukları üzerinden yayılımı, OpenTelemetry TraceId entegrasyonu ve .NET'te uygulama stratejileri ele alınır.

Microservices'te Log Aggregation ve Correlation ID

Microservices'te Log Aggregation ve Correlation ID

Mikroservis mimarisinde, tek bir kullanıcı isteği onlarca servisten geçer ve her servis kendi log'unu kendi container'ına yazar. Bir sorun oluştuğunda, "hangi serviste ne oldu?" sorusunu yanıtlamak için onlarca farklı yere bakmak gerekir. İşte Log Aggregation (Merkezi Log Toplama) ve Correlation ID (Korelasyon Kimliği), bu kaosu düzene çeviren iki temel yapı taşıdır.


1. Neden Merkezi Log Toplama Gerekli?

Geleneksel (Monolitik) Yaklaşım:

text

Uygulama → app.log dosyası → Geliştirici okur

Mikroservis Yaklaşımı:

text

Order Service → Container log
Payment Service → Container log
Inventory Service → Container log
Notification Service → Container log
... (50+ servis)

Sorunlar:

  • Dağıtık Log'lar: Her servisin log'u ayrı yerde; korelasyon yok.

  • Ephemeral Container'lar: Container silinince log'lar da kaybolur.

  • Ölçeklenme: 100 pod'un log'unu manuel toplamak imkansız.

  • Arama Zorluğu: "Sipariş 123 için ne oldu?" sorusuna tek noktadan yanıt yok.

Çözüm: Merkezi log toplama + Correlation ID.


2. Correlation ID: Dağıtık İzlemenin Anahtarı

Correlation ID, bir kullanıcı isteğinin tüm servisler arasındaki yolculuğunu takip eden benzersiz bir tanımlayıcıdır. Her servis, gelen Correlation ID'yi log'una ekler ve downstream servislere iletir.

Temel Akış:

text

Client → API Gateway
         (Correlation ID üretir: abc-123)
              ↓
         Order Service
         (Log: [abc-123] Sipariş alındı)
              ↓ (HTTP header: X-Correlation-ID: abc-123)
         Payment Service
         (Log: [abc-123] Ödeme işlendi)
              ↓ (Message header: CorrelationId=abc-123)
         Notification Service
         (Log: [abc-123] E-posta gönderildi)

Sonuç: abc-123 araması ile tüm servislerdeki ilgili log'lar tek ekranda görünür.


3. Correlation ID Yayılımı (Propagation)

Correlation ID'nin tüm servisler ve iletişim kanalları boyunca taşınması gerekir.

A. HTTP Çağrılarında Yayılım

csharp

// Gelen istekten Correlation ID al veya üret
public class CorrelationIdMiddleware
{
    private const string HeaderName = "X-Correlation-ID";
    private readonly RequestDelegate _next;

    public async Task InvokeAsync(HttpContext context)
    {
        // 1. Gelen header'dan al veya yeni üret
        var correlationId = context.Request.Headers[HeaderName].FirstOrDefault()
                            ?? Guid.NewGuid().ToString();

        // 2. HttpContext.Items'a ekle (request boyunca erişilebilir)
        context.Items["CorrelationId"] = correlationId;

        // 3. Yanıt header'ına ekle (istemci de görsün)
        context.Response.OnStarting(() =>
        {
            context.Response.Headers[HeaderName] = correlationId;
            return Task.CompletedTask;
        });

        // 4. Log context'ine ekle (Serilog)
        using (LogContext.PushProperty("CorrelationId", correlationId))
        {
            await _next(context);
        }
    }
}

B. HttpClient ile Otomatik Yayılım

csharp

// DelegatingHandler - giden isteklere Correlation ID ekler
public class CorrelationIdDelegatingHandler : DelegatingHandler
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var correlationId = _httpContextAccessor.HttpContext?.Items["CorrelationId"]?.ToString()
                            ?? Guid.NewGuid().ToString();

        request.Headers.Add("X-Correlation-ID", correlationId);
        return await base.SendAsync(request, cancellationToken);
    }
}

// Kayıt
builder.Services.AddTransient<CorrelationIdDelegatingHandler>();
builder.Services.AddHttpClient("PaymentService")
    .AddHttpMessageHandler<CorrelationIdDelegatingHandler>();

C. Message Queue ile Yayılım (MassTransit)

csharp

// Publish sırasında Correlation ID ekle
public class CorrelationIdPublishFilter<T> : IFilter<PublishContext<T>>
    where T : class
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public async Task Send(PublishContext<T> context, IPipe<PublishContext<T>> next)
    {
        var correlationId = _httpContextAccessor.HttpContext?.Items["CorrelationId"]?.ToString();
        if (correlationId != null)
            context.Headers.Set("X-Correlation-ID", correlationId);

        await next.Send(context);
    }
}

// Consume sırasında Correlation ID'yi geri yükle
public class CorrelationIdConsumeFilter<T> : IFilter<ConsumeContext<T>>
    where T : class
{
    public async Task Send(ConsumeContext<T> context, IPipe<ConsumeContext<T>> next)
    {
        var correlationId = context.Headers.Get<string>("X-Correlation-ID")
                            ?? Guid.NewGuid().ToString();

        using (LogContext.PushProperty("CorrelationId", correlationId))
        {
            await next.Send(context);
        }
    }
}

D. Background Service'lerde Yayılım

csharp

public class OrderProcessor : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var message = await _queue.DequeueAsync(stoppingToken);

            // Her mesaj için yeni bir log context
            using (LogContext.PushProperty("CorrelationId", message.CorrelationId))
            {
                _logger.LogInformation("Mesaj işleniyor: {MessageId}", message.Id);
                await ProcessAsync(message);
            }
        }
    }
}

4. Yapılandırılmış Loglama (Structured Logging)

Merkezi log toplamada, log'ların JSON formatında ve aranabilir alanlar içermesi kritiktir. Serilog, .NET ekosisteminde en yaygın kullanılan kütüphanedir.

Serilog Konfigürasyonu:

csharp

// Program.cs
Log.Logger = new LoggerConfiguration()
    .Enrich.FromLogContext()                          // CorrelationId gibi alanlar
    .Enrich.WithProperty("ServiceName", "OrderService")
    .Enrich.WithProperty("Environment", builder.Environment.EnvironmentName)
    .Enrich.WithMachineName()
    .WriteTo.Console(new JsonFormatter())              // JSON formatı
    .WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://elasticsearch:9200"))
    {
        AutoRegisterTemplate = true,
        IndexFormat = "logs-{0:yyyy.MM.dd}",
        NumberOfShards = 2,
        NumberOfReplicas = 1
    })
    .CreateLogger();

builder.Host.UseSerilog();

Yapılandırılmış Log Örneği:

json

{
  "@timestamp": "2026-10-15T10:30:45.123Z",
  "level": "Information",
  "messageTemplate": "Sipariş oluşturuldu: {OrderId}",
  "properties": {
    "OrderId": 123,
    "CorrelationId": "abc-123-def",
    "ServiceName": "OrderService",
    "Environment": "Production",
    "MachineName": "order-service-7d9f8b-x2k4m"
  },
  "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
  "spanId": "00f067aa0ba902b7"
}

Bu sayede Elasticsearch'te CorrelationId: "abc-123-def" araması ile tüm ilgili log'lar bulunur.


5. Log Aggregation Stack'leri

A. ELK Stack (Elasticsearch, Logstash, Kibana)

text

Uygulama → Logstash → Elasticsearch → Kibana
  • Elasticsearch: Log depolama ve arama motoru

  • Logstash: Log toplama ve dönüştürme

  • Kibana: Görselleştirme ve arama arayüzü

B. EFK Stack (Elasticsearch, Fluentd/Fluent Bit, Kibana)

text

Uygulama → Fluent Bit (DaemonSet) → Elasticsearch → Kibana
  • Fluent Bit: Hafif, düşük kaynak tüketimi; Kubernetes'te DaemonSet olarak çalışır

  • Fluentd: Daha zengin plugin ekosistemi

C. Grafana Loki (Modern Alternatif)

text

Uygulama → Promtail → Loki → Grafana
  • Loki: Elasticsearch'e göre daha ucuz, sadece metadata indeksler

  • Promtail: Log toplama agent'ı

  • Grafana: Görselleştirme (metrics ve logs bir arada)

D. Cloud Çözümleri

Bulut Servis
Azure Azure Monitor Logs / Application Insights
AWS CloudWatch Logs
GCP Cloud Logging

6. Kubernetes'te Log Toplama Mimarisi

text

┌─────────────────────────────────────────┐
│  Pod: order-service                      │
│  ┌────────────┐  ┌────────────┐         │
│  │ App        │  │ Fluent Bit │         │
│  │ (stdout)   │─>│ (sidecar)  │         │
│  └────────────┘  └────────────┘         │
└─────────────────────────────────────────┘
         │
         ↓
┌─────────────────────┐
│  Fluent Bit         │
│  (DaemonSet)        │
└─────────────────────┘
         │
         ↓
┌─────────────────────┐
│  Elasticsearch      │
└─────────────────────┘
         │
         ↓
┌─────────────────────┐
│  Kibana             │
└─────────────────────┘

Kritik Nokta: Uygulama log'ları stdout'a yazmalıdır (12-Factor App prensibi). Kubernetes, stdout'u otomatik olarak toplar; Fluent Bit DaemonSet'i tüm node'lardan log'ları Elasticsearch'e gönderir.


7. TraceId ile Correlation ID Entegrasyonu

Modern observability'de, Correlation ID yerine OpenTelemetry TraceId kullanmak daha güçlüdür. TraceId, hem log'larda hem trace'lerde aynıdır; log-trace korelasyonu sağlar.

Serilog + OpenTelemetry Entegrasyonu:

csharp

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
    );

// Serilog'a TraceId ve SpanId ekle
Log.Logger = new LoggerConfiguration()
    .Enrich.FromLogContext()
    .Enrich.With<ActivityEnricher>()  // TraceId, SpanId ekler
    .WriteTo.Console(new JsonFormatter())
    .CreateLogger();

Özel ActivityEnricher:

csharp

public class ActivityEnricher : ILogEventEnricher
{
    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        var activity = Activity.Current;
        if (activity != null)
        {
            logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
                "TraceId", activity.TraceId.ToString()));
            logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
                "SpanId", activity.SpanId.ToString()));
        }
    }
}

Sonuç: Kibana'da bir log bulduğunuzda, aynı TraceId ile Jaeger'da ilgili trace'e geçebilirsiniz.


8. Gerçek Dünya Senaryosu: Sipariş Hatası

Senaryo: Kullanıcı "Sipariş 12345 oluşturulamadı" hatası alıyor. Hangi serviste sorun var?

Adım 1: Kibana'da Ara

text

CorrelationId: "abc-123-def" OR OrderId: 12345

Adım 2: Log'ları İncele

text

[abc-123-def] OrderService: Sipariş oluşturuluyor...
[abc-123-def] OrderService: PaymentService çağrılıyor...
[abc-123-def] PaymentService: Ödeme işleniyor...
[abc-123-def] PaymentService: ERROR - Kart reddedildi (Kod: 51)
[abc-123-def] OrderService: ERROR - Ödeme başarısız, sipariş iptal edildi

Adım 3: Kök Nedeni Bul
Ödeme servisi, kart reddi (yetersiz bakiye) nedeniyle başarısız olmuş.

Adım 4: Trace ile Doğrula
TraceId: 4bf92f3577b34da6a3ce929d0e0e4736 ile Jaeger'da trace'i aç; PaymentService span'inde hata detayını gör.

Sonuç: 3 farklı servisin log'u tek bir arama ile ilişkilendirildi; sorun 2 dakikada tespit edildi.


9. En İyi Pratikler

Pratik Açıklama
Correlation ID'yi Gateway'de Üret İlk giriş noktası (API Gateway) üretmeli; downstream servisler aynısını kullanmalı.
Yapılandırılmış Log Kullan JSON formatı; aranabilir alanlar (CorrelationId, OrderId, UserId).
stdout'a Log Yaz 12-Factor App prensibi; container ortamında dosya yazma.
Hassas Veri Loglama Şifre, token, kredi kartı gibi bilgileri log'lamayın.
TraceId ile İlişkilendir Log'larda TraceId ve SpanId olsun; log-trace korelasyonu sağla.
Log Seviyelerini Yönet Production'da Information ve üstü; Debug sadece gerektiğinde.
Retention Policy Belirle Log'ları sonsuza kadar saklamayın; 30-90 gün tipik.
Index Yönetimi Elasticsearch'te günlük index (logs-2026.10.15); ILM ile otomatik silme.
Sampling Kullan Yüksek trafikte her log'u değil, önemli olanları topla.
Alerting Kur ERROR seviyesindeki log'lar için otomatik uyarı.

10. Log Aggregation vs. Distributed Tracing

Özellik Log Aggregation Distributed Tracing
Amaç Olayları kaydetmek ve aramak İstek yolculuğunu görselleştirmek
Veri Türü Metin/JSON log'lar Span'ler ve süreler
Cevapladığı Soru "Ne oldu?" "Nerede yavaş?"
Korelasyon CorrelationId TraceId
Depolama Maliyeti Yüksek (tüm log'lar) Orta (örnekleme)
Kullanım Hata ayıklama, audit Performans analizi

En İyi Yaklaşım: İkisini birlikte kullanın. Log'lar detay verir; trace'ler bağlam verir. TraceId ile her ikisini ilişkilendirin.


Sonuç

Mikroservis mimarisinde, log'ları merkezi olarak toplamak ve Correlation ID ile ilişkilendirmek, sorun tespitini saatlerden dakikalara indirir. Yapılandırılmış loglama (Serilog), ELK/EFK/Loki stack'leri, Kubernetes Fluent Bit DaemonSet'i ve OpenTelemetry TraceId entegrasyonu ile uçtan uca gözlemlenebilirlik sağlarsınız. Correlation ID'yi HTTP header'ları, mesaj kuyrukları ve background service'ler üzerinden yayarak, bir kullanıcı isteğinin tüm servislerdeki izini sürebilirsiniz. Unutmayın: Dağıtık sistemlerde, log'ları olmayan bir sorun, hiç yaşanmamış gibidir. Merkezi log toplama ve Correlation ID, bu sorunu çözmenin temelidir.

Tüm yazılar Soru ya da yorumun mu var? Yaz