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
-
Counter (Sayaç): Sadece artan değerler (Toplam istek sayısı, toplam hata sayısı)
http_requests_total{method="GET", endpoint="/users"} 15234 -
Gauge (Ölçer): Artıp azalabilen değerler (Aktif bağlantı sayısı, CPU kullanımı)
active_connections 42 cpu_usage_percent 67.5 -
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 -
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.
-
Metrics (İlk İpucu):
# Request sürelerinde artış var mı? histogram_quantile(0.99, rate(request_duration_bucket[5m]))request_duration_secondsmetriğinde %99'luk dilimin normalde 200ms iken şimdi 2 saniye olduğunu görürsünüz.
-
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.
-
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.
- Payment service'in loglarını trace ID ile filtreleyerek incelersiniz:
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!