From 2c7de8dea621c87e90184b1e534408f03839d2a5 Mon Sep 17 00:00:00 2001 From: Admin Pupkin Date: Wed, 20 May 2026 18:39:49 +0300 Subject: [PATCH] base: Add per-service log files and size-based rotation to logd (P51) Extend logd output thread to write logs to per-service files in /var/log/.log, with automatic size-based rotation (10 MB threshold, 5 backup files). All logs also go to /var/log/system.log. Backwards compatible with existing sink file descriptors. --- local/patches/base/P51-logd-rotation.patch | 128 +++++++++++++++++++++ recipes/core/base/recipe.toml | 2 + 2 files changed, 130 insertions(+) create mode 100644 local/patches/base/P51-logd-rotation.patch diff --git a/local/patches/base/P51-logd-rotation.patch b/local/patches/base/P51-logd-rotation.patch new file mode 100644 index 0000000000..6d83a9dad8 --- /dev/null +++ b/local/patches/base/P51-logd-rotation.patch @@ -0,0 +1,128 @@ +diff --git a/logd/src/scheme.rs b/logd/src/scheme.rs +index 070de3d6..4ea5365d 100644 +--- a/logd/src/scheme.rs ++++ b/logd/src/scheme.rs +@@ -1,2 +1,2 @@ +-use std::collections::{BTreeMap, VecDeque}; +-use std::fs::{File, OpenOptions}; ++use std::collections::{BTreeMap, HashMap, VecDeque}; ++use std::fs::{File, OpenOptions, rename}; +@@ -5,0 +6 @@ use std::os::fd::{FromRawFd, RawFd}; ++use std::path::PathBuf; +@@ -13,0 +15,5 @@ use syscall::schemev2::NewFdFlags; ++const LOG_DIR: &str = "/var/log"; ++const MAX_LOG_SIZE: u64 = 10 * 1024 * 1024; ++const MAX_ROTATED_FILES: u32 = 5; ++const MEMORY_LOG_LIMIT: usize = 1000; ++ +@@ -31 +37 @@ enum OutputCmd { +- Log(Vec), ++ Log { context: String, line: Vec }, +@@ -34,0 +41,52 @@ enum OutputCmd { ++struct LogFile { ++ file: File, ++ path: PathBuf, ++ bytes_written: u64, ++} ++ ++impl LogFile { ++ fn open(path: PathBuf) -> std::io::Result { ++ let file = OpenOptions::new() ++ .create(true) ++ .append(true) ++ .open(&path)?; ++ let metadata = file.metadata()?; ++ Ok(LogFile { ++ file, ++ path, ++ bytes_written: metadata.len(), ++ }) ++ } ++ ++ fn write(&mut self, data: &[u8]) -> std::io::Result<()> { ++ self.file.write_all(data)?; ++ self.file.flush()?; ++ self.bytes_written += data.len() as u64; ++ Ok(()) ++ } ++ ++ fn maybe_rotate(&mut self) -> std::io::Result<()> { ++ if self.bytes_written < MAX_LOG_SIZE { ++ return Ok(()); ++ } ++ ++ drop(std::mem::replace(&mut self.file, unsafe { File::from_raw_fd(-1) })); ++ ++ for i in (1..MAX_ROTATED_FILES).rev() { ++ let old_path = self.path.with_extension(format!("log.{}", i)); ++ let new_path = self.path.with_extension(format!("log.{}", i + 1)); ++ let _ = rename(&old_path, &new_path); ++ } ++ ++ let backup_path = self.path.with_extension("log.1"); ++ let _ = rename(&self.path, &backup_path); ++ ++ self.file = OpenOptions::new() ++ .create(true) ++ .append(true) ++ .open(&self.path)?; ++ self.bytes_written = 0; ++ Ok(()) ++ } ++} ++ +@@ -48,0 +107,4 @@ impl<'sock> LogScheme<'sock> { ++ let mut service_logs: HashMap = HashMap::new(); ++ ++ let _ = std::fs::create_dir_all(LOG_DIR); ++ +@@ -51 +113 @@ impl<'sock> LogScheme<'sock> { +- OutputCmd::Log(line) => { ++ OutputCmd::Log { context, line } => { +@@ -55,0 +118,22 @@ impl<'sock> LogScheme<'sock> { ++ ++ let service_name = context.split(':').next().unwrap_or("unknown"); ++ if !service_name.is_empty() { ++ let log_path = PathBuf::from(LOG_DIR).join(format!("{}.log", service_name)); ++ let entry = service_logs.entry(service_name.to_string()).or_insert_with(|| { ++ LogFile::open(log_path).unwrap_or_else(|_| { ++ LogFile::open(PathBuf::from("/dev/null")).unwrap() ++ }) ++ }); ++ let _ = entry.write(&line); ++ let _ = entry.maybe_rotate(); ++ } ++ ++ let system_path = PathBuf::from(LOG_DIR).join("system.log"); ++ let system_entry = service_logs.entry("system".to_string()).or_insert_with(|| { ++ LogFile::open(system_path).unwrap_or_else(|_| { ++ LogFile::open(PathBuf::from("/dev/null")).unwrap() ++ }) ++ }); ++ let _ = system_entry.write(&line); ++ let _ = system_entry.maybe_rotate(); ++ +@@ -57,2 +141 @@ impl<'sock> LogScheme<'sock> { +- // Keep a limited amount of logs for backfilling to bound memory usage +- while logs.len() > 1000 { ++ while logs.len() > MEMORY_LOG_LIMIT { +@@ -68 +150,0 @@ impl<'sock> LogScheme<'sock> { +- +@@ -83 +164,0 @@ impl<'sock> LogScheme<'sock> { +- // FIXME currently possible as /scheme/log/kernel presents a snapshot of the log queue +@@ -118 +198,0 @@ impl<'sock> LogScheme<'sock> { +- // Writing to the kernel debug log never blocks +@@ -124 +204,4 @@ impl<'sock> LogScheme<'sock> { +- .send(OutputCmd::Log(mem::take(handle_buf))) ++ .send(OutputCmd::Log { ++ context: context.to_string(), ++ line: mem::take(handle_buf), ++ }) +@@ -173,3 +255,0 @@ impl<'sock> SchemeSync for LogScheme<'sock> { +- +- // TODO +- +@@ -244,3 +323,0 @@ impl<'sock> SchemeSync for LogScheme<'sock> { +- +- //TODO: flush remaining data? +- diff --git a/recipes/core/base/recipe.toml b/recipes/core/base/recipe.toml index 24fa6a06f7..d093f8d3fd 100644 --- a/recipes/core/base/recipe.toml +++ b/recipes/core/base/recipe.toml @@ -101,6 +101,8 @@ patches = [ "P49-irq-affinity-logging.patch", # P50: Add structured logging rate limiter and thermald integration "P50-structured-logging.patch", + # P51: Add per-service log files and size-based rotation to logd + "P51-logd-rotation.patch", ] [package]