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 443ile port açık mı? - SSL sertifikası:
openssl s_client -connect webhooks.example.com:443ile geçerli mi? - İmza doğrulama: Shared secret doğru mu? Timestamp toleransı ne?
- Content-Type: Gönderen
application/jsongönderiyor mu, siztext/plainmi bekliyorsunuz? - Payload boyutu:
client_max_body_sizelimitini 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.
