Skip to content

Debugging

For general debugging without profiling, you can increase log verbosity with the ODEION_LOG_LEVEL environment variable:

Terminal window
ODEION_LOG_LEVEL=debug odeion serve --database-url local

See Configuration for all startup configuration options.

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.

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

Scrubbed by default:

  • Email addresses become [EMAIL].
  • Bearer <token> becomes Bearer [TOKEN].
  • JWT-looking values become [JWT].
  • Hex strings of 32 characters or more become [SECRET]. This catches most API keys.
  • Public IPv4 and IPv6 addresses become [IP] or [IPV6]. Loopback, RFC1918, and link-local addresses are preserved because they don’t identify users and are useful for local debugging.
  • Absolute paths under $HOME are rewritten as ~/....
  • In the settings dump, any value whose key contains api_key, apikey, password, passwd, secret, token, jwt, webhook, or auth is replaced with [REDACTED:<length>]. Those same values are also registered as literals so they won’t appear in logs even if they were 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.