From 7c64ee002f741d69c3f8b70845c03e1e1e1defd9 Mon Sep 17 00:00:00 2001 From: Dirreke Date: Mon, 4 Mar 2024 10:54:28 +0800 Subject: [PATCH 1/3] Update time.rs --- .../policy/compound/trigger/time.rs | 112 +++++++++--------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/src/append/rolling_file/policy/compound/trigger/time.rs b/src/append/rolling_file/policy/compound/trigger/time.rs index 4568a524..ffad356f 100644 --- a/src/append/rolling_file/policy/compound/trigger/time.rs +++ b/src/append/rolling_file/policy/compound/trigger/time.rs @@ -12,7 +12,7 @@ use rand::Rng; use serde::de; #[cfg(feature = "config_parsing")] use std::fmt; -use std::sync::RwLock; +use std::sync::{Once, RwLock}; use crate::append::rolling_file::{policy::compound::trigger::Trigger, LogFile}; #[cfg(feature = "config_parsing")] @@ -23,27 +23,27 @@ use crate::config::{Deserialize, Deserializers}; #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct TimeTriggerConfig { + /// The date/time interval between log file rolls. interval: TimeTriggerInterval, + /// Whether to modulate the interval. #[serde(default)] modulate: bool, + /// The maximum random delay in seconds. #[serde(default)] max_random_delay: u64, } -#[cfg(not(feature = "config_parsing"))] -/// Configuration for the time trigger. -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] -pub struct TimeTriggerConfig { - interval: TimeTriggerInterval, - modulate: bool, - max_random_delay: u64, -} - /// A trigger which rolls the log once it has passed a certain time. #[derive(Debug)] pub struct TimeTrigger { - config: TimeTriggerConfig, + /// The date/time interval between log file rolls. + interval: TimeTriggerInterval, + /// Whether to modulate the interval. + modulate: bool, + /// The maximum random delay in seconds. + max_random_delay: u64, next_roll_time: RwLock>, + initial: Once, } /// The TimeTrigger supports the following units (case insensitive): @@ -175,31 +175,17 @@ impl<'de> serde::Deserialize<'de> for TimeTriggerInterval { impl TimeTrigger { /// Returns a new trigger which rolls the log once it has passed the /// specified time. - pub fn new(config: TimeTriggerConfig) -> TimeTrigger { - #[cfg(test)] - let current = { - let now: std::time::Duration = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("system time before Unix epoch"); - NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()) - .unwrap() - .and_local_timezone(Local) - .unwrap() - }; - - #[cfg(not(test))] - let current = Local::now(); - let next_time = TimeTrigger::get_next_time(current, config.interval, config.modulate); - let next_roll_time = if config.max_random_delay > 0 { - let random_delay = rand::thread_rng().gen_range(0..config.max_random_delay); - next_time + Duration::seconds(random_delay as i64) - } else { - next_time - }; - + pub fn new( + interval: TimeTriggerInterval, + modulate: bool, + max_random_delay: u64, + ) -> TimeTrigger { TimeTrigger { - config, - next_roll_time: RwLock::new(next_roll_time), + interval, + modulate, + max_random_delay, + next_roll_time: RwLock::default(), + initial: Once::new(), } } @@ -274,10 +260,37 @@ impl TimeTrigger { } panic!("Should not reach here!"); } + + fn refresh_time(&self) { + #[cfg(test)] + let current = { + let now: std::time::Duration = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time before Unix epoch"); + NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()) + .unwrap() + .and_local_timezone(Local) + .unwrap() + }; + + #[cfg(not(test))] + let current = Local::now(); + let next_time = TimeTrigger::get_next_time(current, self.interval, self.modulate); + let next_roll_time = if self.max_random_delay > 0 { + let random_delay = rand::thread_rng().gen_range(0..self.max_random_delay); + next_time + Duration::seconds(random_delay as i64) + } else { + next_time + }; + *self.next_roll_time.write().unwrap() = next_roll_time; + } } impl Trigger for TimeTrigger { fn trigger(&self, _file: &LogFile) -> anyhow::Result { + self.initial.call_once(|| { + self.refresh_time(); + }); #[cfg(test)] let current = { let now = SystemTime::now() @@ -291,12 +304,11 @@ impl Trigger for TimeTrigger { #[cfg(not(test))] let current: DateTime = Local::now(); - let mut next_roll_time = self.next_roll_time.write().unwrap(); + let next_roll_time = self.next_roll_time.read().unwrap(); let is_trigger = current >= *next_roll_time; + drop(next_roll_time); if is_trigger { - let tmp = TimeTrigger::new(self.config); - let time_new = tmp.next_roll_time.read().unwrap(); - *next_roll_time = *time_new; + self.refresh_time(); } Ok(is_trigger) } @@ -333,7 +345,11 @@ impl Deserialize for TimeTriggerDeserializer { config: TimeTriggerConfig, _: &Deserializers, ) -> anyhow::Result> { - Ok(Box::new(TimeTrigger::new(config))) + Ok(Box::new(TimeTrigger::new( + config.interval, + config.modulate, + config.max_random_delay, + ))) } } @@ -355,13 +371,8 @@ mod test { len: 0, }; - let config = TimeTriggerConfig { - interval, - modulate, - max_random_delay: 0, - }; - - let trigger = TimeTrigger::new(config); + let trigger = TimeTrigger::new(interval, modulate, 0); + trigger.trigger(&logfile).unwrap(); MockClock::advance_system_time(Duration::from_millis(millis / 2)); let result1 = trigger.trigger(&logfile).unwrap(); @@ -485,12 +496,7 @@ mod test { #[test] fn pre_process() { - let config = TimeTriggerConfig { - interval: TimeTriggerInterval::Minute(2), - modulate: true, - max_random_delay: 0, - }; - let trigger = TimeTrigger::new(config); + let trigger = TimeTrigger::new(TimeTriggerInterval::Minute(2), true, 0); assert!(trigger.is_pre_process()); } } From 0e56e1807e78dfeece5e6ae3db282b40389cb0fd Mon Sep 17 00:00:00 2001 From: Dirreke Date: Mon, 11 Mar 2024 14:08:32 +0800 Subject: [PATCH 2/3] bump chrono --- Cargo.toml | 2 +- .../policy/compound/trigger/time.rs | 87 ++++++++++++++----- 2 files changed, 68 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fbc77abd..85932203 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ harness = false [dependencies] arc-swap = "1.6" -chrono = { version = "0.4.23", optional = true, features = ["clock"], default-features = false } +chrono = { version = "0.4.35", optional = true, features = ["clock"], default-features = false } flate2 = { version = "1.0", optional = true } fnv = "1.0" humantime = { version = "2.1", optional = true } diff --git a/src/append/rolling_file/policy/compound/trigger/time.rs b/src/append/rolling_file/policy/compound/trigger/time.rs index ffad356f..2d2d6234 100644 --- a/src/append/rolling_file/policy/compound/trigger/time.rs +++ b/src/append/rolling_file/policy/compound/trigger/time.rs @@ -2,8 +2,6 @@ //! //! Requires the `time_trigger` feature. -#[cfg(test)] -use chrono::NaiveDateTime; use chrono::{DateTime, Datelike, Duration, Local, TimeZone, Timelike}; #[cfg(test)] use mock_instant::{SystemTime, UNIX_EPOCH}; @@ -13,6 +11,7 @@ use serde::de; #[cfg(feature = "config_parsing")] use std::fmt; use std::sync::{Once, RwLock}; +use thiserror::Error; use crate::append::rolling_file::{policy::compound::trigger::Trigger, LogFile}; #[cfg(feature = "config_parsing")] @@ -73,6 +72,12 @@ impl Default for TimeTriggerInterval { } } +#[derive(Debug, Error)] +enum TimeTrigerError { + #[error("too large interval in time trigger {0:?}")] + TooLargeInterval(TimeTriggerInterval), +} + #[cfg(feature = "config_parsing")] impl<'de> serde::Deserialize<'de> for TimeTriggerInterval { fn deserialize(d: D) -> Result @@ -193,13 +198,14 @@ impl TimeTrigger { current: DateTime, interval: TimeTriggerInterval, modulate: bool, - ) -> DateTime { + ) -> anyhow::Result> { let year = current.year(); if let TimeTriggerInterval::Year(n) = interval { let n = n as i32; let increment = if modulate { n - year % n } else { n }; let year_new = year + increment; - return Local.with_ymd_and_hms(year_new, 1, 1, 0, 0, 0).unwrap(); + let result = Local.with_ymd_and_hms(year_new, 1, 1, 0, 0, 0).unwrap(); + return Ok(result); } if let TimeTriggerInterval::Month(n) = interval { @@ -210,9 +216,10 @@ impl TimeTrigger { let num_months_new = num_months + increment; let year_new = (num_months_new / 12) as i32; let month_new = (num_months_new) % 12 + 1; - return Local + let result = Local .with_ymd_and_hms(year_new, month_new, 1, 0, 0, 0) .unwrap(); + return Ok(result); } let month = current.month(); @@ -222,14 +229,21 @@ impl TimeTrigger { let weekday = current.weekday().num_days_from_monday() as i64; // Monday is the first day of the week let time = Local.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap(); let increment = if modulate { n - week0 % n } else { n }; - return time + Duration::weeks(increment) - Duration::days(weekday); + let result = time + + Duration::try_weeks(increment) + .ok_or(TimeTrigerError::TooLargeInterval(interval))? + - Duration::try_days(weekday).unwrap(); + return Ok(result); } if let TimeTriggerInterval::Day(n) = interval { let ordinal0 = current.ordinal0() as i64; let time = Local.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap(); let increment = if modulate { n - ordinal0 % n } else { n }; - return time + Duration::days(increment); + let result = time + + Duration::try_days(increment) + .ok_or(TimeTrigerError::TooLargeInterval(interval))?; + return Ok(result); } let hour = current.hour(); @@ -238,7 +252,10 @@ impl TimeTrigger { .with_ymd_and_hms(year, month, day, hour, 0, 0) .unwrap(); let increment = if modulate { n - (hour as i64) % n } else { n }; - return time + Duration::hours(increment); + let result = time + + Duration::try_hours(increment) + .ok_or(TimeTrigerError::TooLargeInterval(interval))?; + return Ok(result); } let min = current.minute(); @@ -247,7 +264,10 @@ impl TimeTrigger { .with_ymd_and_hms(year, month, day, hour, min, 0) .unwrap(); let increment = if modulate { n - (min as i64) % n } else { n }; - return time + Duration::minutes(increment); + let result = time + + Duration::try_minutes(increment) + .ok_or(TimeTrigerError::TooLargeInterval(interval))?; + return Ok(result); } let sec = current.second(); @@ -256,48 +276,56 @@ impl TimeTrigger { .with_ymd_and_hms(year, month, day, hour, min, sec) .unwrap(); let increment = if modulate { n - (sec as i64) % n } else { n }; - return time + Duration::seconds(increment); + let result = time + + Duration::try_seconds(increment) + .ok_or(TimeTrigerError::TooLargeInterval(interval))?; + return Ok(result); } panic!("Should not reach here!"); } - fn refresh_time(&self) { + fn refresh_time(&self) -> anyhow::Result<()> { #[cfg(test)] let current = { let now: std::time::Duration = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("system time before Unix epoch"); - NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()) + DateTime::from_timestamp(now.as_secs() as i64, now.subsec_nanos()) .unwrap() + .naive_local() .and_local_timezone(Local) .unwrap() }; #[cfg(not(test))] let current = Local::now(); - let next_time = TimeTrigger::get_next_time(current, self.interval, self.modulate); + let next_time = TimeTrigger::get_next_time(current, self.interval, self.modulate)?; let next_roll_time = if self.max_random_delay > 0 { let random_delay = rand::thread_rng().gen_range(0..self.max_random_delay); - next_time + Duration::seconds(random_delay as i64) + next_time + + Duration::try_seconds(random_delay as i64) + .unwrap_or(Duration::try_milliseconds(i64::MAX).unwrap()) } else { next_time }; *self.next_roll_time.write().unwrap() = next_roll_time; + Ok(()) } } impl Trigger for TimeTrigger { fn trigger(&self, _file: &LogFile) -> anyhow::Result { - self.initial.call_once(|| { - self.refresh_time(); - }); + let mut result = anyhow::Result::Ok(()); + self.initial.call_once(|| result = self.refresh_time()); + result?; #[cfg(test)] let current = { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("system time before Unix epoch"); - NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()) + DateTime::from_timestamp(now.as_secs() as i64, now.subsec_nanos()) .unwrap() + .naive_local() .and_local_timezone(Local) .unwrap() }; @@ -308,7 +336,7 @@ impl Trigger for TimeTrigger { let is_trigger = current >= *next_roll_time; drop(next_roll_time); if is_trigger { - self.refresh_time(); + self.refresh_time()?; } Ok(is_trigger) } @@ -357,7 +385,7 @@ impl Deserialize for TimeTriggerDeserializer { mod test { use super::*; use mock_instant::MockClock; - use std::time::Duration; + use std::{error::Error as StdError, time::Duration}; fn trigger_with_time_and_modulate( interval: TimeTriggerInterval, @@ -494,6 +522,25 @@ mod test { assert_eq!(interval, TimeTriggerInterval::Second(1)); } + #[test] + fn trigger_large_interval() { + let interval = TimeTriggerInterval::Second(i64::MAX); + let file = tempfile::tempdir().unwrap(); + let logfile = LogFile { + writer: &mut None, + path: file.path(), + len: 0, + }; + + let trigger = TimeTrigger::new(interval, false, 0); + let error = trigger.trigger(&logfile).unwrap_err(); + let box_dyn = Box::::from(error); + assert_eq!( + format!("too large interval in time trigger {:?}", interval), + box_dyn.to_string() + ); + } + #[test] fn pre_process() { let trigger = TimeTrigger::new(TimeTriggerInterval::Minute(2), true, 0); From d0d35b84101336867e8abcd7114b38395874dc2a Mon Sep 17 00:00:00 2001 From: Dirreke Date: Tue, 2 Apr 2024 18:57:49 +0800 Subject: [PATCH 3/3] update time.rs --- .../policy/compound/trigger/time.rs | 114 +++++++++--------- 1 file changed, 60 insertions(+), 54 deletions(-) diff --git a/src/append/rolling_file/policy/compound/trigger/time.rs b/src/append/rolling_file/policy/compound/trigger/time.rs index 2d2d6234..32e06eb6 100644 --- a/src/append/rolling_file/policy/compound/trigger/time.rs +++ b/src/append/rolling_file/policy/compound/trigger/time.rs @@ -17,6 +17,12 @@ use crate::append::rolling_file::{policy::compound::trigger::Trigger, LogFile}; #[cfg(feature = "config_parsing")] use crate::config::{Deserialize, Deserializers}; +macro_rules! try_from { + ($func: ident, $para: expr, $interval: expr) => { + Duration::$func($para).ok_or(TimeTrigerError::TooLargeInterval($interval))? + }; +} + #[cfg(feature = "config_parsing")] /// Configuration for the time trigger. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default, serde::Deserialize)] @@ -32,15 +38,22 @@ pub struct TimeTriggerConfig { max_random_delay: u64, } +#[cfg(not(feature = "config_parsing"))] +/// Configuration for the time trigger. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] +pub struct TimeTriggerConfig { + /// The date/time interval between log file rolls.Q + pub interval: TimeTriggerInterval, + /// Whether to modulate the interval. + pub modulate: bool, + /// The maximum random delay in seconds. + pub max_random_delay: u64, +} + /// A trigger which rolls the log once it has passed a certain time. #[derive(Debug)] pub struct TimeTrigger { - /// The date/time interval between log file rolls. - interval: TimeTriggerInterval, - /// Whether to modulate the interval. - modulate: bool, - /// The maximum random delay in seconds. - max_random_delay: u64, + config: TimeTriggerConfig, next_roll_time: RwLock>, initial: Once, } @@ -74,7 +87,7 @@ impl Default for TimeTriggerInterval { #[derive(Debug, Error)] enum TimeTrigerError { - #[error("too large interval in time trigger {0:?}")] + #[error("The integer value {0:?} for the specified time trigger interval is too large, it must be less than 9,223,372,036,854,775,807 seconds.")] TooLargeInterval(TimeTriggerInterval), } @@ -180,25 +193,18 @@ impl<'de> serde::Deserialize<'de> for TimeTriggerInterval { impl TimeTrigger { /// Returns a new trigger which rolls the log once it has passed the /// specified time. - pub fn new( - interval: TimeTriggerInterval, - modulate: bool, - max_random_delay: u64, - ) -> TimeTrigger { + pub fn new(config: TimeTriggerConfig) -> TimeTrigger { TimeTrigger { - interval, - modulate, - max_random_delay, + config, next_roll_time: RwLock::default(), initial: Once::new(), } } - fn get_next_time( - current: DateTime, - interval: TimeTriggerInterval, - modulate: bool, - ) -> anyhow::Result> { + fn get_next_time(&self, current: DateTime) -> Result, TimeTrigerError> { + let interval = self.config.interval; + let modulate = self.config.modulate; + let year = current.year(); if let TimeTriggerInterval::Year(n) = interval { let n = n as i32; @@ -229,21 +235,17 @@ impl TimeTrigger { let weekday = current.weekday().num_days_from_monday() as i64; // Monday is the first day of the week let time = Local.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap(); let increment = if modulate { n - week0 % n } else { n }; - let result = time - + Duration::try_weeks(increment) - .ok_or(TimeTrigerError::TooLargeInterval(interval))? - - Duration::try_days(weekday).unwrap(); - return Ok(result); + let dur = + try_from!(try_weeks, increment, interval) - try_from!(try_days, weekday, interval); + return Ok(time + dur); } if let TimeTriggerInterval::Day(n) = interval { let ordinal0 = current.ordinal0() as i64; let time = Local.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap(); let increment = if modulate { n - ordinal0 % n } else { n }; - let result = time - + Duration::try_days(increment) - .ok_or(TimeTrigerError::TooLargeInterval(interval))?; - return Ok(result); + let dur = try_from!(try_days, increment, interval); + return Ok(time + dur); } let hour = current.hour(); @@ -252,10 +254,8 @@ impl TimeTrigger { .with_ymd_and_hms(year, month, day, hour, 0, 0) .unwrap(); let increment = if modulate { n - (hour as i64) % n } else { n }; - let result = time - + Duration::try_hours(increment) - .ok_or(TimeTrigerError::TooLargeInterval(interval))?; - return Ok(result); + let dur = try_from!(try_hours, increment, interval); + return Ok(time + dur); } let min = current.minute(); @@ -264,10 +264,8 @@ impl TimeTrigger { .with_ymd_and_hms(year, month, day, hour, min, 0) .unwrap(); let increment = if modulate { n - (min as i64) % n } else { n }; - let result = time - + Duration::try_minutes(increment) - .ok_or(TimeTrigerError::TooLargeInterval(interval))?; - return Ok(result); + let dur = try_from!(try_minutes, increment, interval); + return Ok(time + dur); } let sec = current.second(); @@ -276,15 +274,13 @@ impl TimeTrigger { .with_ymd_and_hms(year, month, day, hour, min, sec) .unwrap(); let increment = if modulate { n - (sec as i64) % n } else { n }; - let result = time - + Duration::try_seconds(increment) - .ok_or(TimeTrigerError::TooLargeInterval(interval))?; - return Ok(result); + let dur = try_from!(try_seconds, increment, interval); + return Ok(time + dur); } panic!("Should not reach here!"); } - fn refresh_time(&self) -> anyhow::Result<()> { + fn refresh_time(&self) -> Result<(), TimeTrigerError> { #[cfg(test)] let current = { let now: std::time::Duration = SystemTime::now() @@ -299,9 +295,9 @@ impl TimeTrigger { #[cfg(not(test))] let current = Local::now(); - let next_time = TimeTrigger::get_next_time(current, self.interval, self.modulate)?; - let next_roll_time = if self.max_random_delay > 0 { - let random_delay = rand::thread_rng().gen_range(0..self.max_random_delay); + let next_time = self.get_next_time(current)?; + let next_roll_time = if self.config.max_random_delay > 0 { + let random_delay = rand::thread_rng().gen_range(0..self.config.max_random_delay); next_time + Duration::try_seconds(random_delay as i64) .unwrap_or(Duration::try_milliseconds(i64::MAX).unwrap()) @@ -373,11 +369,7 @@ impl Deserialize for TimeTriggerDeserializer { config: TimeTriggerConfig, _: &Deserializers, ) -> anyhow::Result> { - Ok(Box::new(TimeTrigger::new( - config.interval, - config.modulate, - config.max_random_delay, - ))) + Ok(Box::new(TimeTrigger::new(config))) } } @@ -398,8 +390,13 @@ mod test { path: file.path(), len: 0, }; + let config = TimeTriggerConfig { + interval, + modulate, + max_random_delay: 0, + }; - let trigger = TimeTrigger::new(interval, modulate, 0); + let trigger = TimeTrigger::new(config); trigger.trigger(&logfile).unwrap(); MockClock::advance_system_time(Duration::from_millis(millis / 2)); @@ -531,19 +528,28 @@ mod test { path: file.path(), len: 0, }; + let config = TimeTriggerConfig { + interval, + ..Default::default() + }; - let trigger = TimeTrigger::new(interval, false, 0); + let trigger = TimeTrigger::new(config); let error = trigger.trigger(&logfile).unwrap_err(); let box_dyn = Box::::from(error); assert_eq!( - format!("too large interval in time trigger {:?}", interval), + format!("The integer value {:?} for the specified time trigger interval is too large, it must be less than 9,223,372,036,854,775,807 seconds.", interval), box_dyn.to_string() ); } #[test] fn pre_process() { - let trigger = TimeTrigger::new(TimeTriggerInterval::Minute(2), true, 0); + let config = TimeTriggerConfig { + interval: TimeTriggerInterval::Minute(2), + modulate: true, + max_random_delay: 0, + }; + let trigger = TimeTrigger::new(config); assert!(trigger.is_pre_process()); } }