Webhook Loglama ve Hata Ayıklama: Kapsamlı Rehber

Webhook entegrasyonlarında en sinir bozucu şey şu: bir şeyler çalışmıyor, ama neden çalışmadığını bilmiyorsun. Karşı taraf “biz gönderdik” diyor, senin sistemin “biz almadık” diyor. Ortada bir kara delik var ve sen onu bulmak zorundasm. İşte bu yüzden webhook loglama ve hata ayıklama, entegrasyon işlerinin belki de en kritik parçası.

Bu yazıda gerçek dünya senaryoları üzerinden webhook loglamanın nasıl yapılandırılacağını, hataların nasıl yakalanıp analiz edileceğini ve production ortamında karşılaşılan tipik sorunların nasıl çözüleceğini ele alacağız.

Webhook Loglamanın Temelleri

Webhook aldığında loglaman gereken minimum bilgi seti şunlardır:

  • Timestamp: Tam olarak ne zaman geldi, milisaniye hassasiyetiyle
  • Source IP: İsteği kim gönderdi
  • HTTP Headers: Content-Type, User-Agent, imza başlıkları
  • HTTP Method: POST mu, GET mi
  • Request Body: Ham payload, işlenmemiş hali
  • Response Code: Senin verdiğin yanıt
  • Processing Time: Kaç milisaniyede işledin
  • Error Details: Varsa hata mesajı ve stack trace

Bunların hepsini loglamıyorsan, bir sorun çıktığında elinde hiçbir şey olmayacak.

Nginx ile Webhook Loglama

Eğer webhook endpoint’ini Nginx arkasına koyduysan, önce Nginx seviyesinde loglama yapılandırın. Bu size uygulama katmanına bile ulaşmadan ne geldiğini gösterir.

# /etc/nginx/nginx.conf veya ilgili site config dosyası

http {
    log_format webhook_detailed '$remote_addr - $remote_user [$time_local] '
                                '"$request" $status $body_bytes_sent '
                                '"$http_referer" "$http_user_agent" '
                                'rt=$request_time '
                                'ua="$upstream_addr" '
                                'us="$upstream_status" '
                                'ut="$upstream_response_time" '
                                'body="$request_body"';

    server {
        listen 443 ssl;
        server_name webhooks.example.com;

        # Webhook logları ayrı dosyaya
        access_log /var/log/nginx/webhook_access.log webhook_detailed;
        error_log /var/log/nginx/webhook_error.log warn;

        location /webhook/ {
            # Request body'yi loglamak için buffer ayarı
            client_body_buffer_size 1m;
            client_max_body_size 5m;

            proxy_pass http://localhost:8080;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

Bu yapılandırmayla Nginx seviyesinde her isteği ayrı bir log dosyasına yazıyorsunuz. Production’da $request_body loglamak dikkatli yapılması gereken bir şey, hassas veri içeriyorsa maskeleme yapmanız gerekebilir.

Python ile Webhook Receiver ve Structured Logging

Gerçek hayatta en çok karşılaşılan senaryo: bir Python Flask/FastAPI uygulamasının webhook alması. Aşağıda production’da kullanabileceğiniz bir örnek var.

# webhook_receiver.py
import json
import logging
import time
import hashlib
import hmac
from datetime import datetime
from functools import wraps
from flask import Flask, request, jsonify

app = Flask(__name__)

# Structured JSON logging
class WebhookFormatter(logging.Formatter):
    def format(self, record):
        log_data = {
            "timestamp": datetime.utcnow().isoformat(),
            "level": record.levelname,
            "message": record.getMessage(),
            "module": record.module,
        }
        if hasattr(record, 'webhook_data'):
            log_data.update(record.webhook_data)
        return json.dumps(log_data, ensure_ascii=False)

handler = logging.FileHandler('/var/log/webhooks/app.log')
handler.setFormatter(WebhookFormatter())
logger = logging.getLogger('webhook')
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

def log_webhook(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        start_time = time.time()
        request_id = hashlib.md5(
            f"{time.time()}{request.remote_addr}".encode()
        ).hexdigest()[:8]

        # Gelen isteği logla
        logger.info(
            "Webhook received",
            extra={
                "webhook_data": {
                    "request_id": request_id,
                    "source_ip": request.remote_addr,
                    "method": request.method,
                    "path": request.path,
                    "headers": dict(request.headers),
                    "content_type": request.content_type,
                    "content_length": request.content_length,
                }
            }
        )

        try:
            result = f(*args, request_id=request_id, **kwargs)
            processing_time = (time.time() - start_time) * 1000
            logger.info(
                "Webhook processed successfully",
                extra={
                    "webhook_data": {
                        "request_id": request_id,
                        "processing_time_ms": round(processing_time, 2),
                        "status": "success"
                    }
                }
            )
            return result
        except Exception as e:
            processing_time = (time.time() - start_time) * 1000
            logger.error(
                f"Webhook processing failed: {str(e)}",
                extra={
                    "webhook_data": {
                        "request_id": request_id,
                        "processing_time_ms": round(processing_time, 2),
                        "status": "error",
                        "error_type": type(e).__name__,
                        "error_message": str(e)
                    }
                },
                exc_info=True
            )
            return jsonify({"error": "Internal server error"}), 500

    return decorated

@app.route('/webhook/github', methods=['POST'])
@log_webhook
def github_webhook(request_id=None):
    payload = request.get_json()
    # İşleme logic'i buraya
    return jsonify({"status": "ok", "request_id": request_id}), 200

Bu decorator yaklaşımı her webhook endpoint’inizi otomatik olarak logluyor ve hata durumunda tam stack trace’i kaydediyor.

Webhook İmza Doğrulama ve Loglama

Gerçek bir production senaryosunda şunu yaşarsınız: birisi webhook endpoint’inize sahte istek atmaya çalışır veya ağda paket bozulması olur. GitHub, Stripe, Shopify gibi servisler webhook’larını imzalıyor, bu imzaları hem doğrulamanız hem de loglamanız gerekiyor.

# imza dogrulama scripti - bash ile test etmek icin
#!/bin/bash

WEBHOOK_SECRET="your_secret_here"
PAYLOAD='{"action":"push","ref":"refs/heads/main"}'
TIMESTAMP=$(date +%s)

# GitHub style HMAC-SHA256 imza
SIGNATURE=$(echo -n "v0:${TIMESTAMP}:${PAYLOAD}" | 
    openssl dgst -sha256 -hmac "${WEBHOOK_SECRET}" | 
    awk '{print $2}')

echo "Timestamp: ${TIMESTAMP}"
echo "Signature: v0=${SIGNATURE}"

# Test isteği gönder
curl -X POST https://webhooks.example.com/webhook/github 
  -H "Content-Type: application/json" 
  -H "X-Hub-Signature-256: sha256=${SIGNATURE}" 
  -H "X-GitHub-Event: push" 
  -H "X-Request-ID: test-$(date +%s)" 
  -d "${PAYLOAD}" 
  -v 2>&1 | tee /tmp/webhook_test.log

Log Rotasyonu ve Arşivleme

Webhook logları hızla büyür. Özellikle yüksek trafikli bir entegrasyonda günde gigabyte’larca log üretebilirsiniz. Logrotate ile bunu yönetmek zorundasınız.

# /etc/logrotate.d/webhooks
/var/log/webhooks/*.log {
    hourly
    rotate 168
    compress
    delaycompress
    missingok
    notifempty
    create 0640 www-data adm
    sharedscripts
    postrotate
        # Nginx'i log dosyasını yeniden açmaya zorla
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 $(cat /var/run/nginx.pid)
        fi
        # Uygulama loglarını da flush et
        if [ -f /var/run/webhook_app.pid ]; then
            kill -HUP $(cat /var/run/webhook_app.pid)
        fi
    endscript
}

Saatlik rotasyon ve 168 saat (7 gün) saklama politikası çoğu senaryo için yeterli. Uyumluluk gereksinimleri varsa rotate değerini ve arşiv hedefini buna göre ayarlayın.

Hata Ayıklama: Gerçek Dünya Senaryoları

Senaryo 1: “Webhook Geliyor Ama İşlenmiyor”

Bu en yaygın sorun. Önce Nginx loglarına bakın, istek geliyor mu?

# Son 100 webhook isteğini görüntüle
tail -100 /var/log/nginx/webhook_access.log | 
    grep "/webhook/" | 
    awk '{print $1, $7, $9}'

# 4xx ve 5xx hataları filtrele
grep " [45][0-9][0-9] " /var/log/nginx/webhook_access.log | 
    tail -50

# Belirli bir IP'den gelen istekleri analiz et
grep "185.199.108" /var/log/nginx/webhook_access.log | 
    awk '{print $9}' | sort | uniq -c | sort -rn

# Son 1 saatin istatistikleri
awk -v d="$(date '+%d/%b/%Y:%H' -d '1 hour ago')" 
    '$4 > "["d {print $9}' 
    /var/log/nginx/webhook_access.log | 
    sort | uniq -c | sort -rn

Senaryo 2: Payload Parse Hatası

JSON parse hatalarını yakalamak için özel bir log analiz scripti:

#!/bin/bash
# webhook_error_analyzer.sh

LOG_FILE="/var/log/webhooks/app.log"
ERROR_LOG="/tmp/webhook_errors_$(date +%Y%m%d).log"

echo "=== Webhook Hata Analizi - $(date) ===" > $ERROR_LOG

# JSON parse hatalarını say
echo -e "n--- JSON Parse Hataları ---" >> $ERROR_LOG
grep -c "JSONDecodeError|json.decoder" $LOG_FILE >> $ERROR_LOG

# En cok hata veren source IP'ler
echo -e "n--- Hata Veren Source IP'ler ---" >> $ERROR_LOG
grep '"status": "error"' $LOG_FILE | 
    python3 -c "
import sys, json
from collections import Counter
ips = []
for line in sys.stdin:
    try:
        data = json.loads(line)
        if 'source_ip' in data:
            ips.append(data['source_ip'])
    except:
        pass
for ip, count in Counter(ips).most_common(10):
    print(f'{count:5d}  {ip}')
" >> $ERROR_LOG

# Ortalama processing time
echo -e "n--- Processing Time İstatistikleri ---" >> $ERROR_LOG
grep 'processing_time_ms' $LOG_FILE | 
    python3 -c "
import sys, json
times = []
for line in sys.stdin:
    try:
        data = json.loads(line)
        if 'processing_time_ms' in data:
            times.append(float(data['processing_time_ms']))
    except:
        pass
if times:
    print(f'Ortalama: {sum(times)/len(times):.2f}ms')
    print(f'Maksimum: {max(times):.2f}ms')
    print(f'Minimum: {min(times):.2f}ms')
    print(f'Toplam istek: {len(times)}')
" >> $ERROR_LOG

cat $ERROR_LOG

Senaryo 3: Timeout Sorunları

Webhook gönderen taraf timeout alıyorsa, processing time loglarına bakın. Webhook receiver’ınız hızlı yanıt verip işi arka plana atmak zorunda.

# Son 24 saatte 5 saniyeyi gecen istekler
grep 'processing_time_ms' /var/log/webhooks/app.log | 
    python3 -c "
import sys, json
slow_requests = []
for line in sys.stdin:
    try:
        data = json.loads(line)
        if data.get('processing_time_ms', 0) > 5000:
            slow_requests.append({
                'time': data.get('timestamp'),
                'ms': data.get('processing_time_ms'),
                'id': data.get('request_id')
            })
    except:
        pass
for r in sorted(slow_requests, key=lambda x: x['ms'], reverse=True)[:20]:
    print(f"{r['time']} | {r['ms']:.0f}ms | ID: {r['id']}")
print(f'Toplam yavas istek: {len(slow_requests)}')
"

Webhook Replay ve Test Altyapısı

Production’da bir webhook’u tekrar test etmeniz gereken durumlar oluyor. Bunun için gelen payload’ları kaydetmeniz ve tekrar oynatabilmeniz gerekiyor.

# /usr/local/bin/webhook_replay.sh
#!/bin/bash

# Kaydedilmis bir webhook'u tekrar gonder
WEBHOOK_DIR="/var/log/webhooks/payloads"
TARGET_URL="${1:-http://localhost:8080/webhook/test}"
PAYLOAD_FILE="$2"

if [ -z "$PAYLOAD_FILE" ]; then
    echo "Kullanim: $0 <url> <payload_file>"
    echo ""
    echo "Kayitli payload'lar:"
    ls -lt $WEBHOOK_DIR/*.json 2>/dev/null | head -20
    exit 1
fi

if [ ! -f "$PAYLOAD_FILE" ]; then
    echo "Hata: $PAYLOAD_FILE bulunamadi"
    exit 1
fi

# Payload metadata'sini oku
METADATA_FILE="${PAYLOAD_FILE%.json}.meta"
EVENT_TYPE="push"
ORIGINAL_IP="unknown"

if [ -f "$METADATA_FILE" ]; then
    EVENT_TYPE=$(grep "event_type" $METADATA_FILE | cut -d= -f2)
    ORIGINAL_IP=$(grep "source_ip" $METADATA_FILE | cut -d= -f2)
fi

echo "Replay basliyor..."
echo "Target: $TARGET_URL"
echo "Payload: $PAYLOAD_FILE"
echo "Event: $EVENT_TYPE"
echo "Original IP: $ORIGINAL_IP"
echo ""

RESPONSE=$(curl -s -w "n%{http_code}n%{time_total}" 
    -X POST "$TARGET_URL" 
    -H "Content-Type: application/json" 
    -H "X-GitHub-Event: $EVENT_TYPE" 
    -H "X-Replay: true" 
    -H "X-Original-IP: $ORIGINAL_IP" 
    --data-binary @"$PAYLOAD_FILE")

HTTP_CODE=$(echo "$RESPONSE" | tail -2 | head -1)
RESPONSE_TIME=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | head -n -2)

echo "HTTP Status: $HTTP_CODE"
echo "Response Time: ${RESPONSE_TIME}s"
echo "Response Body: $BODY"

# Sonucu logla
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) REPLAY status=$HTTP_CODE time=${RESPONSE_TIME}s file=$PAYLOAD_FILE" 
    >> /var/log/webhooks/replay.log

Alerting: Kritik Hataları Anında Öğrenin

Log dosyalarını pasif izlemek yetmez. Belirli eşiklerin aşılmasında veya kritik hataların oluşmasında anında haberdar olmak için basit bir monitoring scripti kurun.

#!/bin/bash
# /usr/local/bin/webhook_monitor.sh
# Cron: */5 * * * * /usr/local/bin/webhook_monitor.sh

SLACK_WEBHOOK="https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
LOG_FILE="/var/log/webhooks/app.log"
STATE_FILE="/tmp/webhook_monitor_state"
ERROR_THRESHOLD=10
ALERT_COOLDOWN=300  # 5 dakika

# Son kontrol zamanini oku
LAST_CHECK=$(cat $STATE_FILE 2>/dev/null || echo "0")
NOW=$(date +%s)

# Son 5 dakikanin hatalarini say
ERROR_COUNT=$(awk -v since="$(date -u -d @$LAST_CHECK '+%Y-%m-%dT%H:%M')" 
    '$0 > since && /"level": "ERROR"/' $LOG_FILE | wc -l)

echo $NOW > $STATE_FILE

if [ $ERROR_COUNT -ge $ERROR_THRESHOLD ]; then
    # Cooldown kontrolu
    LAST_ALERT=$(cat /tmp/webhook_alert_time 2>/dev/null || echo "0")
    if [ $((NOW - LAST_ALERT)) -lt $ALERT_COOLDOWN ]; then
        exit 0
    fi

    echo $NOW > /tmp/webhook_alert_time

    # Slack bildirimi
    MESSAGE="*Webhook Alert!* Son 5 dakikada ${ERROR_COUNT} hata tespit edildi. Sunucu: $(hostname)"

    curl -s -X POST "$SLACK_WEBHOOK" 
        -H "Content-Type: application/json" 
        -d "{"text": "$MESSAGE"}" > /dev/null

    echo "$(date): Alert gonderildi - $ERROR_COUNT hata" >> /var/log/webhooks/monitor.log
fi

# 5xx hataları Nginx logunda kontrol et
NGINX_5XX=$(awk -v since="$(date '+%d/%b/%Y:%H:%M' -d '5 minutes ago')" 
    '$4 > "["since && / 5[0-9][0-9] /' 
    /var/log/nginx/webhook_access.log | wc -l)

if [ $NGINX_5XX -gt 5 ]; then
    echo "$(date): Nginx 5xx uyarisi - $NGINX_5XX hata" >> /var/log/webhooks/monitor.log
fi

ELK Stack ile Merkezi Webhook Log Yönetimi

Birden fazla sunucuda webhook alıyorsanız, logları merkezi bir yerde toplamak hayat kurtarır. Filebeat ile Elasticsearch’e göndermek için temel yapılandırma:

# /etc/filebeat/filebeat.yml
filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /var/log/webhooks/*.log
    fields:
      log_type: webhook
      environment: production
      server: "${HOSTNAME}"
    fields_under_root: true
    json.keys_under_root: true
    json.add_error_key: true
    multiline.pattern: '^{'
    multiline.negate: true
    multiline.match: after

processors:
  - add_host_metadata:
      when.not.contains.tags: forwarded
  - drop_fields:
      fields: ["agent.ephemeral_id", "agent.hostname"]

output.elasticsearch:
  hosts: ["elasticsearch:9200"]
  index: "webhooks-%{+yyyy.MM.dd}"
  template.name: "webhooks"
  template.pattern: "webhooks-*"

logging.level: warning
logging.to_files: true
logging.files:
  path: /var/log/filebeat
  name: filebeat
  keepfiles: 7

Bu yapılandırmayla tüm sunuculardan gelen webhook logları Elasticsearch’te birleşiyor ve Kibana üzerinden gerçek zamanlı analiz yapabiliyorsunuz. Hangi endpoint’in en çok hata verdiğini, hangi saatlerde yoğunluğun arttığını görsel olarak takip edebilirsiniz.

Pratik Hata Ayıklama Checklist

Bir webhook çalışmıyor diye geldiğinizde sırayla kontrol etmeniz gerekenler:

  • Nginx erişim logları: İstek sunucuya ulaşıyor mu? HTTP 200 mü dönüyor?
  • Firewall kuralları: iptables -L -n | grep 443 ile port açık mı?
  • SSL sertifikası: openssl s_client -connect webhooks.example.com:443 ile geçerli mi?
  • İmza doğrulama: Shared secret doğru mu? Timestamp toleransı ne?
  • Content-Type: Gönderen application/json gönderiyor mu, siz text/plain mi bekliyorsunuz?
  • Payload boyutu: client_max_body_size limitini aşıyor mu?
  • Processing time: 30 saniyeyi geçiyor mu, karşı taraf timeout alıyor mu?
  • Retry mekanizması: Karşı taraf kaç kez retry yapıyor, her birini logluyorum mu?
  • Duplicate handling: Aynı payload iki kez işleniyor mu, idempotency var mı?

Sonuç

Webhook loglama göz ardı edilen ama kriz anında değerini ortaya koyan bir yatırım. Düzgün yapılandırılmış bir loglama altyapısıyla, “neden çalışmıyor” sorusuna dakikalar içinde yanıt verebilirsiniz. Structured JSON loglama, ayrı log dosyaları, düzgün log rotasyonu ve basit bir alert mekanizması kurmanız için harcayacağınız birkaç saat, sonraki troubleshooting senaryolarında size günlerce zaman kazandırır.

En önemli prensip şu: her webhook isteğini, başarılı olsun ya da olmasın, logla. Sadece hataları loglamak yetmez çünkü başarılı görünen bir istek arka planda yanlış işlenmiş olabilir. Request ID kullanımı, tüm log satırlarını tek bir istek etrafında gruplamanızı sağlar ve bir problemi izlerken inanılmaz kolaylık sunar. Bu altyapıyı bir kez kurun, sonra rahat edin.

Bir yanıt yazın

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