What gets archived
Every line a container writes to stdout or stderr, exactly as the node-local log store received it. Objects are written to:log-archive/service/web/2026/09/24/13/1790255400000000000.ndjson.gz. The prefix can be changed with APP_LOG_ARCHIVE_PREFIX. Each line is a JSON object:
fields is only present when message parsed as a JSON object (structured: true); plain-text lines carry message only.
How it behaves
- The archiver pulls, it does not push. A scheduler wakes once a minute (
APP_LOG_ARCHIVE_TICK), checks which policies are due, and reads new lines from the node-local store. - Each policy keeps a watermark. Objects are named by their chunk start, so a retry after a failure overwrites the same key instead of duplicating lines. A failed window keeps the watermark in place and is retried on the next run.
- Runs are bounded: one at a time, at most 24 hour-windows per scheduled run, and at most 200,000 lines per object (
APP_LOG_ARCHIVE_MAX_LINES_PER_OBJECT). Lines are spooled to a temp file first so the log database is never held open during an upload. A new policy archives forward from the moment it is created; use a dump for history. - Each scheduled tick or manual dump is recorded as a run with a status (
running,succeeded,failed), the time range it covered, and object/line/byte counts, visible vialevelrail-cli logs archive statusorGET /api/v1/log-archive/runs.
Retention
Set “Keep for (days)” on a policy (0 means forever) to have the archiver delete objects older than that after each successful scheduled run, up to 500 deletions per run. The global policy (no app set) leaves apps that have their own policy alone, so a per-app retention setting always wins over the global one. If you would rather let the provider manage deletion, add a bucket lifecycle rule on thelog-archive/ prefix and leave retention at 0.
Manage policies
- Dashboard
- CLI
Use an app’s Logs, Archive tab for a per-app policy, or Settings, Storage destinations for the global policy that covers every app.
PUT /api/v1/log-archive/policy).
Dump now
Archive a past range immediately, without waiting for the schedule:--from and --to take an RFC3339 timestamp or a duration back from now. The range is capped at 31 days. The dashboard has the same control on the Archive tab. Under the hood this is POST /api/v1/log-archive/dump, which answers 202 Accepted with a run to poll (GET /api/v1/log-archive/runs) rather than blocking until the dump finishes.
Retrieving archived logs
GET /api/v1/log-archive/objects?target_id=<id>&app=web lists keys page by page (100 per page, follow next), and GET /api/v1/log-archive/objects/download?target_id=<id>&key=<key> streams the object back with Content-Type: application/gzip. Download is limited to keys under the archive prefix; a key outside it is rejected.
Read an object with zcat file.ndjson.gz | jq .. Archived objects have no search index; use your bucket provider’s tooling (Athena, DuckDB, zgrep) to search them.
Alerting
Create alog_archive_stale alert rule (dashboard: Alerts, or levelrail-cli apps alerts create <app> --kind log_archive_stale) to be notified when a policy’s last run failed, or when it has gone longer than the rule’s for_duration without a success (default: three intervals, at least two hours).
API and MCP
REST routes live under/api/v1/log-archive (see the API reference). Reads need the read ability, policy changes and dumps need write. The MCP server exposes list_log_archive_policies, set_log_archive_policy, start_log_archive_dump, list_log_archive_runs, and list_archived_logs.
Next steps
Object storage
Connect the bucket that archived logs are written to.
Observability
Live logs, metrics, and alert rules.