API İsteklerini Loglama ve İzleme: Kapsamlı Bir Rehber

Bir servisi production’a aldıktan sonra “acaba şu istek gerçekten gitti mi?”, “neden bu endpoint bu kadar yavaş?” ya da “kim bu kadar istek atıyor?” gibi sorularla uğraşmaya başlarsın. İşte tam bu noktada API loglama ve izleme devreye girer. Düzgün kurulmamış bir loglama sistemi, production ortamında kör uçmak gibidir. Bu yazıda API isteklerini nasıl düzgün loglayacağını, anlamlı metrikler nasıl toplayacağını ve uyarı mekanizmalarını nasıl kuracağını gerçek dünya senaryolarıyla ele alacağız.

Neden API Loglaması Bu Kadar Önemli?

Çoğu geliştirici ya hiç log almaz ya da her şeyi loglar, ikisi de yanlış. Log almamak seni karanlıkta bırakır, her şeyi loglamak ise hem disk dolmasına hem de önemli bilgilerin gürültüde kaybolmasına yol açar.

API loglaması doğru yapıldığında sana şunları verir:

  • Hata tespiti: Hangi endpoint’te ne zaman 500 hatası geldi?
  • Performans analizi: Hangi istekler 2 saniyenin üzerinde sürüyor?
  • Güvenlik izleme: Kimin ne zaman hangi kaynağa eriştiği
  • Kapasite planlama: Trafik artışı öngörüsü
  • Debugging: Production’da ne olduğunu anlamak

Şimdi bunları nasıl yapacağımıza bakalım.

Temel Loglama Yapısı

Neyi Loglamalısın?

Her API isteği için şu bilgileri loglamanı öneririm:

  • Timestamp: ISO 8601 formatında, UTC olarak
  • Request ID: Her isteğe unique bir UUID
  • HTTP Method: GET, POST, PUT, DELETE vb.
  • Path: İstek yapılan endpoint yolu
  • Status Code: Dönen HTTP durum kodu
  • Response Time: Milisaniye cinsinden
  • Client IP: İstek yapan IP adresi
  • User Agent: İstemci bilgisi
  • User ID: Varsa authenticate olmuş kullanıcı
  • Request Size: Byte cinsinden istek boyutu
  • Response Size: Byte cinsinden yanıt boyutu

Nginx ile API Loglaması

Eğer Nginx arkasında bir API çalıştırıyorsan, custom log formatı tanımlayarak başlayabilirsin.

# /etc/nginx/nginx.conf içinde log_format tanımla

log_format api_json escape=json
  '{'
    '"timestamp":"$time_iso8601",'
    '"request_id":"$request_id",'
    '"remote_addr":"$remote_addr",'
    '"method":"$request_method",'
    '"uri":"$request_uri",'
    '"status":$status,'
    '"request_time":$request_time,'
    '"request_length":$request_length,'
    '"bytes_sent":$bytes_sent,'
    '"http_user_agent":"$http_user_agent",'
    '"http_referer":"$http_referer",'
    '"upstream_response_time":"$upstream_response_time"'
  '}';

# Virtual host config'inde kullan
server {
    listen 80;
    server_name api.example.com;
    
    access_log /var/log/nginx/api_access.log api_json;
    error_log /var/log/nginx/api_error.log warn;
    
    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header X-Request-ID $request_id;
    }
}

Bu konfigürasyon JSON formatında loglar üretir. JSON formatı tercih etmemin sebebi log aggregation araçlarıyla çok daha kolay çalışmasıdır.

Python/Flask ile Middleware Tabanlı Loglama

Uygulama katmanında loglama yapmak istiyorsan, middleware yaklaşımı en temiz yoldur.

# requirements.txt
flask>=2.0
python-json-logger>=2.0
uuid
# app/middleware/logging_middleware.py
import time
import uuid
import logging
from flask import request, g
from pythonjsonlogger import jsonlogger

# Logger yapılandırması
logger = logging.getLogger('api')
handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter(
    '%(asctime)s %(levelname)s %(name)s %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)

def setup_request_logging(app):
    @app.before_request
    def before_request():
        g.start_time = time.time()
        g.request_id = request.headers.get('X-Request-ID', str(uuid.uuid4()))
    
    @app.after_request
    def after_request(response):
        duration = (time.time() - g.start_time) * 1000  # ms cinsinden
        
        log_data = {
            'request_id': g.request_id,
            'method': request.method,
            'path': request.path,
            'status_code': response.status_code,
            'duration_ms': round(duration, 2),
            'ip': request.remote_addr,
            'user_agent': request.user_agent.string,
            'content_length': response.content_length,
        }
        
        # Yavaş istekleri özel olarak işaretle
        if duration > 1000:
            log_data['slow_request'] = True
            logger.warning('Slow API request detected', extra=log_data)
        elif response.status_code >= 500:
            logger.error('API server error', extra=log_data)
        elif response.status_code >= 400:
            logger.warning('API client error', extra=log_data)
        else:
            logger.info('API request', extra=log_data)
        
        response.headers['X-Request-ID'] = g.request_id
        return response

Bu yaklaşımla her isteği otomatik olarak loglamış olursun ve iş mantığına dokunmak zorunda kalmazsın.

Log Aggregation: ELK Stack Kurulumu

Logları dosyaya yazmak başlangıç için iyidir ama zamanla onlarca sunucudan gelen logları tek bir yerden sorgulamak istersin. Bunun için ELK Stack (Elasticsearch, Logstash, Kibana) veya daha hafif alternatif olan EFK Stack (Elasticsearch, Fluentd, Kibana) kullanabilirsin.

Filebeat ile Log Toplama

# /etc/filebeat/filebeat.yml

filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /var/log/nginx/api_access.log
    json.keys_under_root: true
    json.add_error_key: true
    fields:
      service: api-gateway
      environment: production
    fields_under_root: true

output.elasticsearch:
  hosts: ["elasticsearch:9200"]
  index: "api-logs-%{+yyyy.MM.dd}"

processors:
  - add_host_metadata: ~
  - add_cloud_metadata: ~

logging.level: warning
# Filebeat servisini başlat ve kontrol et
systemctl enable filebeat
systemctl start filebeat
systemctl status filebeat

# Log alındığını kontrol et
journalctl -u filebeat -f

Logstash Pipeline Konfigürasyonu

# /etc/logstash/conf.d/api-pipeline.conf

input {
  beats {
    port => 5044
  }
}

filter {
  if [service] == "api-gateway" {
    # Response time'a göre threshold ekle
    ruby {
      code => "
        rt = event.get('request_time').to_f * 1000
        event.set('response_time_ms', rt.round(2))
        if rt > 2000
          event.set('performance_tier', 'critical')
        elsif rt > 500
          event.set('performance_tier', 'slow')
        else
          event.set('performance_tier', 'normal')
        end
      "
    }
    
    # Bot trafiğini işaretle
    if [http_user_agent] =~ /bot|crawler|spider/i {
      mutate {
        add_field => { "is_bot" => true }
      }
    }
  }
}

output {
  elasticsearch {
    hosts => ["localhost:9200"]
    index => "api-logs-%{+YYYY.MM.dd}"
  }
}

Prometheus ile Metrik Toplama

Loglar geçmişe bakmak için iyidir ama anlık durum için metrik sistemlerine ihtiyacın var. Prometheus + Grafana ikilisi bu iş için endüstri standardı haline geldi.

API Metriklerini Expose Etme

# app/metrics.py
from prometheus_client import Counter, Histogram, Gauge, generate_latest
from flask import Response
import time

# Metrik tanımlamaları
REQUEST_COUNT = Counter(
    'api_requests_total',
    'Total API request count',
    ['method', 'endpoint', 'status_code']
)

REQUEST_LATENCY = Histogram(
    'api_request_duration_seconds',
    'API request latency',
    ['method', 'endpoint'],
    buckets=[0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0]
)

ACTIVE_REQUESTS = Gauge(
    'api_active_requests',
    'Number of active requests being processed'
)

ERROR_RATE = Counter(
    'api_errors_total',
    'Total API error count',
    ['method', 'endpoint', 'error_type']
)

def track_request_metrics(app):
    @app.before_request
    def before_request():
        from flask import g
        g.start_time = time.time()
        ACTIVE_REQUESTS.inc()
    
    @app.after_request
    def after_request(response):
        from flask import g, request
        
        ACTIVE_REQUESTS.dec()
        
        # Endpoint path'ini normalize et (ID'leri sil)
        path = request.path
        # /users/123/orders -> /users/{id}/orders
        import re
        normalized_path = re.sub(r'/d+', '/{id}', path)
        
        duration = time.time() - g.start_time
        
        REQUEST_COUNT.labels(
            method=request.method,
            endpoint=normalized_path,
            status_code=response.status_code
        ).inc()
        
        REQUEST_LATENCY.labels(
            method=request.method,
            endpoint=normalized_path
        ).observe(duration)
        
        if response.status_code >= 500:
            ERROR_RATE.labels(
                method=request.method,
                endpoint=normalized_path,
                error_type='server_error'
            ).inc()
        
        return response

@app.route('/metrics')
def metrics():
    return Response(generate_latest(), mimetype='text/plain')

Prometheus Konfigürasyonu

# /etc/prometheus/prometheus.yml

global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - /etc/prometheus/rules/*.yml

scrape_configs:
  - job_name: 'api-service'
    static_configs:
      - targets: ['api-server-1:5000', 'api-server-2:5000']
    metrics_path: /metrics
    scrape_interval: 10s

alerting:
  alertmanagers:
    - static_configs:
        - targets: ['alertmanager:9093']

Uyarı Kuralları Tanımlama

Metrikler toplamak güzel ama aksiyon almanı sağlayan uyarılar olmadan bunların bir anlamı yok.

# /etc/prometheus/rules/api-alerts.yml

groups:
  - name: api_alerts
    rules:
      # Yüksek hata oranı uyarısı
      - alert: HighErrorRate
        expr: |
          rate(api_errors_total[5m]) / rate(api_requests_total[5m]) > 0.05
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "API hata oranı yüksek"
          description: "Son 5 dakikada hata oranı %5'i aştı. Mevcut oran: {{ $value | humanizePercentage }}"
      
      # Yüksek gecikme uyarısı
      - alert: HighLatency
        expr: |
          histogram_quantile(0.95, rate(api_request_duration_seconds_bucket[5m])) > 2
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "API yanıt süresi yüksek"
          description: "P95 latency 2 saniyeyi aştı. Endpoint: {{ $labels.endpoint }}"
      
      # Trafik ani düşüşü (servis problemi olabilir)
      - alert: LowTraffic
        expr: |
          rate(api_requests_total[5m]) < 1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "API trafiği beklenenden düşük"
          description: "Son 5 dakikada dakika başı istek sayısı 1'in altına düştü"

Distributed Tracing ile Derinlemesine İzleme

Mikroservis mimarisinde bir istek onlarca servisten geçebilir. Hangi servisin bottleneck yarattığını bulmak için distributed tracing şart.

# OpenTelemetry ile tracing kurulumu
pip install opentelemetry-api opentelemetry-sdk opentelemetry-instrumentation-flask opentelemetry-exporter-jaeger
# app/tracing.py
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor

def setup_tracing(app, service_name="api-service"):
    jaeger_exporter = JaegerExporter(
        agent_host_name="jaeger",
        agent_port=6831,
    )
    
    provider = TracerProvider()
    provider.add_span_processor(BatchSpanProcessor(jaeger_exporter))
    trace.set_tracer_provider(provider)
    
    # Flask'ı otomatik olarak instrument et
    FlaskInstrumentor().instrument_app(app)
    # Dışarı yapılan HTTP isteklerini de takip et
    RequestsInstrumentor().instrument()
    
    return trace.get_tracer(service_name)

Logların Güvenli Saklanması ve Rotasyonu

Log dosyaları zamanla devasa boyutlara ulaşabilir. Logrotate ile bunu yönetebilirsin.

# /etc/logrotate.d/api-logs

/var/log/nginx/api_access.log
/var/log/nginx/api_error.log
/var/log/app/api.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    create 0640 www-data adm
    sharedscripts
    postrotate
        # Nginx'e yeni log dosyasına geçmesini söyle
        nginx -s reopen
        # Uygulama loglarını da rotate et
        systemctl kill -s USR1 gunicorn
    endscript
}
# Logrotate'i test et
logrotate --debug /etc/logrotate.d/api-logs

# Manuel olarak çalıştır
logrotate --force /etc/logrotate.d/api-logs

# Log boyutlarını kontrol et
du -sh /var/log/nginx/api_*.log
ls -lh /var/log/nginx/api_*.log*

Hassas Veri Maskeleme

API loglarında şifre, token veya kredi kartı numarası gibi hassas bilgilerin loglanmasını önlemek zorundasın. Hem GDPR hem de genel güvenlik açısından bu kritik.

# app/utils/log_sanitizer.py
import re
import json

SENSITIVE_FIELDS = [
    'password', 'passwd', 'secret', 'token', 
    'authorization', 'api_key', 'credit_card',
    'cvv', 'ssn'
]

def mask_sensitive_data(data):
    if isinstance(data, dict):
        masked = {}
        for key, value in data.items():
            if any(sensitive in key.lower() for sensitive in SENSITIVE_FIELDS):
                masked[key] = '***MASKED***'
            else:
                masked[key] = mask_sensitive_data(value)
        return masked
    elif isinstance(data, str):
        # Kredi kartı numarasını maskele
        data = re.sub(
            r'b(?:d{4}[-s]?){3}d{4}b',
            '****-****-****-****',
            data
        )
        # Bearer token'ı maskele
        data = re.sub(
            r'Bearers+[A-Za-z0-9-._~+/]+=*',
            'Bearer ***MASKED***',
            data
        )
        return data
    return data

class SanitizingFilter(logging.Filter):
    def filter(self, record):
        if hasattr(record, 'request_body'):
            try:
                body = json.loads(record.request_body)
                record.request_body = json.dumps(mask_sensitive_data(body))
            except (json.JSONDecodeError, TypeError):
                pass
        return True

Gerçek Dünya Senaryosu: Rate Limiting Tespiti

Diyelim ki bir gecenin köründe bir IP adresinden anormal istek sayısı gelmeye başladı. Bunu loglardan tespit edip otomatik olarak engelleyen bir script yazalım.

#!/bin/bash
# /usr/local/bin/detect-api-abuse.sh
# Crontab'a ekle: */5 * * * * /usr/local/bin/detect-api-abuse.sh

LOG_FILE="/var/log/nginx/api_access.log"
THRESHOLD=100        # 5 dakikada maksimum istek sayısı
BLOCK_TIME=3600      # Saniye cinsinden blok süresi
WHITELIST_FILE="/etc/nginx/api_whitelist.txt"

# Son 5 dakikadaki logları al ve IP başına say
ABUSIVE_IPS=$(tail -n 10000 "$LOG_FILE" | 
  awk -v threshold="$THRESHOLD" '
    {
      # JSON logdan remote_addr al
      match($0, /"remote_addr":"([^"]+)"/, arr)
      if (arr[1] != "") {
        count[arr[1]]++
      }
    }
    END {
      for (ip in count) {
        if (count[ip] > threshold) {
          print ip, count[ip]
        }
      }
    }
  ')

if [ -z "$ABUSIVE_IPS" ]; then
    echo "$(date): Anormal aktivite tespit edilmedi" >> /var/log/api-security.log
    exit 0
fi

while IFS=' ' read -r ip count; do
    # Whitelist kontrolü
    if grep -q "^${ip}$" "$WHITELIST_FILE" 2>/dev/null; then
        echo "$(date): $ip whitelist'te, atlanıyor (istek sayısı: $count)" >> /var/log/api-security.log
        continue
    fi
    
    # Zaten bloklanmış mı?
    if iptables -L INPUT -n | grep -q "$ip"; then
        echo "$(date): $ip zaten bloklanmış" >> /var/log/api-security.log
        continue
    fi
    
    # IP'yi engelle
    iptables -A INPUT -s "$ip" -j DROP
    echo "$(date): $ip bloklandı (istek sayısı: $count)" >> /var/log/api-security.log
    
    # Slack'e bildirim gönder
    curl -s -X POST "$SLACK_WEBHOOK_URL" 
      -H 'Content-type: application/json' 
      -d "{"text":"Güvenlik Uyarısı: $ip adresi 5 dakikada $count istek attı ve engellendi."}" 
      > /dev/null
    
    # Geçici blok için at-remove işlemi planla
    echo "iptables -D INPUT -s $ip -j DROP" | at "now + $(( BLOCK_TIME / 60 )) minutes" 2>/dev/null
    
done <<< "$ABUSIVE_IPS"

Grafana Dashboard Kurulumu

Prometheus verilerini görselleştirmek için Grafana’da birkaç kritik panel oluşturman gerekiyor. Bu paneller için kullanabileceğin temel PromQL sorguları:

# Saniye başına istek sayısı (RPS)
rate(api_requests_total[1m])

# Hata oranı yüzdesi
100 * rate(api_errors_total[5m]) / rate(api_requests_total[5m])

# P50, P95, P99 latency
histogram_quantile(0.50, rate(api_request_duration_seconds_bucket[5m]))
histogram_quantile(0.95, rate(api_request_duration_seconds_bucket[5m]))
histogram_quantile(0.99, rate(api_request_duration_seconds_bucket[5m]))

# En yavaş endpoint'ler
topk(10, histogram_quantile(0.95, 
  rate(api_request_duration_seconds_bucket[5m])) 
  by (endpoint))

# Endpoint başına hata oranı
rate(api_errors_total[5m]) by (endpoint, error_type)
# Grafana'yı Docker ile hızlıca kur
docker run -d 
  --name grafana 
  -p 3000:3000 
  -e GF_SECURITY_ADMIN_PASSWORD=secretpassword 
  -v grafana-storage:/var/lib/grafana 
  grafana/grafana:latest

# Prometheus datasource'u API üzerinden ekle
curl -X POST http://admin:secretpassword@localhost:3000/api/datasources 
  -H 'Content-Type: application/json' 
  -d '{
    "name": "Prometheus",
    "type": "prometheus",
    "url": "http://prometheus:9090",
    "access": "proxy",
    "isDefault": true
  }'

Log Analizi için Pratik Komutlar

Bir sorun çıktığında logları hızlıca analiz etmek için kullandığım komutlar:

# Son 1 saatin en çok 500 dönen endpoint'lerini bul
cat /var/log/nginx/api_access.log | 
  python3 -c "
import sys, json
from collections import Counter
errors = Counter()
for line in sys.stdin:
    try:
        log = json.loads(line)
        if log.get('status', 0) >= 500:
            errors[log.get('uri', 'unknown')] += 1
    except:
        pass
for uri, count in errors.most_common(10):
    print(f'{count:6d}  {uri}')
"

# Ortalama response time'a göre endpoint sırala
cat /var/log/nginx/api_access.log | 
  python3 -c "
import sys, json
from collections import defaultdict
times = defaultdict(list)
for line in sys.stdin:
    try:
        log = json.loads(line)
        uri = log.get('uri', '').split('?')[0]
        rt = float(log.get('request_time', 0))
        times[uri].append(rt)
    except:
        pass
results = [(sum(v)/len(v)*1000, k) for k, v in times.items() if len(v) > 10]
for avg_ms, uri in sorted(results, reverse=True)[:10]:
    print(f'{avg_ms:8.1f}ms  {uri}')
"

# Belirli bir IP'nin son 100 isteğini göster
grep '"remote_addr":"1.2.3.4"' /var/log/nginx/api_access.log | tail -100 | 
  python3 -m json.tool | grep -E '"uri"|"status"|"request_time"'

Sonuç

API loglama ve izleme sistemi kurmak bir defalık yapılan bir iş değil, sürekli iyileştirilen bir süreç. Başlangıç olarak şu sıralamayı öneririm:

  • İlk adım: Nginx veya uygulama katmanında JSON formatında yapılandırılmış loglama kur
  • İkinci adım: Logları merkezi bir sisteme (ELK veya basitçe bir Elasticsearch cluster) gönder
  • Üçüncü adım: Prometheus metriklerini topla ve Grafana’da görselleştir
  • Dördüncü adım: Kritik eşikler için uyarı kuralları tanımla
  • Beşinci adım: Mikroservis kullanıyorsan distributed tracing ekle

En önemlisi, hassas verileri loglamama konusunda titiz ol. Bir log sistemi hem debugging aracın hem de potansiyel bir güvenlik açığın olabilir. Logrotate ve retention policy’leri unutma; 30 günlük log genellikle yeterlidir, bunun ötesini compressed arşiv olarak sakla.

Prodüksiyonda kör uçmak yerine, veriye dayalı kararlar vermek sysadmin’in işini hem kolaylaştırır hem de gece uyku uyutmayan “ne oldu acaba” sorularının önüne geçer.

Bir yanıt yazın

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