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); + });