Modern yazılım sistemleri, monolitik yapılardan mikroservis mimarilerine evrildikçe, sistemin içinde neler olup bittiğini anlamak giderek zorlaştı. İşte tam bu noktada Gözlemleyebilirlik (Observability) kavramı devreye girer. Bir sistemi gözlemleyebilir kılan üç temel yapı taşı vardır: Loglar (Logs), Metrikler (Metrics) ve İzler (Traces).

Bu yazıda, bu üç direğin ne olduğunu, nasıl çalıştıklarını ve bir sistemin sağlığını anlamak için nasıl birlikte kullanıldıklarını detaylıca inceleyeceğiz.


1. Logs (Günlükler) - "Ne Oldu?"

Mantık

Loglar, bir sistemde meydana gelen olayların ayrıntılı, zaman damgalı kayıtlarıdır. Bir uygulamanın "günlüğü" veya "güncesi" gibi düşünülebilir. Bir hata oluştuğunda, bir kullanıcı giriş yaptığında veya bir servis dışarıya çağrı yaptığında, bu olaylar log'a yazılır.

Teknik Detaylar

Loglar genellikle düz metin (plain text) veya yapılandırılmış (structured) formatlarda olabilir:

  • Düz Metin Log: İnsan tarafından okunabilir ama makine tarafından işlenmesi zordur.

    2026-02-28 10:15:30 ERROR Kullanıcı 12345 için sipariş oluşturulamadı: Veritabanı bağlantı hatası
  • Yapılandırılmış Log (JSON): Makineler tarafından kolayca parse edilebilir ve sorgulanabilir.

    {
    "timestamp": "2026-02-28T10:15:30.123Z",
    "level": "ERROR",
    "service": "order-service",
    "userId": 12345,
    "message": "Sipariş oluşturulamadı",
    "error": "Veritabanı bağlantı hatası",
    "duration_ms": 150
    }

Log Seviyeleri

Loglar genellikle önem derecesine göre seviyelendirilir:

  • DEBUG: Geliştiriciler için detaylı bilgi (sadece geliştirme ortamında)
  • INFO: Normal işlemler hakkında bilgi (Kullanıcı giriş yaptı, servis başladı)
  • WARN: Potansiyel sorun (Disk dolmak üzere, yeniden deneme yapıldı)
  • ERROR: Hata ama sistem çalışmaya devam ediyor (Veritabanı sorgusu başarısız)
  • FATAL: Kritik hata, sistem kapanabilir

Kod Örneği (Node.js + Winston)

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

// Kullanım
app.get('/user/:id', async (req, res) => {
  logger.info(`Kullanıcı bilgisi istendi: ${req.params.id}`);

  try {
    const user = await getUserFromDB(req.params.id);
    logger.debug('Kullanıcı veritabanından alındı', { userId: req.params.id });
    res.json(user);
  } catch (error) {
    logger.error('Kullanıcı getirilemedi', { 
      userId: req.params.id, 
      error: error.message 
    });
    res.status(500).send('Hata oluştu');
  }
});

2. Metrics (Metrikler) - "Ne Kadar?"

Mantık

Metrikler, sistemin belirli bir andaki durumunu veya zaman içindeki değişimini gösteren sayısal ölçümlerdir. Loglar olayları anlatırken, metrikler sistemin performansını ve sağlığını özetler. "Kaç tane?", "Ne kadar sürede?", "Yüzde kaçı?" sorularına cevap verir.

Metrik Türleri

  1. Counter (Sayaç): Sadece artan değerler (Toplam istek sayısı, toplam hata sayısı)

    http_requests_total{method="GET", endpoint="/users"} 15234
  2. Gauge (Ölçer): Artıp azalabilen değerler (Aktif bağlantı sayısı, CPU kullanımı)

    active_connections 42
    cpu_usage_percent 67.5
  3. Histogram (Histogram): Gözlemleri gruplandırarak dağılım gösterir (İstek süreleri, paket boyutları)

    request_duration_seconds_bucket{le="0.1"} 1250  # 0.1 saniyeden kısa süren istekler
    request_duration_seconds_bucket{le="0.5"} 4500  # 0.5 saniyeden kısa süren istekler
    request_duration_seconds_bucket{le="+Inf"} 5120 # Toplam istek
  4. Summary (Özet): Histogram'a benzer ama quantile (yüzdelik dilim) hesaplar

    request_duration_seconds{quantile="0.5"} 0.072  # Medyan (50%): 72ms
    request_duration_seconds{quantile="0.99"} 0.534 # %99'luk dilim: 534ms

Kod Örneği (Python + Prometheus)

from prometheus_client import Counter, Histogram, Gauge, generate_latest
import time
from flask import Flask, request

app = Flask(__name__)

# Metrik tanımlamaları
REQUEST_COUNT = Counter('http_requests_total', 'Toplam HTTP isteği', 
                        ['method', 'endpoint', 'status'])
REQUEST_DURATION = Histogram('request_duration_seconds', 'İstek süresi',
                             ['method', 'endpoint'])
ACTIVE_REQUESTS = Gauge('active_requests', 'Aktif istek sayısı')

@app.route('/api/users/<int:user_id>')
def get_user(user_id):
    ACTIVE_REQUESTS.inc()  # Aktif istek sayısını artır
    start_time = time.time()

    try:
        # İşlemler...
        user = fetch_user(user_id)
        status = 200
        return user
    except Exception as e:
        status = 500
        return "Hata", 500
    finally:
        # Metrikleri kaydet
        duration = time.time() - start_time
        REQUEST_COUNT.labels(method='GET', endpoint='/users', status=status).inc()
        REQUEST_DURATION.labels(method='GET', endpoint='/users').observe(duration)
        ACTIVE_REQUESTS.dec()  # Aktif istek sayısını azalt

@app.route('/metrics')
def metrics():
    # Prometheus'un scrape edeceği endpoint
    return generate_latest()

3. Traces (İzler) - "Nerede ve Nasıl?"

Mantık

Traces, bir isteğin dağıtık bir sistemde izlediği yolu gösterir. Özellikle mikroservis mimarilerinde, bir kullanıcı isteği onlarca farklı servisi ziyaret edebilir. Trace, bu isteğin tüm yolculuğunu uçtan uca görselleştirir.

Temel Kavramlar

  • Trace: Bir isteğin tüm yolculuğunu temsil eden ağaç yapısı
  • Span: Trace içindeki tek bir işlem birimi (bir servis çağrısı, bir veritabanı sorgusu)
  • Trace ID: Tüm trace'i tanımlayan benzersiz kimlik
  • Span ID: Tek bir span'i tanımlayan kimlik
  • Parent Span ID: Hangi span'in bu span'i çağırdığını gösterir

Görselleştirme

Bir trace şöyle görünebilir:

[Trace ID: abc-123]
    ├── [Span A: 0-100ms] API Gateway
    │   ├── [Span B: 10-30ms] Auth Service (doğrulama)
    │   └── [Span C: 35-90ms] Order Service
    │       ├── [Span D: 40-50ms] PostgreSQL (sorgu)
    │       └── [Span E: 55-80ms] Payment Service
    │           └── [Span F: 60-75ms] Redis (ödeme önbelleği)

Kod Örneği (Node.js + OpenTelemetry)

const { NodeTracerProvider } = require('@opentelemetry/node');
const { SimpleSpanProcessor } = require('@opentelemetry/tracing');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
const express = require('express');

// Tracer provider'ı yapılandır
const provider = new NodeTracerProvider();
const exporter = new JaegerExporter({ serviceName: 'order-service' });
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
provider.register();

const tracer = provider.getTracer('order-service');
const app = express();

app.get('/api/orders/:id', async (req, res) => {
  // Ana span oluştur
  const parentSpan = tracer.startSpan('get-order');

  try {
    // Veritabanı sorgusu için alt span
    const dbSpan = tracer.startSpan('database-query', {
      parent: parentSpan
    });

    const order = await database.findOrder(req.params.id);
    dbSpan.setAttribute('order.id', req.params.id);
    dbSpan.end();  // Veritabanı span'i bitir

    // Payment servisi çağrısı için alt span
    const paymentSpan = tracer.startSpan('payment-service-call', {
      parent: parentSpan
    });

    const payment = await paymentService.getPayment(order.paymentId);
    paymentSpan.setAttribute('payment.status', payment.status);
    paymentSpan.end();  // Payment span'i bitir

    parentSpan.setStatus({ code: 0 });  // Başarılı
    res.json({ order, payment });

  } catch (error) {
    parentSpan.setStatus({ 
      code: 1,  // Hata
      message: error.message 
    });
    res.status(500).send('Hata');
  } finally {
    parentSpan.end();  // Ana span'i bitir
  }
});

Üçlünün Birlikte Çalışması

Bu üç araç, birbirini tamamlayarak sistemin tam bir resmini çizer:

Gerçek Hayat Senaryosu: Yavaşlama Problemi

Diyelim ki kullanıcılar "siparişlerim" sayfasının çok yavaş açıldığını bildiriyor.

  1. Metrics (İlk İpucu):

    # Request sürelerinde artış var mı?
    histogram_quantile(0.99, rate(request_duration_bucket[5m]))
    • request_duration_seconds metriğinde %99'luk dilimin normalde 200ms iken şimdi 2 saniye olduğunu görürsünüz.
  2. Traces (Detaylı Analiz):

    • Yavaş olan isteklerin trace'lerini incelersiniz.
    • Trace'lerde "payment-service" çağrısının normalde 50ms sürerken şimdi 1.5 saniye sürdüğünü fark edersiniz.
  3. Logs (Kök Neden):

    • Payment service'in loglarını trace ID ile filtreleyerek incelersiniz:
      {
      "traceId": "abc-123",
      "level": "WARN",
      "message": "Redis bağlantı zaman aşımı, fallback'e geçildi",
      "timestamp": "2026-02-28T10:15:30.123Z"
      }
    • Redis'e bağlanılamadığı ve fallback mekanizmasının devreye girdiği için yavaşlama olduğu anlaşılır.

Popüler Araçlar ve Stack'ler

Tümleşik Çözümler (All-in-One)

  • Grafana Stack (LGTM): Loki (Logs), Grafana (Görselleştirme), Tempo (Traces), Mimir (Metrics)
  • Elastic Stack (ELK): Elasticsearch (Depolama), Logstash (İşleme), Kibana (Görselleştirme)
  • Datadog: Ticari, hepsi bir arada
  • New Relic: Ticari, APM odaklı

Açık Kaynak Araçlar

  • Metrics: Prometheus, Graphite, InfluxDB
  • Logs: Elasticsearch, Loki, Fluentd
  • Traces: Jaeger, Zipkin, OpenTelemetry
  • Görselleştirme: Grafana, Kibana

En İyi Pratikler (Best Practices)

1. Yapılandırılmış Log Kullanın

JSON formatında log tutun. Parse etmesi ve sorgulaması kolaydır.

2. Correlation ID (İlişkilendirme Kimliği) Ekleyin

Tüm loglara ve metriklere trace ID veya request ID ekleyerek ilişkilendirme yapın.

3. Önemli Metrikleri Standardize Edin

Her servis için şu metrikleri mutlaka toplayın:

  • RED Method: Rate (istek sayısı), Errors (hata sayısı), Duration (süre)
  • USE Method: Utilization (kullanım), Saturation (doygunluk), Errors (hatalar)

4. Log Seviyelerine Dikkat Edin

  • Production'da DEBUG logu açmayın (performans ve maliyet sorunu)
  • ERROR logları mutlaka bir alert sistemine bağlı olmalı

5. Örnekleme (Sampling) Kullanın

Özellikle trace'lerde her isteği kaydetmek maliyetlidir. Başarılı isteklerin sadece %1-10'unu, hatalı isteklerin ise tamamını kaydedin.

// Akıllı örnekleme
if (request.status === 'error' || Math.random() < 0.01) {
  // Trace'i kaydet
}

Sonuç

Logs, Metrics ve Traces, modern yazılım sistemlerini anlamanın üç temel direğidir:

  • Loglar size "Ne oldu?" sorusunun detaylı cevabını verir
  • Metrikler size "Ne kadar?" sorusunun özet istatistiğini sunar
  • İzler size "Nerede ve nasıl?" sorusunun yol haritasını çizer

Bu üçünü birlikte kullanmak, bir sistem karanlıkta kaldığında elinizde bir fener olmasını sağlar. Hangisini kullanacağınız sorusu değil, hepsini nasıl entegre edeceğiniz sorusu önemlidir. Unutmayın, gözlemleyemediğiniz bir sistemi yönetemezsiniz!