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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir