diff --git a/Cargo.toml b/Cargo.toml index 8477cd7c..119b3a98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,7 @@ lazy_static = "1.4" streaming-stats = "0.2.3" humantime = "2.1" tempfile = "3.8" +mock_instant = "0.3" [[example]] name = "json_logger" diff --git a/src/append/rolling_file/mod.rs b/src/append/rolling_file/mod.rs index 6160142f..360cfa6e 100644 --- a/src/append/rolling_file/mod.rs +++ b/src/append/rolling_file/mod.rs @@ -390,8 +390,8 @@ appenders: path: {0}/foo.log policy: trigger: - kind: size - limit: 1024 + kind: time + limit: 2 minutes roller: kind: delete bar: diff --git a/src/append/rolling_file/policy/compound/trigger/size.rs b/src/append/rolling_file/policy/compound/trigger/size.rs index d1e8b4a5..d399cb2f 100644 --- a/src/append/rolling_file/policy/compound/trigger/size.rs +++ b/src/append/rolling_file/policy/compound/trigger/size.rs @@ -153,3 +153,14 @@ impl Deserialize for SizeTriggerDeserializer { Ok(Box::new(SizeTrigger::new(config.limit))) } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn pre_process() { + let trigger = SizeTrigger::new(2048); + assert!(!trigger.is_pre_process()); + } +} diff --git a/src/append/rolling_file/policy/compound/trigger/time.rs b/src/append/rolling_file/policy/compound/trigger/time.rs index 9e5ab737..0e031e48 100644 --- a/src/append/rolling_file/policy/compound/trigger/time.rs +++ b/src/append/rolling_file/policy/compound/trigger/time.rs @@ -3,6 +3,10 @@ //! Requires the `time_trigger` feature. use chrono::{DateTime, Datelike, Duration, Local, TimeZone, Timelike}; +#[cfg(test)] +use chrono::{NaiveDateTime, Utc}; +#[cfg(test)] +use mock_instant::{SystemTime, UNIX_EPOCH}; #[cfg(feature = "config_parsing")] use serde::de; #[cfg(feature = "config_parsing")] @@ -145,6 +149,18 @@ impl TimeTrigger { /// Returns a new trigger which rolls the log once it has passed the /// specified time. pub fn new(limit: TimeTriggerLimit) -> TimeTrigger { + #[cfg(test)] + let time = { + let now: std::time::Duration = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time before Unix epoch"); + let naive_time = + NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()) + .unwrap(); + Utc.from_utc_datetime(&naive_time) + }; + + #[cfg(not(test))] let time = Local::now(); let year = time.year(); let month = time.month(); @@ -152,9 +168,12 @@ impl TimeTrigger { let weekday = time.weekday(); let hour = time.hour(); let min = time.minute(); + let sec = time.second(); let time_new = match limit { - TimeTriggerLimit::Second(_) => time, + TimeTriggerLimit::Second(_) => Local + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap(), TimeTriggerLimit::Minute(_) => Local .with_ymd_and_hms(year, month, day, hour, min, 0) .unwrap(), @@ -179,7 +198,19 @@ impl TimeTrigger { impl Trigger for TimeTrigger { fn trigger(&self, _file: &LogFile) -> anyhow::Result { - let time_now = Local::now(); + #[cfg(test)] + let time_now = { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time before Unix epoch"); + let naive_time = + NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()) + .unwrap(); + Utc.from_utc_datetime(&naive_time) + }; + + #[cfg(not(test))] + let time_now: DateTime = Local::now(); let mut time_start = self.time_start.write().unwrap(); let duration = time_now.signed_duration_since(*time_start); let is_triger = match self.limit { @@ -223,9 +254,9 @@ impl Trigger for TimeTrigger { /// ```yaml /// kind: time /// -/// # The time limit in second. The following units are supported (case insensitive): -/// # "second", "seconds", "minute", "minutes", "hour", "hours", "day", "days", "week", "weeks", "month", "months", "year", "years". The unit defaults to -/// # second if not specified. Required. +/// # The time limit in seconds. The following units are supported (case insensitive): +/// # "second(s)", "minute(s)", "hour(s)", "day(s)", "week(s)", "month(s)", "year(s)". The unit defaults to +/// # second if not specified. /// limit: 7 day /// ``` #[cfg(feature = "config_parsing")] @@ -250,25 +281,236 @@ impl Deserialize for TimeTriggerDeserializer { #[cfg(test)] mod test { use super::*; + use mock_instant::MockClock; + use std::time::Duration; - #[test] - fn trigger() { + fn trigger_with_time(limit: TimeTriggerLimit, millis: u64) -> (bool, bool) { let file = tempfile::tempdir().unwrap(); let logfile = LogFile { writer: &mut None, path: file.path(), len: 0, }; - let trigger = TimeTrigger::new(TimeTriggerLimit::Second(10)); - let result = trigger.trigger(&logfile).unwrap(); - assert_eq!(false, result); - std::thread::sleep(std::time::Duration::from_secs(12)); - let result = trigger.trigger(&logfile).unwrap(); - assert_eq!(true, result); - let result = trigger.trigger(&logfile).unwrap(); - assert_eq!(false, result); - std::thread::sleep(std::time::Duration::from_secs(12)); - let result = trigger.trigger(&logfile).unwrap(); - assert_eq!(true, result); + + let trigger = TimeTrigger::new(limit); + + MockClock::advance_system_time(Duration::from_millis(millis / 2)); + let result1 = trigger.trigger(&logfile).unwrap(); + + MockClock::advance_system_time(Duration::from_millis(millis / 2)); + let result2 = trigger.trigger(&logfile).unwrap(); + + (result1, result2) + } + + #[test] + fn trigger() { + // Second + let second_in_milli = 1000; + let minute_in_milli = second_in_milli * 60; + let hour_in_milli = minute_in_milli * 60; + let day_in_milli = hour_in_milli * 24; + let week_in_milli = day_in_milli * 7; + let month_in_milli = day_in_milli * 31; + let year_in_milli = day_in_milli * 365; + MockClock::set_system_time(Duration::from_millis(0)); + assert_eq!( + trigger_with_time(TimeTriggerLimit::Second(1), second_in_milli), + (false, true) + ); + // Minute + MockClock::set_system_time(Duration::from_millis(0)); + assert_eq!( + trigger_with_time(TimeTriggerLimit::Minute(1), minute_in_milli), + (false, true) + ); + // Hour + MockClock::set_system_time(Duration::from_millis(0)); + assert_eq!( + trigger_with_time(TimeTriggerLimit::Hour(1), hour_in_milli), + (false, true) + ); + // Day + MockClock::set_system_time(Duration::from_millis(0)); + assert_eq!( + trigger_with_time(TimeTriggerLimit::Day(1), day_in_milli), + (false, true) + ); + // Week + MockClock::set_system_time(Duration::from_millis(4 * day_in_milli)); // Monday + assert_eq!( + trigger_with_time(TimeTriggerLimit::Week(1), week_in_milli), + (false, true) + ); + // Month + MockClock::set_system_time(Duration::from_millis(0)); + assert_eq!( + trigger_with_time(TimeTriggerLimit::Month(1), month_in_milli), + (false, true) + ); + // Year + MockClock::set_system_time(Duration::from_millis(0)); + assert_eq!( + trigger_with_time(TimeTriggerLimit::Year(1), year_in_milli), + (false, true) + ); + } + + #[test] + fn trigger2() { + // This test may fail if it runs in the following timezones (UTC+12, UTC-12) + // Second + let second_in_milli = 1000; + let minute_in_milli = second_in_milli * 60; + let hour_in_milli = minute_in_milli * 60; + let day_in_milli = hour_in_milli * 24; + let week_in_milli = day_in_milli * 7; + let month_in_milli = day_in_milli * 31; + let year_in_milli = day_in_milli * 365; + MockClock::set_system_time(Duration::from_millis(second_in_milli / 2)); + assert_eq!( + trigger_with_time(TimeTriggerLimit::Second(1), second_in_milli), + (true, false) + ); + // Minute + MockClock::set_system_time(Duration::from_millis(minute_in_milli / 2)); + assert_eq!( + trigger_with_time(TimeTriggerLimit::Minute(1), minute_in_milli), + (true, false) + ); + // Hour + MockClock::set_system_time(Duration::from_millis(hour_in_milli / 2)); + assert_eq!( + trigger_with_time(TimeTriggerLimit::Hour(1), hour_in_milli), + (true, false) + ); + // Day + MockClock::set_system_time(Duration::from_millis(day_in_milli / 2)); + assert_eq!( + trigger_with_time(TimeTriggerLimit::Day(1), day_in_milli), + (true, false) + ); + // Week + MockClock::set_system_time(Duration::from_millis(4 * day_in_milli + week_in_milli / 2)); // Monday + assert_eq!( + trigger_with_time(TimeTriggerLimit::Week(1), week_in_milli), + (true, false) + ); + // Month + MockClock::set_system_time(Duration::from_millis(month_in_milli / 2)); + assert_eq!( + trigger_with_time(TimeTriggerLimit::Month(1), month_in_milli), + (true, false) + ); + // Year + MockClock::set_system_time(Duration::from_millis(year_in_milli / 2)); + assert_eq!( + trigger_with_time(TimeTriggerLimit::Year(1), year_in_milli), + (true, false) + ); + } + + #[test] + #[cfg(feature = "yaml_format")] + fn test_serde() { + // str none + let limit = format!("limit: abc",); + let error = ::serde_yaml::from_str::(&limit); + assert!(error.is_err()); + + // none + let limit = format!("limit: ",); + let error = ::serde_yaml::from_str::(&limit); + assert!(error.is_err()); + + // bad unit + let limit = format!("limit: 5 das",); + let error = ::serde_yaml::from_str::(&limit); + assert!(error.is_err()); + + // i64 + let limit = format!("limit: -1",); + let error = ::serde_yaml::from_str::(&limit); + assert!(error.is_err()); + + // u64 + let limit = format!("limit: 1",); + let limit = ::serde_yaml::from_str::(&limit).unwrap(); + assert_eq!(limit.limit, TimeTriggerLimit::Second(1)); + + // str second + let limit = format!("limit: 1 second",); + let limit = ::serde_yaml::from_str::(&limit).unwrap(); + assert_eq!(limit.limit, TimeTriggerLimit::Second(1)); + + let limit = format!("limit: 1 seconds",); + let limit = ::serde_yaml::from_str::(&limit).unwrap(); + assert_eq!(limit.limit, TimeTriggerLimit::Second(1)); + + // str minute + let limit = format!("limit: 1 minute",); + let limit = ::serde_yaml::from_str::(&limit).unwrap(); + assert_eq!(limit.limit, TimeTriggerLimit::Minute(1)); + + let limit = format!("limit: 1 minutes",); + let limit = ::serde_yaml::from_str::(&limit).unwrap(); + assert_eq!(limit.limit, TimeTriggerLimit::Minute(1)); + + // str hour + let limit = format!("limit: 1 hour",); + let limit = ::serde_yaml::from_str::(&limit).unwrap(); + assert_eq!(limit.limit, TimeTriggerLimit::Hour(1)); + + let limit = format!("limit: 1 hours",); + let limit = ::serde_yaml::from_str::(&limit).unwrap(); + assert_eq!(limit.limit, TimeTriggerLimit::Hour(1)); + + // str day + let limit = format!("limit: 1 day",); + let limit = ::serde_yaml::from_str::(&limit).unwrap(); + assert_eq!(limit.limit, TimeTriggerLimit::Day(1)); + + let limit = format!("limit: 1 days",); + let limit = ::serde_yaml::from_str::(&limit).unwrap(); + assert_eq!(limit.limit, TimeTriggerLimit::Day(1)); + + // str week + let limit = format!("limit: 1 week",); + let limit = ::serde_yaml::from_str::(&limit).unwrap(); + assert_eq!(limit.limit, TimeTriggerLimit::Week(1)); + + let limit = format!("limit: 1 weeks",); + let limit = ::serde_yaml::from_str::(&limit).unwrap(); + assert_eq!(limit.limit, TimeTriggerLimit::Week(1)); + + // str month + let limit = format!("limit: 1 month",); + let limit = ::serde_yaml::from_str::(&limit).unwrap(); + assert_eq!(limit.limit, TimeTriggerLimit::Month(1)); + + let limit = format!("limit: 1 months",); + let limit = ::serde_yaml::from_str::(&limit).unwrap(); + assert_eq!(limit.limit, TimeTriggerLimit::Month(1)); + + // str year + let limit = format!("limit: 1 year",); + let limit = ::serde_yaml::from_str::(&limit).unwrap(); + assert_eq!(limit.limit, TimeTriggerLimit::Year(1)); + + let limit = format!("limit: 1 years",); + let limit = ::serde_yaml::from_str::(&limit).unwrap(); + assert_eq!(limit.limit, TimeTriggerLimit::Year(1)); + } + + #[test] + fn test_time_trigger_limit_default() { + let limit = TimeTriggerLimit::default(); + assert_eq!(limit, TimeTriggerLimit::Week(1)); + } + + #[test] + fn pre_process() { + let trigger = TimeTrigger::new(TimeTriggerLimit::Minute(2)); + assert!(trigger.is_pre_process()); } }