Node.js’te İzlenebilirlik (Observability): Log, Metrik ve Trace’i Uyumlu Kurmak
Node.js uygulamalarında log, metrik ve dağıtık iz sürmeyi (trace) birlikte kurgulayarak hatayı hızlı bulma.
Modern Node.js uygulamalarında “çalışıyor” demek yetmiyor; ne olduğunu hızlı anlayabilmek gerekiyor. İzlenebilirlik (observability) üç temel sinyalin birlikte ele alınmasıdır: loglar, metrikler ve trace’ler. Bu yazıda, Node.js tarafında bu üçlüyü aynı dilde konuşturmanın pratik bir yolunu anlatıyorum.
1) Neden üçü birden?
- Log: O anda ne oldu? (Bağlam, hata mesajı)
- Metrik: Ne kadar oldu? (İstek sayısı, gecikme, hata oranı)
- Trace: Nerede oldu? (Bir isteğin servisler/fonksiyonlar boyunca yolculuğu)
Tek başına log ile “koptu”yu görürsünüz; metrikle “arttı”yı görürsünüz; trace ile “hangi adımda”yı görürsünüz. En iyi sonuç, korelasyon ile gelir.
2) Yapılandırılmış log: metin değil veri
console.log yerine JSON log basın. Amaç: Logları sorgulanabilir kılmak.
Pino + request-id (korelasyon id) örneği:
import express from 'express';
import pino from 'pino';
import { randomUUID } from 'crypto';
const app = express();
const logger = pino({ level: process.env.LOG_LEVEL ?? 'info' });
// Her isteğe requestId ekle
app.use((req, res, next) => {
req.requestId = req.headers['x-request-id'] ?? randomUUID();
res.setHeader('x-request-id', req.requestId);
next();
});
app.get('/health', (req, res) => {
logger.info({ requestId: req.requestId, route: '/health' }, 'health check');
res.json({ ok: true });
});
app.listen(3000);
İpucu: Loglara requestId, userId (varsa), route, duration_ms, error_code gibi alanları ekleyin. Bu alanlar sonradan altın değerinde olur.
3) Metrik: “sistem nasıl gidiyor?” sorusunun cevabı
Node.js için yaygın yaklaşım Prometheus formatında metrik yayımlamaktır. prom-client ile hızlıca başlayabilirsiniz.
import express from 'express';
import client from 'prom-client';
const app = express();
client.collectDefaultMetrics();
const httpDuration = new client.Histogram({
name: 'http_request_duration_seconds',
help: 'HTTP request duration in seconds',
labelNames: ['method', 'route', 'status'],
buckets: [0.01, 0.05, 0.1, 0.3, 0.5, 1, 2, 5]
});
app.use((req, res, next) => {
const end = httpDuration.startTimer();
res.on('finish', () => {
end({ method: req.method, route: req.route?.path ?? req.path, status: String(res.statusCode) });
});
next();
});
app.get('/metrics', async (_, res) => {
res.set('Content-Type', client.register.contentType);
res.send(await client.register.metrics());
});
app.get('/orders/:id', (req, res) => {
// ...
res.json({ id: req.params.id });
});
app.listen(3000);
Bu histogram sayesinde:
- p95/p99 gecikmelerini,
- belirli route’larda hata artışını,
- deploy sonrası performans etkisini net görürsünüz.
4) Trace: “bu istek neden yavaş?” sorusunun cevabı
Dağıtık iz sürme için pratik standart: OpenTelemetry. Temel fikir: Her isteğe bir trace id atanır; DB sorgusu, dış servis çağrısı gibi adımlar “span” olarak kaydedilir.
Minimal kavramlar:
- Trace: Bir isteğin uçtan uca hikayesi
- Span: Hikayedeki adımlar (örn. “postgres query”, “payment provider call”)
Önemli nokta: Loglarınıza traceId/spanId koyarsanız, log ↔ trace arasında tek tıkla gezebilirsiniz (Grafana/Jaeger/Tempo gibi araçlarla).
5) Altın kural: Korelasyon olmadan observability eksik kalır
Şu üçlü aynı anda bulunmalı:
- Log:
requestIdve mümkünsetraceId - Metrik:
route/statusgibi etiketler (label) + aşırı etiketlemeden kaçınma - Trace: dış çağrılar ve kritik bloklar için span’ler
Dikkat: Metriklerde userId, orderId gibi yüksek kardinaliteli label’lar sistemi şişirir. Bu tür detaylar log/trace tarafına daha uygundur.
6) Küçük bir kontrol listesi
- JSON log + requestId
- Hata loglarında stack trace + anlamlı error code
- HTTP latency histogram (p95/p99 takip)
- /metrics endpoint’i
- TraceId’yi loglara ekleme
- Prod’da örnekleme (sampling) ve PII maskeleme
İzlenebilirliği baştan kurduğunuzda, “prod’da oldu ama bende olmadı” cümlesi giderek tarihe karışır.