//! Shared non-blocking file appender + worker-guard registry for telemetry file-log layers. use std::path::Path; use std::sync::{Mutex, OnceLock}; use tracing_appender::non_blocking::{NonBlocking, WorkerGuard}; // Park every worker guard for process lifetime; dropping a guard flushes and // shuts down that file's writer thread, so accumulate (never overwrite) to let // multiple file-log layers coexist. static FILE_LOG_GUARDS: OnceLock>> = OnceLock::new(); /// Shared non-blocking file writer for telemetry file-log layers. Opens `path` /// in append mode and parks the worker guard for process lifetime so buffered /// logs aren't lost. Sibling loggers (hooks/memory/sampling/instrumentation) can /// migrate onto this in a follow-up. pub(crate) fn non_blocking_file_writer(path: &Path) -> std::io::Result { if let Some(parent) = path.parent() { let _ = std::fs::create_dir_all(parent); } let file = std::fs::OpenOptions::new() .create(true) .append(true) .open(path)?; let (non_blocking, guard) = tracing_appender::non_blocking(file); let guards = FILE_LOG_GUARDS.get_or_init(|| Mutex::new(Vec::new())); // Recover from a poisoned mutex so the guard is always parked; dropping it // would shut down the writer thread and silently lose buffered logs. let mut guards = guards .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); guards.push(guard); Ok(non_blocking) } /// Drop all parked worker guards, flushing their non-blocking writers. Call at /// process exit so short-lived runs (e.g. headless `kigi -p`) don't lose buffered logs. pub(crate) fn flush_file_log_guards() { if let Some(m) = FILE_LOG_GUARDS.get() { // Recover from a poisoned mutex so exit-flush still drains the guards. let mut guards = m.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); guards.clear(); // dropping each WorkerGuard flushes + joins its writer thread } }