Error Log #
Error log Caddy mencatat masalah internal yang terjadi di Caddy sendiri — kegagalan mendapatkan sertifikat TLS, error konfigurasi, kegagalan koneksi ke backend, dan berbagai kondisi error lainnya. Ini berbeda dari access log yang mencatat setiap request.
Error log dikonfigurasi di global options block karena ia bersifat server-wide, bukan per-site. Caddy menulis error log ke stderr secara default dengan level INFO.
Struktur Error vs Access Log #
Access Log:
→ Dicatat per request
→ Siapa meminta apa, dengan status apa, berapa lama
→ Dikonfigurasi per-site dengan direktif 'log'
→ Tujuan: monitoring traffic, analisis penggunaan
Error Log (Server Log):
→ Dicatat untuk event internal Caddy
→ Kegagalan TLS, config error, koneksi backend gagal
→ Dikonfigurasi di global options
→ Tujuan: debugging, alerting, operasional
Konfigurasi Error Log di Global Options #
{
# Error log global (server log)
log {
# Output ke file
output file /var/log/caddy/error.log {
roll_size 20mb
roll_keep 10
roll_keep_days 30
}
# Format: json atau console
format json
# Level: DEBUG, INFO, WARN, ERROR
# Gunakan WARN untuk production (kurangi noise)
level WARN
}
}
example.com {
file_server { root /var/www/html }
}
Level Logging #
DEBUG → Informasi sangat detail untuk debugging mendalam
Contoh: setiap langkah TLS handshake, routing decision
Penggunaan: development, debugging masalah spesifik
⚠️ JANGAN aktifkan di production — sangat verbose, penuhi disk
INFO → Event normal yang informatif (default)
Contoh: server started, sertifikat renewed, config loaded
Penggunaan: production standar
WARN → Kondisi yang tidak ideal tapi tidak kritis
Contoh: retry koneksi ke backend, sertifikat akan expire
Penggunaan: production dengan noise minimal
ERROR → Kegagalan yang perlu perhatian segera
Contoh: backend tidak bisa dijangkau, sertifikat gagal diperbarui
Penggunaan: hanya alert untuk kondisi kritis
# Development: lihat semua detail
{
debug # Shortcut untuk log level DEBUG di global
}
# Atau eksplisit:
{
log {
level DEBUG
output stderr
format console
}
}
Format JSON untuk Error Log #
{
"ts": 1704067200.123456,
"level": "error",
"logger": "tls.obtain",
"msg": "could not get certificate from issuer",
"identifier": "example.com",
"issuer": "acme-v02.api.letsencrypt.org/directory",
"error": "ACME challenge failed: HTTP-01 challenge: could not connect to CA"
}
{
"ts": 1704067200.123456,
"level": "warn",
"logger": "http.handlers.reverse_proxy",
"msg": "upstream is now unavailable",
"upstream": "backend-2:3000",
"total_upstreams": 3,
"healthy_upstreams": 2
}
Memisahkan Error Log dan Access Log #
Untuk production yang baik, pisahkan error log dan access log:
{
# Error log: event internal Caddy
log {
output file /var/log/caddy/server.log {
roll_size 50mb
roll_keep 10
roll_keep_days 30
}
format json
level WARN
}
}
example.com {
# Access log: traffic per-site
log {
output file /var/log/caddy/access.log {
roll_size 200mb
roll_keep 7
roll_keep_days 30
}
format json
}
reverse_proxy backend:3000
}
Memonitor TLS Errors #
TLS errors adalah kategori penting yang perlu dipantau:
# Filter hanya TLS-related errors
tail -f /var/log/caddy/server.log | \
jq 'select(.logger | startswith("tls"))'
# Contoh output TLS error:
# {"ts":...,"level":"error","logger":"tls.obtain","msg":"could not get certificate",
# "identifier":"example.com","error":"..."}
# Alert jika ada TLS error
tail -f /var/log/caddy/server.log | while read line; do
if echo "$line" | jq -e '.level == "error" and (.logger // "" | startswith("tls"))' > /dev/null 2>&1; then
echo "TLS ERROR DETECTED!"
echo "$line" | jq .
# Kirim alert via email/Slack
fi
done
# Cek kapan sertifikat akan expire
# Caddy log warning sebelum expire
grep "certificate" /var/log/caddy/server.log | \
jq 'select(.msg | contains("expir"))' | head -5
Memonitor Backend Health via Error Log #
# Filter upstream availability events
cat /var/log/caddy/server.log | \
jq 'select(.msg | contains("upstream"))' | \
jq -r '"\(.ts | todate) [\(.level)] \(.msg): \(.upstream // "unknown")"'
# Hitung berapa kali setiap backend jadi unavailable
cat /var/log/caddy/server.log | \
jq 'select(.msg == "upstream is now unavailable") | .upstream' | \
sort | uniq -c | sort -rn
# Real-time stream hanya error dan warning
tail -f /var/log/caddy/server.log | \
jq 'select(.level == "error" or .level == "warn")'
Integrasi dengan Alerting #
#!/bin/bash
# caddy-alert.sh — Monitor error log dan kirim alert
LOG_FILE="/var/log/caddy/server.log"
SLACK_WEBHOOK="https://hooks.slack.com/services/T.../B.../..."
tail -F "$LOG_FILE" | while read line; do
level=$(echo "$line" | jq -r '.level' 2>/dev/null)
msg=$(echo "$line" | jq -r '.msg' 2>/dev/null)
if [ "$level" = "error" ]; then
logger=$(echo "$line" | jq -r '.logger' 2>/dev/null)
payload=$(cat <<EOF
{
"text": ":alert: *Caddy Error*\n*Logger:* $logger\n*Message:* $msg\n\`\`\`$line\`\`\`"
}
EOF
)
curl -s -X POST -H "Content-Type: application/json" \
-d "$payload" "$SLACK_WEBHOOK"
fi
done
# Jalankan sebagai systemd service
# /etc/systemd/system/caddy-monitor.service
# [Unit]
# Description=Caddy Log Monitor
# After=caddy.service
#
# [Service]
# ExecStart=/usr/local/bin/caddy-alert.sh
# Restart=always
#
# [Install]
# WantedBy=multi-user.target
Debug Mode untuk Troubleshooting #
Saat menghadapi masalah yang sulit di production, aktifkan debug log sementara:
{
# SEMENTARA: aktifkan debug log
# Ingat: kembalikan ke WARN/INFO setelah selesai troubleshoot!
debug
}
example.com {
reverse_proxy backend:3000
}
# Atau via Admin API tanpa restart
# Ubah log level sementara
curl -X PUT http://localhost:2019/config/logging/logs/default/level \
-H "Content-Type: application/json" \
-d '"debug"'
# Kembalikan ke INFO setelah selesai
curl -X PUT http://localhost:2019/config/logging/logs/default/level \
-H "Content-Type: application/json" \
-d '"info"'
# Lihat detail koneksi TLS di debug mode
tail -f /var/log/caddy/server.log | \
jq 'select(.level == "debug" and (.logger | contains("tls")))'
Structured Error Logging dari Handle Errors #
example.com {
log {
output file /var/log/caddy/access.log
format json
}
reverse_proxy backend:3000
handle_errors {
# Log error responses dengan detail tambahan
log {
output file /var/log/caddy/error-responses.log
format json
}
@5xx expression {err.status_code} >= 500
handle @5xx {
respond "Internal Server Error: {err.status_code}" {err.status_code}
}
respond "{err.status_code} {err.status_text}" {err.status_code}
}
}
Ringkasan #
- Error log dikonfigurasi di global options (
{ log { ... } }), bukan di dalam site block seperti access log.- Gunakan level
WARNdi production — cukup informatif tanpa noise berlebihan. AktifkanDEBUGhanya saat troubleshooting masalah spesifik.- Pisahkan error log server (
/var/log/caddy/server.log) dari access log per-site untuk kemudahan monitoring dan analisis.- TLS errors dan upstream unavailability adalah kategori yang paling penting untuk dipantau — setup alerting untuk kedua jenis event ini.
- Gunakan Admin API untuk mengubah log level sementara tanpa restart:
PUT /config/logging/logs/default/leveldengan value"debug".- Format JSON memudahkan parsing dengan
jqdan integrasi dengan tools aggregation seperti Elasticsearch, Loki, atau Datadog.
← Sebelumnya: Access Log Berikutnya: Format Log →
Error Log untuk Certificate Monitoring #
Sertifikat TLS yang tidak diperbarui adalah salah satu penyebab downtime paling umum. Caddy biasanya memperbarui sertifikat otomatis, tapi kadang gagal karena masalah DNS atau ACME challenge:
# Monitor certificate renewal events
grep -E "renew|certificate|tls" /var/log/caddy/server.log | \
jq 'select(.level == "error" or .level == "warn")' | \
jq -r '"\(.ts | todate) [\(.level)] \(.logger): \(.msg)"'
# Cek apakah ada domain yang gagal renewal
cat /var/log/caddy/server.log | jq '
select(.msg | test("(renew|obtain|certificate)"; "i")) |
select(.level == "error") |
{time: (.ts | todate), domain: .identifier, error: .error}
'
# Setup alert untuk certificate errors
# Masukkan ke crontab atau monitoring script
Caddy biasanya mencoba renewal saat sertifikat menyisakan 30 hari — log warning pada titik ini adalah normal. Jika ada error, periksa apakah DNS sudah benar dan port 80/443 dapat dijangkau.