REST API Loglama ve İzleme: Sorun Tespiti İçin Kapsamlı Rehber
Bir REST API’nin ne zaman sorun yaşadığını anlamak, bazen dedektiflik çalışmasına dönüşebilir. Loglar eksik, metrikler yetersiz, hata mesajları anlamsız… Bu durumda production ortamında bir şeyler patladığında saatlerce karanlıkta el yordamıyla ilerliyorsunuz. Bu yazıda, REST API’lerini düzgün şekilde nasıl izleyeceğinizi, anlamlı loglar nasıl üreteceğinizi ve sorun çıktığında hızlıca kök nedene nasıl ulaşacağınızı ele alacağız.
Neden API Loglama Bu Kadar Önemli?
Klasik bir senaryo düşünün: Müşteri sabah 09:00’da aradı, “Dün gece ödeme API’miz çalışmadı” diyor. Loglarınıza bakıyorsunuz, sadece şunu görüyorsunuz:
[ERROR] Request failed
[ERROR] Request failed
[ERROR] Request failed
Hangi endpoint? Hangi kullanıcı? Hangi istek gövdesi? Hangi response kodu? Hiçbir şey yok. İşte bu, kötü loglama pratiğinin tipik sonucu.
İyi bir API loglama sistemi size şunları söyleyebilmelidir:
- Hangi endpoint çağrıldı ve ne zaman
- İsteği kim yaptı (IP, kullanıcı kimliği, API key)
- İstek ne kadar sürdü (latency)
- Hangi HTTP status kodu döndü
- Hata varsa tam stack trace
- Correlation ID ile request’i uçtan uca takip edebilme
Temel Log Yapısı: Structured Logging
Ham metin logları analiz etmek çok zordur. Bunun yerine JSON formatında structured logging kullanın. Bu sayede logları Elasticsearch, Splunk veya Loki gibi araçlara kolayca aktarabilirsiniz.
Node.js/Express için Winston ile yapılandırılmış loglama örneği:
# Gerekli paketleri kuralım
npm install winston winston-daily-rotate-file uuid express morgan
// logger.js - Merkezi log yapılandırması
const winston = require('winston');
const { v4: uuidv4 } = require('uuid');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DDTHH:mm:ss.SSSZ' }),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: {
service: 'payment-api',
environment: process.env.NODE_ENV || 'production',
version: process.env.APP_VERSION || '1.0.0'
},
transports: [
new winston.transports.Console(),
new winston.transports.DailyRotateFile({
filename: '/var/log/api/error-%DATE%.log',
datePattern: 'YYYY-MM-DD',
level: 'error',
maxFiles: '30d',
maxSize: '100m'
}),
new winston.transports.DailyRotateFile({
filename: '/var/log/api/combined-%DATE%.log',
datePattern: 'YYYY-MM-DD',
maxFiles: '30d',
maxSize: '500m'
})
]
});
module.exports = logger;
Bu yapıyla her log satırı JSON formatında olacak ve analiz çok daha kolaylaşacak.
Middleware ile Otomatik Request/Response Loglama
Her endpoint’e ayrı ayrı log eklemek yerine, middleware kullanarak bütün istekleri merkezi olarak loglayın.
// middleware/requestLogger.js
const { v4: uuidv4 } = require('uuid');
const logger = require('../logger');
const requestLogger = (req, res, next) => {
const correlationId = req.headers['x-correlation-id'] || uuidv4();
const startTime = Date.now();
// Her request için correlation ID ata
req.correlationId = correlationId;
res.setHeader('X-Correlation-ID', correlationId);
// Request bilgilerini logla
logger.info('API Request', {
correlationId,
method: req.method,
path: req.path,
query: req.query,
ip: req.ip || req.connection.remoteAddress,
userAgent: req.headers['user-agent'],
userId: req.user?.id || 'anonymous',
apiKey: req.headers['x-api-key'] ? '***' + req.headers['x-api-key'].slice(-4) : null,
contentType: req.headers['content-type'],
contentLength: req.headers['content-length']
});
// Response'u yakalamak için override
const originalSend = res.send;
res.send = function(body) {
const duration = Date.now() - startTime;
logger.info('API Response', {
correlationId,
method: req.method,
path: req.path,
statusCode: res.statusCode,
duration: `${duration}ms`,
responseSize: Buffer.byteLength(body || '', 'utf8'),
userId: req.user?.id || 'anonymous'
});
// Yavaş response'ları ayrıca işaretle
if (duration > 2000) {
logger.warn('Slow API Response Detected', {
correlationId,
path: req.path,
duration: `${duration}ms`,
threshold: '2000ms'
});
}
originalSend.call(this, body);
};
next();
};
module.exports = requestLogger;
Önemli not: Request body’yi loglarken dikkatli olun. Şifre, kredi kartı numarası gibi hassas verileri asla loglamamalısınız. Bunun için bir sanitize fonksiyonu yazın.
// utils/sanitize.js - Hassas verileri maskele
const SENSITIVE_FIELDS = [
'password', 'passwordConfirm', 'creditCard',
'cardNumber', 'cvv', 'ssn', 'token', 'secret'
];
const sanitizeBody = (body) => {
if (!body || typeof body !== 'object') return body;
const sanitized = { ...body };
SENSITIVE_FIELDS.forEach(field => {
if (sanitized[field]) {
sanitized[field] = '[REDACTED]';
}
});
return sanitized;
};
module.exports = { sanitizeBody };
Nginx Üzerinde API Access Log Yapılandırması
Uygulamanın önünde Nginx varsa, uygulama seviyesine gelmeden önce de log toplamanız gerekir. Bu sayede hangi isteklerin uygulamaya ulaşıp ulaşmadığını görebilirsiniz.
# /etc/nginx/nginx.conf içinde log formatı tanımlama
http {
log_format api_json escape=json
'{'
'"timestamp":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"method":"$request_method",'
'"uri":"$uri",'
'"args":"$args",'
'"status":$status,'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"body_bytes_sent":$body_bytes_sent,'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_correlation_id":"$http_x_correlation_id",'
'"http_x_forwarded_for":"$http_x_forwarded_for"'
'}';
access_log /var/log/nginx/api_access.log api_json;
error_log /var/log/nginx/api_error.log warn;
server {
listen 443 ssl;
server_name api.sirket.com;
# Rate limiting zone tanımı
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/m;
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend:3000;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
Prometheus ve Grafana ile Metrik İzleme
Log analiziyle birlikte metrik tabanlı izleme de olmazsa olmaz. Prometheus ile API metriklerini toplayıp Grafana’da görselleştirelim.
# Docker Compose ile Prometheus + Grafana kurulumu
cat > docker-compose-monitoring.yml << 'EOF'
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
ports:
- "9090:9090"
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=30d'
grafana:
image: grafana/grafana:latest
environment:
- GF_SECURITY_ADMIN_PASSWORD=gizli_sifre
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- grafana_data:/var/lib/grafana
ports:
- "3001:3000"
depends_on:
- prometheus
volumes:
prometheus_data:
grafana_data:
EOF
docker-compose -f docker-compose-monitoring.yml up -d
Prometheus konfigürasyonu:
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
rule_files:
- "api_alerts.yml"
scrape_configs:
- job_name: 'payment-api'
static_configs:
- targets: ['api-server:3000']
metrics_path: '/metrics'
scrape_interval: 10s
- job_name: 'nginx'
static_configs:
- targets: ['nginx-exporter:9113']
Node.js uygulamasına Prometheus entegrasyonu:
// metrics.js - Prometheus metrikleri
const promClient = require('prom-client');
// Default metrikleri topla (CPU, bellek vs.)
promClient.collectDefaultMetrics({ prefix: 'payment_api_' });
// Özel metrikler tanımla
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'HTTP request duration in seconds',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.01, 0.05, 0.1, 0.3, 0.5, 1, 2, 5]
});
const httpRequestTotal = new promClient.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
});
const activeConnections = new promClient.Gauge({
name: 'http_active_connections',
help: 'Number of active HTTP connections'
});
const apiErrors = new promClient.Counter({
name: 'api_errors_total',
help: 'Total number of API errors',
labelNames: ['route', 'error_type']
});
// Middleware olarak kullan
const metricsMiddleware = (req, res, next) => {
const end = httpRequestDuration.startTimer();
activeConnections.inc();
res.on('finish', () => {
const route = req.route?.path || req.path;
const labels = {
method: req.method,
route: route,
status_code: res.statusCode
};
end(labels);
httpRequestTotal.inc(labels);
activeConnections.dec();
if (res.statusCode >= 500) {
apiErrors.inc({
route: route,
error_type: 'server_error'
});
}
});
next();
};
module.exports = {
metricsMiddleware,
register: promClient.register
};
ELK Stack ile Log Aggregation
Birden fazla sunucu veya mikroservis varsa, logları merkezi bir yerde toplamak zorunlu hale gelir. Filebeat + Logstash + Elasticsearch + Kibana kombinasyonu bu iş için standart çözümdür.
# Filebeat konfigürasyonu - /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/api/*.log
json.keys_under_root: true
json.add_error_key: true
fields:
service: payment-api
environment: production
multiline.pattern: '^{'
multiline.negate: true
multiline.match: after
- type: log
enabled: true
paths:
- /var/log/nginx/api_access.log
json.keys_under_root: true
fields:
service: nginx
log_type: access
processors:
- drop_fields:
fields: ["agent", "ecs", "host", "input", "log"]
ignore_missing: true
output.logstash:
hosts: ["logstash:5044"]
ssl.enabled: true
ssl.certificate_authorities: ["/etc/filebeat/ca.crt"]
Logstash pipeline:
# /etc/logstash/conf.d/api-logs.conf
input {
beats {
port => 5044
ssl => true
ssl_certificate => "/etc/logstash/certs/logstash.crt"
ssl_key => "/etc/logstash/certs/logstash.key"
}
}
filter {
if [fields][service] == "payment-api" {
# Status koduna göre log seviyesi belirle
if [statusCode] >= 500 {
mutate { add_field => { "severity" => "critical" } }
} else if [statusCode] >= 400 {
mutate { add_field => { "severity" => "warning" } }
} else {
mutate { add_field => { "severity" => "info" } }
}
# Duration'ı sayısal değere çevir
if [duration] {
grok {
match => { "duration" => "%{NUMBER:duration_ms}ms" }
}
mutate {
convert => { "duration_ms" => "float" }
}
}
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "api-logs-%{+YYYY.MM.dd}"
user => "logstash_internal"
password => "${LOGSTASH_PASSWORD}"
}
}
Alert Kuralları ile Proaktif Sorun Tespiti
Sorunları müşteriden önce siz tespit etmelisiniz. Prometheus Alertmanager ile kritik durumlar için alarm kuralları tanımlayın.
# api_alerts.yml
groups:
- name: api_alerts
rules:
# 5 dakika içinde error rate %5'i geçerse
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status_code=~"5.."}[5m])) /
sum(rate(http_requests_total[5m])) > 0.05
for: 2m
labels:
severity: critical
annotations:
summary: "API error rate yüksek: {{ $value | humanizePercentage }}"
description: "Son 5 dakikada hata oranı %5'i aştı. Servis: {{ $labels.service }}"
# 95. percentile latency 2 saniyeyi geçerse
- alert: SlowAPIResponse
expr: |
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le, route)
) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "Yavaş API response tespit edildi"
description: "{{ $labels.route }} endpoint'i için p95 latency 2 saniyeyi aştı"
# API tamamen erişilemez olursa
- alert: APIDown
expr: up{job="payment-api"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "API servisi erişilemez durumda!"
description: "payment-api servisi son 1 dakikadır cevap vermiyor"
Gerçek Dünya Senaryosu: Üretimde Sorun Tespiti
Şimdi gelin, bir production sorununu loglar ve metrikler yardımıyla nasıl çözebileceğimizi görelim.
Senaryo: Müşteriler sipariş oluşturamıyor, ama hata mesajı görmüyorlar. Sadece işlemin çok uzun sürdüğünü söylüyorlar.
Önce Nginx loglarına bakalım:
# Son 1 saatin loglarını filtrele ve yavaş requestleri bul
cat /var/log/nginx/api_access.log |
jq 'select(.request_time > 5)' |
jq -r '[.timestamp, .method, .uri, .status, .request_time, .upstream_response_time] | @csv' |
sort -t',' -k6 -rn | head -20
# Belirli bir endpoint'e gelen isteklerin durum dağılımına bak
cat /var/log/nginx/api_access.log |
jq 'select(.uri | contains("/api/orders"))' |
jq -r '.status' |
sort | uniq -c | sort -rn
Çıktıyı inceliyorsunuz ve /api/orders endpoint’i için upstream_response_time değerlerinin aniden 0.1 saniyeden 8-12 saniyeye çıktığını görüyorsunuz. Ama status kodları hala 200. Bu demek oluyor ki uygulama çalışıyor ama çok yavaş.
Uygulama loglarına geçiyoruz:
# Belirli zaman aralığında yavaş sorguları bul
grep '"path":"/api/orders"' /var/log/api/combined-2024-01-15.log |
jq 'select(.duration_ms > 5000)' |
jq -r '[.timestamp, .correlationId, .duration, .userId] | @tsv'
# Correlation ID ile bir isteği uçtan uca takip et
CORRELATION_ID="3f2504e0-4f89-11d3-9a0c-0305e82c3301"
grep "$CORRELATION_ID" /var/log/api/combined-2024-01-15.log | jq '.'
Bu araştırma sonucunda veritabanı bağlantı havuzunun tükendiğini görüyorsunuz. Yeni bir index eklenmeden yapılan bir sorgu, tablo büyüdükçe full scan yapmaya başlamış.
Sorunu bulduktan sonra hızlı düzeltme:
# Veritabanı yavaş sorguları tespit et (PostgreSQL örneği)
psql -U dbuser -d production_db << 'EOF'
SELECT
pid,
now() - query_start AS duration,
query,
state,
wait_event_type,
wait_event
FROM pg_stat_activity
WHERE state != 'idle'
AND now() - query_start > interval '5 seconds'
ORDER BY duration DESC;
EOF
# Gerekli index'i ekle
psql -U dbuser -d production_db -c
"CREATE INDEX CONCURRENTLY idx_orders_user_id_created_at ON orders(user_id, created_at DESC);"
Bu düzeltme sonrası response sürelerinin anında normalleştiğini Grafana dashboard’ınızda göreceksiniz.
Log Retention ve Maliyet Yönetimi
Loglar disk doldurur ve para harcar. Akıllı bir retention politikası şart.
# Logrotate konfigürasyonu - /etc/logrotate.d/api-logs
/var/log/api/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0640 nodejs nodejs
sharedscripts
postrotate
# Uygulamayı log dosyalarını yeniden açmaya zorla
kill -USR1 $(cat /var/run/api.pid 2>/dev/null) 2>/dev/null || true
endscript
}
# Eski Elasticsearch index'lerini temizle (30 günden eski)
# curator_config.yml
client:
hosts:
- elasticsearch
port: 9200
logging:
loglevel: INFO
# curator_actions.yml
actions:
1:
action: delete_indices
description: "30 günden eski API log index'lerini sil"
options:
ignore_empty_list: True
filters:
- filtertype: pattern
kind: prefix
value: api-logs-
- filtertype: age
source: name
direction: older
timestring: '%Y.%m.%d'
unit: days
unit_count: 30
Sonuç
REST API loglama ve izleme, bir kerelik kurulup unutulan bir şey değil; sürekli geliştirilmesi gereken bir sistemdir. Özetlemek gerekirse şunlara dikkat edin:
- Structured logging kullanın, ham metin log yazmayın. JSON format, analizi kolaylaştırır.
- Correlation ID ile her isteği uçtan uca takip edebilir hale gelin. Mikroservis mimarisinde bu kurtarıcı olur.
- Hassas verileri asla loglamayın. GDPR ve PCI-DSS gibi regülasyonlar açısından da büyük risk.
- Hem uygulama hem de altyapı katmanında log toplayın. Nginx logları bazen uygulamanın hiç göremediği sorunları ortaya koyar.
- Metrikler ve loglar birbirini tamamlar. Grafana’da alarm tetiklenince, direkt ELK’ta ilgili zaman dilimine bakabiliyorsanız sorun tespiti dakikalar sürer.
- Alert kurallarınızı gerçekçi eşiklerle ayarlayın. Çok hassas ayarlar alarm yorgunluğuna yol açar ve önemli alarmlar gürültüde kaybolur.
- Log retention politikası belirleyin. Sonsuz log biriktirmek hem maliyetli hem de yasal açıdan riskli olabilir.
İyi bir izleme altyapısı kurduğunuzda, üretimde bir şeyler patladığında paniklemek yerine sakin bir şekilde logları açıp sorunu bulmaya başlarsınız. Bu fark, hem sizin hem de kullanıcılarınızın hayatını çok daha kolay hale getirir.
