08 — Observability
Three log files are written to the current working directory (not the config paths):
+All logs are written to a SQLite database, logs.db, in the current working directory (not the config paths). Run the program from where you want it to land — in Docker that's the mounted /data. The logs table has columns id, ts, level, message, file, extra.
| File | Contents | |
|---|---|---|
| Level | Where | Contents |
info_YYYY-MM-DD.log | INFO messages, dated | |
error_YYYY-MM-DD.log | ERROR messages, dated | |
structured.json | One JSON object per line — every entry, with timestamp, level, message, optional error & file | |
info | db + stdout | Processing / metadata hits / completions |
error | db + stderr | Failures, with the source file recorded |
debug | db only | ffprobe output, zscale width, full ffmpeg/opusenc command, OMDb/TVmaze responses (API key redacted) in extra |
Run the program from the directory where you want the logs to land. A number of DEBUG lines also print to stdout/stderr (ffprobe output, calculated zscale width, the full ffmpeg command) — intentional, but not written to the log files.
The date in info_*.log / error_*.log filenames is computed at daemon start and does not roll over at midnight. Left running across days, all writes continue into the start-day's file — restart to rotate. structured.json does not rotate at all.
Live encode progress (encoding … · 47% · 3.2fps · …) prints to stdout only and is deliberately not stored, so it can't flood the database.
Rows older than log_retention_days (default 7) are purged on startup and shutdown. Editable live from the settings page (/settings); applies at the next purge. Recent non-debug events are viewable in the dashboard and via GET /status when http_addr is set.
.mkv is moved to paths.failed (os.Stat-guarded — if the source is already gone the move is skipped and logged; a move error is logged too). This guarantees the source leaves paths.input on every failure path, so the watcher won't retry it next tick.error_*.log and structured.json with the source path.logs.db (level error) with the source path, and printed to stderr.The watcher continues with the next file; one bad rip won't stop the daemon.
If a finished encode would land on a name that already exists in paths.output, the move is refused (no silent overwrite) and the source is routed to paths.failed. This is the path you hit when two sources sanitize to the same name — two re-rips of the same release, or two episodes that both resolve to SxxExx.