Audit retention
rim-server keeps two audit records, and retention treats them differently:
| Record | Where | Retention |
|---|---|---|
| Command audit log | the command_audit_log table, read by the admin audit view (listAuditLog) |
RIM_AUDIT_RETENTION_DAYS, this page |
| Audit file | audit.log in RIM_AUDIT_LOG_PATH (and every audit event on stdout) |
the operator's own log; rotate it with logrotate, below |
Unset, nothing changes: the table keeps every row forever.
The admin audit view shows the retention in force. It reads serverInfo.auditRetentionDays,
readable without a session: the configured days, or null when rows are kept forever.
Turning it on #
| Variable | Meaning |
|---|---|
RIM_AUDIT_RETENTION_DAYS |
Days a command audit row is kept, 1 to 3650. Unset (the default) keeps rows forever. A value outside the range, or one that is not a whole number, stops the server at boot. |
With a retention set, the server purges rows older than that many days a minute after it starts and then every 24 hours. A purge runs in two steps:
- Export. Every row older than the cutoff is written, oldest first, to
<RIM_DATA_DIR>/audit-exports/audit-<oldest>-<newest>.jsonl.gz, where the two timestamps are thecreated_atof the first and last row (20260712T030000Z). The file is gzipped JSON Lines, one row per line. It is written under a hidden.partialname, synced to disk and only then renamed; a name already taken gets a-2,-3, … suffix. - Delete. Only then are the exported rows deleted, 5000 per statement, so a large table never holds one long lock. Rows newer than the last exported row are never deleted, even if they appear below the cutoff during the run.
If the export fails (the directory cannot be created, the disk is full, the database stops answering while the rows are read), nothing is deleted and the server logs a warning; the next run tries again. If a delete fails halfway, the rows deleted so far are gone and are in the file; the rest stay, and the next run exports them again into a new file, so the same row can appear in two export files.
Each line holds the fields of the admin audit view, plus the parent macro row:
{"id":"0192…","actorUserId":"0192…","serverId":"0192…","commandId":"/topic","initiator":"palette","argsRedacted":{"topic":"[redacted]"},"sensitivity":"low","dangerous":false,"confirmationKind":"none","typedVerb":"write","outcome":"success","errorKind":null,"capabilityCheckOutcome":null,"confirmationOutcome":null,"parentAuditId":null,"createdAt":"2026-07-12T03:00:00.123456+00:00"}
typedVerb and outcome are the stored lowercase values (delete, permission_denied), not
the GraphQL enum names.
Read one with:
zcat audit-20260712T030000Z-20260713T025959Z.jsonl.gz | jq .
The server never deletes an export. They hold user and workspace ids and the redacted command arguments: keep them where the backups go, encrypted, and delete them when your own retention for archived audit data runs out (FILL: how long, and where they are copied).
Each run is on record itself: an audit event audit.retention.purged (target audit) with
trigger (job or cli), cutoff, exported, deleted and export_path (empty when nothing
was old enough). A purge that stops logs audit.retention.failed with the error instead.
More than one server process on the same database each runs the job; the second run of a day
finds nothing left to purge. docs/operations/background-jobs.md lists the server's other
scheduled jobs.
The command #
The same purge, by hand:
rim_server audit purge [--dry-run] [--older-than-days <1-3650>]
Without --older-than-days it uses RIM_AUDIT_RETENTION_DAYS and refuses to run when that is
unset. --dry-run deletes and writes nothing; it prints how many rows would go and their date
range. In the relay deployment:
rim-relay audit purge --dry-run
# would purge 1832 audit rows older than 2026-07-12T09:14:03+00:00 (90 days), from 2026-04-01T08:00:12+00:00 to 2026-07-12T09:10:44+00:00
rim-relay audit purge
# purged 1832 audit rows older than 2026-07-12T09:14:05+00:00 (90 days), from … to …
# exported 1832 rows to /data/audit-exports/audit-20260401T080012Z-20260712T091044Z.jsonl.gz
rim-relay audit purge --older-than-days 365 --dry-run
Exit code 0 on success, 1 on an error (no retention configured, the database unreachable, a failed export), 2 on a usage error.
Metrics #
| Metric | Type | Meaning |
|---|---|---|
rim_audit_rows_purged_total |
counter | Audit rows the purge deleted. |
rim_audit_retention_days |
gauge | RIM_AUDIT_RETENTION_DAYS, set at boot; 0 when rows are kept forever. |
Both need RIM_METRICS_ADDR (docs/operations/metrics.md). A purge run from the command line
is a separate process and is not counted.
The audit file #
audit.log is appended to for as long as the server runs and is never trimmed by it. The server
keeps the file open, so rotate it with copytruncate:
/data/audit.log {
daily
rotate 90
compress
delaycompress
missingok
notifempty
copytruncate
}
In the compose deployments the file is in the rim_rim_data volume (rim-relay_rim_data on a
relay); run logrotate on the host against the volume's mount point, or ship stdout to your log
store and keep the file short.