Skip to content

Debugging

Log verbosity is a runtime setting, not an environment variable. The file_log_level setting (debug, info, warn, or error; default info) controls what is written to the log files under <data-dir>/logs/. Change it from Admin > Logs in the web UI, or via odeion config edit under the logs section. Changes take effect immediately, without a restart.

Console output (stderr) always shows warnings and errors only; the level setting applies to the log files.

odeion doctor collects information about your install into a single zip that you can attach to a bug report or send to support. The bundle is redacted by default and can be encrypted with age so it’s safe to send over any channel.

Terminal window
odeion doctor

This writes odeion-doctor-<uuid>.zip into <data-dir>/diagnostics/ and prints the absolute path on success. In the Docker image that’s /var/lib/odeion/diagnostics/.

doctor expects the Odeion server to be running. It connects to the embedded postgres using the read-only role (odeion_ro) and only runs SELECT queries, so it can’t perturb the running instance. If the server is not running, the postgres_live section fails cleanly and the rest of the bundle (system info, on-disk postgres metadata, logs) is still written.

If the server itself is broken and you can’t reach the database, pass --no-db to skip that section entirely:

Terminal window
odeion doctor --no-db
odeion-doctor-<uuid>.zip
├── manifest.json Sections collected, flags used, per-section status.
├── README.txt Human-readable summary.
├── system/ OS, kernel, CPU, disk free, ffmpeg/ffprobe versions.
├── install/ Odeion build info and data-directory layout.
├── postgres/
│ ├── on_disk.json PG_VERSION, data-dir size, WAL segment count.
│ ├── postgresql.conf Raw copy.
│ ├── pg_hba.conf Raw copy.
│ ├── server_stats.json Uptime, db size, connection counts, cache ratio.
│ ├── migrations.json Applied and pending goose migrations.
│ ├── table_stats.json Per-table row counts and sizes.
│ └── top_statements.json Top 25 queries from pg_stat_statements.
├── settings/
│ └── system_settings.json All admin settings, sensitive values redacted.
├── tasks/
│ └── summary.json processing_tasks counts by type and status.
└── logs/
├── server.log Tail, redacted.
└── postgres.log Tail, redacted.

No table row data is ever included, only counts, sizes, and stats. The exception is system_settings, which holds the values you entered through the admin UI. Sensitive fields (API keys, passwords, tokens, webhook secrets) are replaced with [REDACTED:<length>] unless --unredacted is set.

By default the bundle includes every section. Use --no-* flags to opt out. --unredacted and --age-recipient are the only opt-in flags.

FlagDefaultDescription
--data-dir$ODEION_DATA_DIRData directory. Must match the serve process.
--database-urllocallocal for the embedded postgres managed by the running server, or a DSN for an external database.
--out<data-dir>/diagnostics/odeion-doctor-<uuid>.zip[.age]Override the output path. .age is appended when encrypting.
--no-logsfalseExclude the logs/ section.
--no-dbfalseSkip postgres entirely. Use when the server is broken.
--no-settingsfalseSkip the system_settings dump.
--no-task-summaryfalseSkip processing_tasks row counts.
--log-size10 MiBPer-log-file byte cap. Only the tail of each log is included.
--unredactedfalseSkip all redaction. See warning below.
--age-recipientunsetEncrypt the output with age for the given recipient.
--pproffalseCapture CPU/heap/goroutine profiles into pprof/. From the CLI this profiles the short-lived doctor process, not the server, so it is rarely useful; capture profiles from the admin UI instead.
--pprof-seconds30CPU profile duration when --pprof is set.

Redaction runs on every section except postgresql.conf and pg_hba.conf, which don’t contain credentials.

DataBecomesNotes
Email addresses[EMAIL]
Bearer <token>Bearer [TOKEN]
JWT-looking values[JWT]
Hex strings of 32+ characters[SECRET]Catches most API keys.
Public IPv4/IPv6 addresses[IP] / [IPV6]Loopback, RFC1918, and link-local addresses are preserved; they don’t identify users and help local debugging.
Absolute paths under $HOME~/...
Settings values with sensitive keys[REDACTED:<length>]Keys containing api_key, apikey, password, passwd, secret, token, jwt, webhook, or auth. The values are also scrubbed from logs if they were ever printed.

Passing --unredacted disables all of the above. The bundle will contain real API keys, passwords, file paths, and email addresses. A warning is printed before collection runs. Only use this for local inspection, or pair it with --age-recipient when you need to ship sensitive data to support without exposing it in transit. Do not post an unredacted bundle to a public issue.

When --age-recipient is set, the zip is encrypted with age for that recipient and the output file gets a .zip.age suffix. The plaintext zip never touches disk.

Accepted recipient formats:

  • An age X25519 public key: age1...
  • An SSH public key line: ssh-ed25519 AAAAC3... name@host or ssh-rsa AAAAB3... name@host

The typical support flow:

Terminal window
# Encrypt to the support engineer's published public key
odeion doctor --age-recipient=age1qz...zk2
# Wrote: /var/lib/odeion/diagnostics/odeion-doctor-<uuid>.zip.age
# Send that file to support.
# Support side (holder of the matching private key):
age --decrypt --identity support.key \
odeion-doctor-<uuid>.zip.age > bundle.zip
unzip bundle.zip

Encryption and redaction are independent. A redacted bundle can also be encrypted for transport, and an unredacted bundle can be encrypted so that only the intended recipient can read it.

A default (redacted, unencrypted) bundle is safe to attach to a GitHub issue or forum post. Before sharing, it’s still worth unzipping and skimming manifest.json and logs/server.log to confirm nothing surprising slipped through.

If manifest.json reports a section failure (for example because embedded postgres wasn’t reachable), the rest of the bundle is still written. Mention any reported section errors in your bug report so support knows what’s missing.