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.
