v0.7.4 · updated · GitHub

Log Sources

Every command accepts one or more sources. If you pass none, the tool tries caddy-analyzer.json → pipe → auto-discovery. Below: how to connect to each source and what permission that source needs. For tool configuration (config file, tuning, env), see Guide → Configuration.

File & glob

caddy-analyze /var/log/caddy/access.log
caddy-analyze /var/log/caddy/*.log
caddy-analyze /var/log/caddy/access.log.1 /var/log/caddy/access.log.2.gz
# no args → tries ./access.log, ./caddy.log, ./caddy-access.log, /var/log/caddy/access.log, /var/log/caddy/caddy.log
caddy-analyze

Permissions for files

Caddy often writes as caddy:caddy 0600. You need read on the file and execute on the directory.

sudo caddy-analyze /var/log/caddy/access.log
# persistent — add yourself to the group then re-login:
sudo usermod -aG caddy $USER
Compressed files
Pipe through zcat: zcat /var/log/caddy/*.gz | caddy-analyze -. Do not pass .gz directly.

Stdin — -

cat /var/log/caddy/access.log | caddy-analyze -
zcat /var/log/caddy/access.log.*.gz | caddy-analyze - --detect -f json -o out.json
ssh app@prod "cat /var/log/caddy/access.log" | caddy-analyze - -t 25

Permissions for stdin

None on the local side. Remote side needs read as above, plus SSH access. Stdin is also auto-detected when data is piped and no source arg is given.

Docker — docker://<name|id>

caddy-analyze docker://caddy
caddy-analyze docker://a3f9c1e2d4b5
caddy-analyze tail docker://caddy          # live
caddy-analyze --follow docker://caddy      # same via root flag

Streams with docker logs -f <container> in an interactive terminal; non-interactive reads add --tail=all. Both merge stdout and stderr.

Permissions for Docker

# docker.sock is root:docker 0660 by default
sudo usermod -aG docker $USER  # re-login
# or
sudo caddy-analyze docker://caddy
# if Caddy runs in Docker but guard must block host:
sudo caddy-analyze guard --firewall-backend hybrid docker://caddy
Tip
If you run Caddy in Docker, prefer --firewall-backend hybrid or auto — it blocks both INPUT and DOCKER-USER.

Kubernetes — k8s://<pod>

caddy-analyze k8s://caddy-7d9f8c9d4-abc12
caddy-analyze k8s://caddy-7d9f8c9d4-abc12 -n production
caddy-analyze tail k8s://caddy-7d9f8c9d4-abc12 -n prod --detect

Reads via kubectl logs --tail=-1 --follow -n <ns> <pod> using your active kubectl context.

Permissions for Kubernetes

kubectl auth can-i get pods/log -n production  # should be yes
kubectl config current-context
kubectl get pods -n production | grep caddy

Namespace can also be persisted: caddy-analyze config k8s://caddy-xxx -n production (see Configuration).

journalctl — journalctl://<unit>

caddy-analyze journalctl://caddy.service
caddy-analyze tail journalctl://caddy.service
caddy-analyze journalctl://caddy.service --follow

Reads via journalctl -u <unit> --output=cat [--follow].

Permissions for journalctl

sudo usermod -aG systemd-journal $USER  # re-login
# or
sudo caddy-analyze journalctl://caddy.service

Auto-discovery (no source arg)

When you run caddy-analyze with no file/URI and no pipe, it probes in order:

  1. caddy-analyzer.json (local then global — Configuration)
  2. stdin pipe if data is piped
  3. 5 candidates: ./access.log, ./caddy.log, ./caddy-access.log, /var/log/caddy/access.log, /var/log/caddy/caddy.log
caddy-analyze config /var/log/caddy/access.log  # remember for next time
caddy-analyze --detect                # now works with no arg

Fixtures — try without a server

Repo ships two deterministic fixtures under testdata/ (regenerate with python3 testdata/generate.py). All IPs are RFC 5737 TEST-NET.

FileLinesUse
testdata/sample.log68One entry per category — verify all 26 fire
testdata/large.log~50K24h mix (95% benign) — throughput demo
caddy-analyze --detect testdata/sample.log
caddy-analyze tail -d testdata/sample.log
caddy-analyze -f html -o report.html --detect testdata/sample.log && open report.html
caddy-analyze diff testdata/sample.log testdata/large.log