2.4 KiB
retention
(no submodules)
Purpose
Enforces the maxFiles retention dimension of the rotation policy by
deleting the oldest rotated files until at most maxFiles remain.
Retention runs immediately after each rotation succeeds so the disk
footprint is bounded across long-running deployments — an audit
directory left to grow unchecked is the most common operator pain
point with file-based sinks, and this module's existence is exactly
the answer to it.
The increment on api.audit.rotation_evicted_total{sink="file"} makes
eviction observable. Operators alert on an unexpectedly high rate so a
runaway rotation cycle (e.g. clock skew, misconfigured maxAgeMs) is
visible before the retention sweep drops historical data.
Features
enforceRetention(fs, basePath, maxFiles, instruments)— deletes the oldest rotated files until at mostmaxFilesremain. Idempotent: re-running with no surplus is a no-op.- Counter increment per deletion with the canonical
sink="file"attribute set. - Surfaces
AUDIT_FILE.RETENTION_FAILEDon a delete failure with the offending path incontext.pathand the underlyingcause.
Usage: Basic
import { enforceRetention } from "@suaveplan/api-audit-file/retention";
await enforceRetention(fs, "/var/log/audit.log", 30, instruments);
Usage: Advanced
Call from a custom rotation cadence — e.g. an external cron that wants to prune without rotating:
import { enforceRetention } from "@suaveplan/api-audit-file/retention";
// Cron job: every hour, prune to a smaller retention than the sink's
// default to claw back disk during high-traffic windows.
setInterval(async () => {
await enforceRetention(fs, basePath, 10, instruments);
}, 60 * 60 * 1000);
API Reference
enforceRetention(fs: FsAdapter, basePath: string, maxFiles: number, instruments: AuditFileInstruments): Promise<void>
Implementation Notes
listRotatedFiles returns paths sorted oldest-first by the embedded
ISO timestamp; we slice off the first (count - maxFiles) entries and
unlink them. Sorting is lexicographic on the ISO string, which is also
chronological for the YYYY-MM-DDTHH-MM-SS.sssZ shape — no extra
parsing required. If unlink fails midway, the previously-evicted
deletions are not undone; the throw surfaces the failure and the next
rotation re-attempts retention against whatever survived.