Keep rotated log files for a set number of days
ci / fork-checks (pull_request) Successful in 2m28s
ci / build (pull_request) Successful in 3m47s

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.
This commit is contained in:
2026-09-28 08:23:36 -07:00
parent bdd97c5828
commit 1d5a49409f
21 changed files with 789 additions and 2 deletions
@@ -0,0 +1,62 @@
/*
* 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(())
}
+4
View File
@@ -25,6 +25,7 @@ use crate::task_manager::{manager::spawn_task_manager, scheduler::spawn_task_sch
pub mod broadcast;
// inbuxa: AL-5, delegations end at their date
pub mod inbuxa_lock_expiry;
pub mod inbuxa_log_retention; // inbuxa: personal-data catalog, D1
pub mod state_manager;
pub mod task_manager;
@@ -70,6 +71,9 @@ impl SpawnServices for IpcReceivers {
// inbuxa: AL-5, end delegations at their `until`
inbuxa_lock_expiry::spawn_lock_expiry(inner.clone());
// inbuxa: personal-data catalog, D1: old log files go, per node
inbuxa_log_retention::spawn_log_retention(inner.clone());
// Spawn task scheduler
spawn_task_scheduler(inner);
}