Files
inbuxa-server/crates/services/src/inbuxa_log_retention.rs
T
jcoffey-dev 1d5a49409f
ci / fork-checks (pull_request) Successful in 2m28s
ci / build (pull_request) Successful in 3m47s
Keep rotated log files for a set number of days
Personal-data catalog spec, default D1 (settled 2026-09-28): log files
were never deleted. inbuxa:LogSettings.keepForDays says how many days
rotated log files are kept; unset (null) keeps every file, as before,
and a new install sets 30 days.

It is a fork-owned setting, stored under T + l as audit retention is,
not a field on x:TracerLog: that object is also stored inside
x:Bootstrap with a field after it, so a new field would change
x:Bootstrap's stored format. Server-level, with the tracers'
permissions (sysTracerGet, sysTracerUpdate); changes are in the audit
log, before and after.

Log files are local, so every node deletes its own: hourly, and at once
when the setting changes on that node. Only regular files named
<prefix>.<something> in each enabled log tracer's directory, last
changed more than the limit ago, are removed; the file being written is
never that old, and nothing else in the directory is touched. Minimum
one day. The catalog classifies inbuxa:LogSettings and points the log
file's retention at it.

Tested: unit tests for the file rule (only this log's old files; the
current file, other files and directories stay) and a purge on disk;
the system suite, which reads, sets, refuses zero, restores null and
checks the audit records; fork checks.
2026-09-28 08:23:36 -07:00

63 lines
2.0 KiB
Rust

/*
* SPDX-FileCopyrightText: 2026 Coffey Labs
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
//! Deletes rotated log files past `inbuxa:LogSettings.keepForDays`
//! (personal-data catalog spec, D1). Log files are local, so every node
//! cleans its own: hourly, and at once when the settings change here.
use common::{BuildServer, Inner, Server};
use inbuxa_features::security::log_files;
use registry::schema::structs::Tracer;
use std::{path::PathBuf, sync::Arc, time::Duration};
const EVERY: Duration = Duration::from_secs(3600);
pub fn spawn_log_retention(inner: Arc<Inner>) {
tokio::spawn(async move {
loop {
let server = inner.build_server();
if let Err(err) = purge(&server).await {
trc::error!(err.details("Failed to delete old log files"));
}
tokio::select! {
_ = tokio::time::sleep(EVERY) => {}
_ = log_files::CHANGED.notified() => {}
}
}
});
}
async fn purge(server: &Server) -> trc::Result<()> {
let Some(days) = log_files::get(&server.core.storage.data)
.await?
.keep_for_days
else {
return Ok(());
};
let keep = Duration::from_secs(days.max(log_files::MIN_KEEP_DAYS) * 86_400);
for tracer in server.registry().list::<Tracer>().await? {
let Tracer::Log(log) = tracer.object else {
continue;
};
if !log.enable || log.path.is_empty() {
continue;
}
let (dir, prefix) = (PathBuf::from(&log.path), log.prefix.clone());
let result = tokio::task::spawn_blocking(move || log_files::purge(&dir, &prefix, keep))
.await
.map_err(|err| trc::EventType::Server(trc::ServerEvent::ThreadError).reason(err))?;
if let Err(err) = result {
trc::event!(
Telemetry(trc::TelemetryEvent::LogError),
Details = "Failed to delete old log files",
Path = log.path.clone(),
Reason = err.to_string(),
);
}
}
Ok(())
}