Caddy kullanmaya başladığınızda ilk etkilendiğiniz şey ne olduğunu hatırlıyorum: otomatik HTTPS, sade konfigürasyon dosyası ve varsayılan olarak her şeyin “sadece çalışması”. Ama production ortamına geçtiğinizde log yönetimi konusu ciddi bir gereksinim haline geliyor. Kim erişti, ne zaman erişti, hangi hata döndü, response süresi ne kadardı? Bunlar olmadan sunucunuzu kör bir şekilde yönetiyorsunuz demektir. Bu yazıda Caddy’nin log sistemini, access log yapılandırmasını ve JSON formatındaki log çıktısını gerçek dünya senaryolarıyla birlikte ele alacağız.
Caddy’nin Log Sistemi Hakkında Temel Bilgiler
Caddy, diğer web sunucularından farklı olarak yapılandırılmamış metin logları yerine yapılandırılmış JSON loglarını varsayılan format olarak kullanır. Bu başta garip gelebilir, özellikle nginx veya Apache’den geçenler için klasik combined log formatını özlemek kaçınılmaz. Ama JSON loglarıyla bir süre çalıştıktan sonra neden bu kararın alındığını anlıyorsunuz: log analiz araçları, ELK stack, Grafana Loki veya herhangi bir SIEM aracıyla entegrasyon son derece kolaylaşıyor.
Caddy’nin iki farklı log kategorisi var:
- Admin/Error logları: Caddy’nin kendi işlem logları, startup mesajları, konfigürasyon hataları
- Access logları: HTTP isteklerini kaydeden, kim ne zaman neye erişti bilgilerini içeren loglar
Bu iki kategori birbirinden bağımsız olarak yapılandırılabilir ve bu esneklik production ortamlarında çok işe yarıyor.
Caddyfile ile Temel Access Log Yapılandırması
En basit haliyle access log aktifleştirmek tek satır iş:
example.com {
log
root * /var/www/html
file_server
}
Bu yapılandırma logları stdout’a yazar. Ama production ortamında bunu bir dosyaya yönlendirmek istiyorsunuz. Dosyaya yazmak için:
example.com {
log {
output file /var/log/caddy/access.log
}
root * /var/www/html
file_server
}
Caddy’yi systemd ile çalıştırıyorsanız /var/log/caddy/ dizininin caddy kullanıcısına ait olduğundan emin olun:
sudo mkdir -p /var/log/caddy
sudo chown caddy:caddy /var/log/caddy
sudo chmod 755 /var/log/caddy
Log Format Seçenekleri
Caddy üç farklı log formatı destekliyor:
- json: Yapılandırılmış JSON çıktısı (varsayılan)
- console: İnsan tarafından okunabilir renkli format, development için ideal
- common: Apache/nginx tarzı combined log formatı
JSON Format ile Detaylı Yapılandırma
JSON formatı production için tavsiye edilen yaklaşım. Bir log satırı şu bilgileri içeriyor: timestamp, istek metodu, URI, protokol, durum kodu, response boyutu, süre ve daha fazlası. Tam kontrollü bir JSON log yapılandırması şöyle görünür:
example.com {
log {
output file /var/log/caddy/access.log {
roll_size 100mb
roll_keep 10
roll_keep_for 720h
}
format json {
time_format iso8601
time_local
}
level INFO
}
root * /var/www/html
file_server
}
Bu yapılandırmada:
- roll_size 100mb: Log dosyası 100 MB’a ulaşınca yeni dosya açılır
- roll_keep 10: Maksimum 10 adet rotasyon dosyası saklanır
- roll_keep_for 720h: 30 gün (720 saat) boyunca loglar saklanır
- time_format iso8601: Zaman damgası ISO 8601 formatında yazılır
- time_local: Sunucunun yerel saat dilimini kullanır
Console Format ile Development Ortamı
Development yaparken JSON loglarını okumak zahmetli. Console formatı tam burada devreye giriyor:
localhost:8080 {
log {
output stderr
format console
level DEBUG
}
reverse_proxy localhost:3000
}
stderr çıktısı sayesinde uygulama loglarından ayrı görüntüleyebilirsiniz. level DEBUG ise her şeyi, yani başarılı istekler dahil tüm detayları gösterir.
Common Format ile Legacy Uyumluluk
Mevcut log analiz araçlarınız Apache formatını bekliyorsa common formatı kullanabilirsiniz. Logstash pipeline’larınızı veya GoAccess gibi araçlarınızı değiştirmenize gerek kalmaz:
example.com {
log {
output file /var/log/caddy/access.log
format single_field common_log
}
}
Ama uzun vadede JSON’a geçişi tavsiye ederim, analiz imkanları çok daha zengin.
Birden Fazla Site için Log Yapılandırması
Aynı Caddy instance üzerinde birden fazla site çalıştırıyorsanız her biri için ayrı log dosyası tanımlamak hem yönetimi kolaylaştırır hem de log analizi yaparken karışıklığı önler:
blog.example.com {
log {
output file /var/log/caddy/blog-access.log {
roll_size 50mb
roll_keep 7
}
format json
}
root * /var/www/blog
file_server
}
api.example.com {
log {
output file /var/log/caddy/api-access.log {
roll_size 200mb
roll_keep 14
roll_keep_for 2160h
}
format json {
time_format unix_seconds_float
}
}
reverse_proxy localhost:4000
}
static.example.com {
log {
output discard
}
root * /var/www/static
file_server
}
Bu yapılandırmada ilginç bir detay var: static.example.com için logları tamamen devre dışı bıraktık (output discard). Statik asset sunucusu genellikle binlerce küçük istek alır ve bunları loglamak gereksiz disk kullanımına yol açar. CDN arkasındaki bir sunucu için bu yaklaşım disk I/O’yu ciddi ölçüde azaltır.
Global Log Yapılandırması
Tüm sitelere uygulanacak ortak bir log ayarı varsa bunu global blok içinde tanımlayabilirsiniz:
{
log default {
output file /var/log/caddy/caddy.log {
roll_size 50mb
roll_keep 5
}
format json
level WARN
}
}
example.com {
log {
output file /var/log/caddy/access.log
format json
}
root * /var/www/html
file_server
}
Global blokta tanımlanan log, Caddy’nin sistem loglarını (error logları dahil) yakalar. Site bazlı log direktifleri ise sadece o siteye gelen HTTP isteklerini loglar. İkisini karıştırmamak önemli.
JSON Log Çıktısını Anlama ve Analiz Etme
Bir JSON log satırı şuna benzer bir yapıda gelir. jq ile güzel bastıralım:
tail -f /var/log/caddy/access.log | jq '.'
Log alanlarından en sık kullanacaklarınız:
- ts: Unix timestamp
- request.method: HTTP metodu (GET, POST vb.)
- request.uri: İstek yapılan yol
- request.remote_ip: İstemci IP adresi
- request.host: Host başlığı
- status: HTTP durum kodu
- size: Response boyutu (byte)
- duration: İstek süresi (saniye)
- request.headers.User-Agent: Kullanıcı ajanı
jq ile Pratik Log Analizi
Gerçek dünyada en sık ihtiyaç duyduğunuz sorguları paylaşayım:
# Son 1 saatteki 5xx hatalarını listele
cat /var/log/caddy/access.log | jq 'select(.status >= 500)' | jq '{zaman: .ts, method: .request.method, uri: .request.uri, status: .status}'
# En yavaş 10 isteği bul (saniye cinsinden)
cat /var/log/caddy/access.log | jq -r '[.duration, .request.uri] | @tsv' | sort -rn | head -10
# Belirli bir IP'nin tüm isteklerini filtrele
cat /var/log/caddy/access.log | jq 'select(.request.remote_ip == "192.168.1.100")'
# Saatlik istek sayısını hesapla
cat /var/log/caddy/access.log | jq -r '.ts | todate | .[0:13]' | sort | uniq -c | sort -rn
Bu sorgular günlük operasyonlarda çok işe yarıyor. Özellikle bir saldırı veya anomali şüphesi olduğunda hızlıca durumu değerlendirmenizi sağlıyor.
Log Filtreleme ve Belirli İstekleri Hariç Tutma
Sağlık kontrol endpoint’leri, monitoring araçlarının sorguları veya belirli bot trafiğini loglardan çıkarmak isteyebilirsiniz. Caddy bunu log direktifi içinde filtreler aracılığıyla destekliyor:
api.example.com {
@healthcheck {
path /health /ping /status
}
@monitoring {
remote_ip 10.0.0.50 10.0.0.51
}
log {
output file /var/log/caddy/api-access.log {
roll_size 100mb
roll_keep 10
}
format json
except @healthcheck
}
reverse_proxy localhost:8080
}
except direktifi ile belirli matcher grubuna uyan istekleri loglama dışında tutabiliyorsunuz. Kubernetes ortamında Liveness/Readiness probe’larınız dakikada onlarca istek yapıyorsa bu özellik log dosyalarınızın gereksiz şişmesini önler.
Hassas Veri Maskeleme
Log dosyalarında Authorization başlıkları, cookie değerleri veya şifreler gibi hassas bilgilerin görünmesi güvenlik açısından ciddi risk oluşturur. KVKK ve GDPR uyumu açısından da log’larda kişisel veri bulunmaması gerekebilir. Caddy’de log filtresi ile bu değerleri maskeleyebilirsiniz:
example.com {
log {
output file /var/log/caddy/access.log
format json {
time_format iso8601
}
format filter {
wrap json
fields {
request.headers.Authorization replace {
replace "Bearer [A-Za-z0-9._-]+" "Bearer [REDACTED]"
}
request.headers.Cookie delete
request.uri query {
replace password "[REDACTED]"
replace token "[REDACTED]"
replace api_key "[REDACTED]"
}
}
}
}
reverse_proxy localhost:3000
}
Bu yapılandırma:
- Authorization başlığındaki Bearer token’ı maskeler
- Cookie başlığını tamamen siler
- URL query string içindeki hassas parametreleri maskeler
Production ortamında bu tip yapılandırmaları uygulamak hem güvenlik hem de compliance açısından önemli. Pentester’ların ilk baktığı yerlerden biri log dosyalarıdır.
Caddy API ile Dinamik Log Yapılandırması
Caddy’nin admin API’si sayesinde sunucuyu yeniden başlatmadan log yapılandırmasını değiştirebilirsiniz. Bu özellikle canlı production ortamlarında debug yaparken çok değerli:
# Mevcut log yapılandırmasını görüntüle
curl -s http://localhost:2019/config/ | jq '.apps.http.servers'
# Log seviyesini dinamik olarak DEBUG'a çek (örnek)
curl -X PATCH http://localhost:2019/config/logging/logs/default
-H "Content-Type: application/json"
-d '{"level": "DEBUG"}'
# Yapılandırmayı dosyadan yükle
curl -X POST http://localhost:2019/load
-H "Content-Type: text/caddyfile"
--data-binary @/etc/caddy/Caddyfile
Admin API varsayılan olarak localhost:2019 üzerinde dinliyor ve dışarıdan erişime kapalı. Bunu değiştirmeyin, güvenlik riski oluşturur.
Systemd Journal ile Entegrasyon
Caddy’yi systemd servisi olarak çalıştırıyorsanız logları doğrudan journal’a da yönlendirebilirsiniz:
{
log default {
output stderr
format console
level INFO
}
}
example.com {
log {
output stderr
format json
}
file_server
}
Bu yapılandırmayla journalctl komutlarını kullanabilirsiniz:
# Caddy loglarını canlı izle
sudo journalctl -u caddy -f
# Son 1 saatin loglarını görüntüle
sudo journalctl -u caddy --since "1 hour ago"
# Sadece hata seviyesindeki logları görüntüle
sudo journalctl -u caddy -p err
# Belirli bir zaman aralığını görüntüle
sudo journalctl -u caddy --since "2024-01-15 09:00:00" --until "2024-01-15 10:00:00"
Journal entegrasyonu, logları merkezi bir yerde toplamak ve log rotation konusunu systemd’ye bırakmak isteyenler için ideal. Ama büyük trafik alan sitelerde journal performansı düşebilir, bu durumda dosyaya yazma daha mantıklı.
Grafana Loki ile Log Toplama
Modern log altyapısı kuruyorsanız Promtail aracılığıyla Caddy loglarınızı Grafana Loki’ye gönderebilirsiniz. Bunun için Caddy’nin JSON formatındaki logları Promtail’in anlayacağı şekilde yapılandırmak gerekiyor:
# /etc/promtail/promtail-config.yaml içine eklenecek scrape config
# (Bu bir Caddy konfigürasyonu değil, referans amaçlı gösterilmektedir)
Caddy tarafında yapmanız gereken tek şey JSON formatında dosyaya yazmak. Ardından Promtail konfigürasyonu:
# Caddy log dosyasının doğru izinlere sahip olduğunu doğrula
ls -la /var/log/caddy/access.log
# Promtail kullanıcısına okuma izni ver
sudo setfacl -m u:promtail:r /var/log/caddy/access.log
# Loki'ye bağlantıyı test et
curl -s http://localhost:3100/ready
JSON loglar sayesinde Loki’de LogQL ile son derece güçlü sorgular yazabiliyorsunuz. Örneğin 5 dakikadaki 500 hatalarının grafiğini çizmek veya belirli bir kullanıcının session’ını takip etmek gibi senaryolar dakikalar içinde hayata geçirilebiliyor.
Log Rotation ve Disk Yönetimi
Caddy’nin built-in log rotation özelliği çoğu senaryo için yeterli. Ama logrotate ile entegrasyon tercih ederseniz şu yapılandırmayı kullanabilirsiniz:
# /etc/logrotate.d/caddy
/var/log/caddy/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0640 caddy caddy
sharedscripts
postrotate
systemctl reload caddy 2>/dev/null || true
endscript
}
Önemli bir not: Caddy’nin built-in rotation ile logrotate’i aynı anda kullanmayın. İkisi çakıştığında dosya kilitleme sorunları yaşanabilir. Birini seçin ve ona sadık kalın. Ben production ortamlarında genellikle Caddy’nin built-in rotation’ını tercih ediyorum, dışarıya bağımlılık azalıyor.
Gerçek Dünya Senaryosu: E-ticaret Sitesi Log Altyapısı
Birden fazla servisi olan bir e-ticaret yapısı için örnek log altyapısı:
{
email [email protected]
log default {
output file /var/log/caddy/system.log {
roll_size 20mb
roll_keep 5
}
format json
level WARN
}
}
shop.example.com {
log {
output file /var/log/caddy/shop-access.log {
roll_size 500mb
roll_keep 30
roll_keep_for 8760h
}
format json {
time_format iso8601
}
level INFO
}
@static_assets {
path /static/* /images/* /fonts/*
}
log @static_assets {
output file /var/log/caddy/shop-static.log {
roll_size 100mb
roll_keep 3
}
format json
level INFO
}
handle @static_assets {
root * /var/www/shop
file_server
header Cache-Control "public, max-age=31536000"
}
handle {
reverse_proxy localhost:8080 {
health_uri /health
health_interval 30s
}
}
}
admin.example.com {
log {
output file /var/log/caddy/admin-access.log {
roll_size 50mb
roll_keep 90
roll_keep_for 26280h
}
format json {
time_format iso8601
}
level INFO
}
basicauth {
admin $2a$14$...
}
reverse_proxy localhost:9090
}
Bu yapılandırmada admin paneli loglarını ayrı tutuyoruz ve 3 yıl saklıyoruz. Yasal gereksinimler açısından admin işlemlerinin kaydı kritik önem taşıyabilir.
Log Analizi için Günlük Rutin
Her gün veya hafta sabahı çalıştırabileceğiniz basit bir log özeti scripti:
#!/bin/bash
# /usr/local/bin/caddy-log-summary.sh
LOG_FILE="/var/log/caddy/access.log"
YESTERDAY=$(date -d "yesterday" +%Y-%m-%d)
echo "=== Caddy Log Ozeti: $YESTERDAY ==="
echo -e "n--- Toplam Istek Sayisi ---"
grep "$YESTERDAY" "$LOG_FILE" | wc -l
echo -e "n--- HTTP Durum Kodu Dagilimi ---"
grep "$YESTERDAY" "$LOG_FILE" | jq -r '.status' | sort | uniq -c | sort -rn
echo -e "n--- En Cok Istek Yapan IP'ler (Top 10) ---"
grep "$YESTERDAY" "$LOG_FILE" | jq -r '.request.remote_ip' | sort | uniq -c | sort -rn | head -10
echo -e "n--- En Yavash 5 Istek ---"
grep "$YESTERDAY" "$LOG_FILE" | jq -r '[.duration, .request.method, .request.uri] | @tsv' | sort -rn | head -5
echo -e "n--- 4xx ve 5xx Hatalar ---"
grep "$YESTERDAY" "$LOG_FILE" | jq 'select(.status >= 400)' | jq -r '[.status, .request.method, .request.uri] | @tsv' | sort | uniq -c | sort -rn | head -20
Bu scripti crontab’a ekleyin:
# Her sabah 08:00'de çalıştır ve mail at
0 8 * * * /usr/local/bin/caddy-log-summary.sh | mail -s "Caddy Log Ozeti" [email protected]
Sonuç
Caddy’nin log sistemi ilk bakışta basit görünse de derinleştikçe son derece güçlü ve esnek bir yapı olduğunu görüyorsunuz. JSON formatı modern log altyapılarıyla mükemmel entegrasyon sağlarken built-in rotation, log dosyası yönetimini minimum çabayla hallediyor.
Özellikle şu noktalara dikkat etmenizi öneririm: production ortamında her zaman JSON format kullanın, hassas verileri mutlaka maskeleme filtreleri ile koruyun, admin panel veya yönetim arayüzlerinin loglarını ayrı tutun ve yasal gereksinimlere göre saklama sürelerini belirleyin. Statik asset sunucuları için discard çıktısı disk kullanımını kayda değer ölçüde azaltıyor, bunu küçümsemeyin.
Log altyapısı, bir sunucunun “görünmez kalkanı” gibidir. Bir şeyler ters gittiğinde veya güvenlik olayı yaşandığında iyi yapılandırılmış loglar ile sorunun kaynağına dakikalar içinde ulaşabilirsiniz. Caddy bu konuda sizi doğru yöne yönlendiren akıllı varsayılanlarla geliyor, siz de üzerine ihtiyacınıza özel katmanlar ekliyorsunuz.