access_log
Optional request log: every response gets a line — any status, 404s and 500s included — plus explicit RATELIMIT events. Two destinations, chosen by mode; remove the block to disable logging entirely (no thread, no files).
| key | default | description |
|---|---|---|
| mode | file | file — daily files; db — a table in a dedicated SQLite database |
| path | ./logs | mode=file: directory for log files, created on start |
| db | — | mode=db: database name from db[]. It must be dedicated — a database used by tables or by the built-in users is rejected, so log inserts never queue behind your data writes |
| keep_days | 30 | how many days to keep (including today); older files are deleted at rotation, older rows — by a daily DELETE |
mode=file — "like the big ones": one file per UTC day, finished days are zstd-compressed, old files are deleted automatically. access-2026-07-07.log is the current day; at midnight (UTC) it becomes access-2026-07-07.log.zst and a new file starts. To read a compressed day: zstd -dc access-2026-07-07.log.zst | less.
Line format — timestamp, client IP, method, path (query string is omitted — it may contain sensitive values), status, duration:
2026-07-07T01:16:35Z 203.0.113.7 GET /docs/getting-started 200 1.23ms
A triggered rate limit is logged as its own event with the limiter key — useful because /soe replies HTTP 200 with the error in the body, so the status column alone would not show it:
2026-07-07T01:16:35Z 203.0.113.7 RATELIMIT auth:login:admin 429 -
mode=db — the same records as rows of an access_log table (ts, ip, method, path, status, dur_us; RATELIMIT events get method=RATELIMIT), written in batched transactions:
"db": [
{ "name": "main", "path": "./db/main.db" },
{ "name": "log", "path": "./db/log.db" }
],
"access_log": { "mode": "db", "db": "log", "keep_days": 30 }The table is indexed by time, IP, status and path, and since the database is declared in db[] it is visible in /soe — explore your traffic from sqliteonline.com: SELECT path, COUNT(*) c FROM access_log WHERE status = 404 GROUP BY path ORDER BY c DESC; This example site runs in db mode (the config above is its actual config), so that query works on your own visits right away.
The client IP honors trusted_proxies (see the server chapter): behind a proxy it is the X-Real-IP value, otherwise the connection address. Logging is non-blocking in both modes — requests never wait for storage; if it stalls, lines are dropped rather than queued forever.