diff --git a/local/patches/base/P50-structured-logging.patch b/local/patches/base/P50-structured-logging.patch new file mode 100644 index 0000000000..c0f561802f --- /dev/null +++ b/local/patches/base/P50-structured-logging.patch @@ -0,0 +1,93 @@ +diff --git a/drivers/common/src/lib.rs b/drivers/common/src/lib.rs +index a55014b9..6b7ab2fe 100644 +--- a/drivers/common/src/lib.rs ++++ b/drivers/common/src/lib.rs +@@ -25 +25 @@ pub mod timeout; +-pub use logger::{file_level, output_level, setup_logging}; ++pub use logger::{file_level, output_level, setup_logging, RateLimitedLog}; +diff --git a/drivers/common/src/logger.rs b/drivers/common/src/logger.rs +index a531edd9..8b65f6bd 100644 +--- a/drivers/common/src/logger.rs ++++ b/drivers/common/src/logger.rs +@@ -0,0 +1,2 @@ ++use std::cell::RefCell; ++use std::collections::HashMap; +@@ -71,0 +74,65 @@ pub fn setup_logging( ++/// A simple per-message rate limiter to prevent log spam. ++/// ++/// Tracks the last emission time for each unique message key. If the same ++/// key is logged again within `interval`, the message is suppressed and a ++/// "last message repeated N times" warning is emitted instead. ++pub struct RateLimitedLog { ++ interval: std::time::Duration, ++ last_emission: RefCell>, ++ suppress_count: RefCell>, ++} ++ ++impl RateLimitedLog { ++ pub fn new(interval_secs: u64) -> Self { ++ Self { ++ interval: std::time::Duration::from_secs(interval_secs), ++ last_emission: RefCell::new(HashMap::new()), ++ suppress_count: RefCell::new(HashMap::new()), ++ } ++ } ++ ++ /// Log a message through the rate limiter. ++ pub fn log(&self, key: &str, log_fn: impl FnOnce()) { ++ let now = std::time::Instant::now(); ++ let mut last_map = self.last_emission.borrow_mut(); ++ let mut count_map = self.suppress_count.borrow_mut(); ++ ++ if let Some(last) = last_map.get(key) { ++ if now.duration_since(*last) < self.interval { ++ *count_map.entry(key.to_string()).or_insert(0) += 1; ++ return; ++ } ++ } ++ ++ if let Some(count) = count_map.remove(key) { ++ if count > 0 { ++ log::warn!("RateLimitedLog: last message '{}' repeated {} times", key, count); ++ } ++ } ++ ++ last_map.insert(key.to_string(), now); ++ log_fn(); ++ } ++} ++ ++/// Format a structured log message with key=value pairs. ++/// ++/// Example: `structured_log!("thermald", "event=temperature_read", "zone=CPU", "temp=45.2")` ++/// produces: `thermald: event=temperature_read zone=CPU temp=45.2` ++#[macro_export] ++macro_rules! structured_log { ++ ($source:expr, $($key:expr),+ $(,)?) => { ++ { ++ let mut msg = String::new(); ++ msg.push_str($source); ++ msg.push_str(": "); ++ $( ++ msg.push_str($key); ++ msg.push(' '); ++ )+ ++ msg.pop(); ++ log::info!("{}", msg); ++ } ++ }; ++} ++ +diff --git a/drivers/thermald/src/main.rs b/drivers/thermald/src/main.rs +index b8d271b5..e64cf162 100644 +--- a/drivers/thermald/src/main.rs ++++ b/drivers/thermald/src/main.rs +@@ -86,0 +87,2 @@ fn main() -> Result<()> { ++ let rate_limiter = common::RateLimitedLog::new(30); ++ +@@ -138 +140,4 @@ fn main() -> Result<()> { +- log::info!("thermald: max temp = {:.1}C from {}", max_temp, max_source); ++ let source = max_source.clone(); ++ rate_limiter.log(&format!("max_temp_{:.0}", max_temp), || { ++ log::info!("thermald: max temp = {:.1}C from {}", max_temp, source); ++ }); diff --git a/recipes/core/base/recipe.toml b/recipes/core/base/recipe.toml index 56a092478f..24fa6a06f7 100644 --- a/recipes/core/base/recipe.toml +++ b/recipes/core/base/recipe.toml @@ -99,6 +99,8 @@ patches = [ "P48-acpid-fan-support.patch", # P49: Add IRQ affinity logging and CPU tracking to pcid "P49-irq-affinity-logging.patch", + # P50: Add structured logging rate limiter and thermald integration + "P50-structured-logging.patch", ] [package]