diff --git a/CHANGELOG.md b/CHANGELOG.md index 025ac3a..063d22b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to the time project will be documented in this file. --- + +### 0.3.1 (2022-04-26) + +#### Changes + +* Added warning if running with old time format from chrono and falling back to default one ### 0.3.0 (2022-03-14) diff --git a/Cargo.toml b/Cargo.toml index d52c9f6..354d183 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2018" name = "flowgger" -version = "0.3.0" +version = "0.3.1" authors = ["Frank Denis ", "Matteo Bigoi ", "Vivien Chene ", "Francesco Berni "] build = "build.rs" repository = "https://github.com/awslabs/flowgger" diff --git a/src/flowgger/encoder/mod.rs b/src/flowgger/encoder/mod.rs index a0e0cba..06c712f 100644 --- a/src/flowgger/encoder/mod.rs +++ b/src/flowgger/encoder/mod.rs @@ -24,10 +24,12 @@ pub use self::rfc3164_encoder::RFC3164Encoder; #[cfg(feature = "rfc5424")] pub use self::rfc5424_encoder::RFC5424Encoder; -use crate::flowgger::config::Config; use crate::flowgger::record::Record; +use crate::flowgger::{config::Config, validate_time_format_input}; use time::{format_description, OffsetDateTime}; +const SYSLOG_PREPEND_DEFAULT_TIME_FORMAT: &str = "[year][month][day]T[hour][minute][second]Z"; + pub trait CloneBoxedEncoder { fn clone_boxed<'a>(&self) -> Box where @@ -54,7 +56,7 @@ pub trait Encoder: CloneBoxedEncoder { } pub fn config_get_prepend_ts(config: &Config) -> Option { - config + let prepend_ts = config .lookup("output.syslog_prepend_timestamp") .map_or(None, |bs| { Some( @@ -62,7 +64,20 @@ pub fn config_get_prepend_ts(config: &Config) -> Option { .expect("output.syslog_prepend_timestamp should be a string") .to_string(), ) - }) + }); + + match prepend_ts { + Some(time_format) => { + let actual_time_format = validate_time_format_input( + "syslog_prepend_timestamp", + &time_format, + SYSLOG_PREPEND_DEFAULT_TIME_FORMAT.to_string(), + ); + Some(actual_time_format) + } + // Not using syslog_prepend_timestamp - no need to validate + None => None, + } } pub fn build_prepend_ts(format_str: &str) -> Result { diff --git a/src/flowgger/mod.rs b/src/flowgger/mod.rs index 5c3b87a..6aa408b 100644 --- a/src/flowgger/mod.rs +++ b/src/flowgger/mod.rs @@ -8,6 +8,8 @@ mod record; mod splitter; mod utils; +use std::io::{stderr, Write}; + #[cfg(feature = "capnp-recompile")] extern crate capnp; extern crate clap; @@ -307,6 +309,48 @@ fn get_encoder_passthrough(_config: &Config) -> ! { panic!("Support for passthrough hasn't been compiled in") } +/// Validate that the time format used are in +/// conform to https://docs.rs/time/0.3.7/time/format_description/index.html +/// +/// This is to raise a warning to users that are still using the old format from +/// `chrono` library. +/// +/// If '%' is still desirable to be part of the time format string, it needs to be escaped, like: +/// file_rotation_timeformat = "[year][month][day]\\%T[hour][minute]Z" +/// This will result in a file with name: "20220425%T1043Z" +/// +/// # Paramters +/// - `name`: the name of the param. Like `file_rotation_timeformat` +/// - `time_format`: the format to be validated +/// - `time_format_default`: the default value to use if `time_format` is invalid +/// +/// # Returns +/// Return an `String` which is the same value as `time_format` if valid +/// or `time_format_default` +/// +pub fn validate_time_format_input( + name: &str, + time_format: &str, + time_format_default: String, +) -> String { + if time_format.matches("%").count() != time_format.matches("\\%").count() { + let _ = writeln!( + stderr(), + "WARNING: Wrong {} value received: {}.\n\ + From version \"0.3.0\" forward the time format needs to be compliant with:\n\ + https://docs.rs/time/0.3.7/time/format_description/index.html \n\ + Will use the default one: {}. If you want to use %, you need to escape it (\\\\%)\n", + name, + time_format, + time_format_default + ); + time_format_default + } else { + //replacing the escaped chars so the file has the correct name + time_format.replace("\\%", "%").to_string() + } +} + pub fn start(config_file: &str) { let config = match Config::from_path(config_file) { Ok(config) => config, @@ -385,3 +429,45 @@ pub fn start(config_file: &str) { output.start(arx, merger); input.accept(tx, decoder, encoder); } + +#[cfg(test)] +mod tests { + use super::validate_time_format_input; + + #[test] + fn test_invalid_time_format() { + let default_value = "DEFAULT VALUE"; + let time_format = validate_time_format_input( + "file_rotation_timeformat", + "%Y%M", + default_value.to_string(), + ); + + assert!(time_format.eq(default_value)); + } + + #[test] + fn test_valid_time_format() { + let input_time_format = "[year][month]]"; + let time_format = validate_time_format_input( + "file_rotation_timeformat", + input_time_format, + "default_value".to_string(), + ); + + assert!(time_format.eq(input_time_format)); + } + + #[test] + fn test_valid_time_format_escaped() { + let input_time_format = "[year]\\%T[month]]"; + let input_time_format_without_escaped_char = "[year]%T[month]]"; + let time_format = validate_time_format_input( + "file_rotation_timeformat", + input_time_format, + "default_value".to_string(), + ); + + assert!(time_format.eq(input_time_format_without_escaped_char)); + } +} diff --git a/src/flowgger/output/file_output.rs b/src/flowgger/output/file_output.rs index f23b59a..873b660 100644 --- a/src/flowgger/output/file_output.rs +++ b/src/flowgger/output/file_output.rs @@ -2,6 +2,7 @@ use super::Output; use crate::flowgger::config::Config; use crate::flowgger::merger::Merger; use crate::flowgger::utils::rotating_file::RotatingFile; +use crate::flowgger::validate_time_format_input; use std::io::{BufWriter, Write}; use std::sync::mpsc::Receiver; use std::sync::{Arc, Mutex}; @@ -9,7 +10,7 @@ use std::thread; use std::io::stderr; const FILE_DEFAULT_BUFFER_SIZE: usize = 0; -const FILE_DEFAULT_TIME_FORMAT: &str = "%Y%m%dT%H%M%SZ"; +const FILE_DEFAULT_TIME_FORMAT: &str = "[year][month][day]T[hour][minute][second]Z"; const FILE_DEFAULT_ROTATION_SIZE: usize = 0; const FILE_DEFAULT_ROTATION_TIME: u32 = 0; const FILE_DEFAULT_ROTATION_MAXFILES: i32 = 50; @@ -99,6 +100,12 @@ impl FileOutput { }, ); + let time_format = validate_time_format_input( + "file_rotation_timeformat", + &time_format, + FILE_DEFAULT_TIME_FORMAT.to_string(), + ); + FileOutput { path, buffer_size, diff --git a/src/flowgger/utils/rotating_file.rs b/src/flowgger/utils/rotating_file.rs index 4c3a3ef..f8adf1b 100644 --- a/src/flowgger/utils/rotating_file.rs +++ b/src/flowgger/utils/rotating_file.rs @@ -32,7 +32,7 @@ impl RotatingFile { /// /// If the time trigger is specified (max_time >0): /// All file names are appended with their creation timestamp. i.e. configured file "abcd.log" might become - /// "abcd-20180108T0143Z.log" if the time format is configured to be "%Y%m%dT%H%MZ" + /// "abcd-20180108T0143Z.log" if the time format is configured to be "[year][month][day]T[hour][minute]Z" /// A file "expires" when its creation time + configured max_time is reached (based on current UTC time). /// Rotation occurs when a write is requested to an expired file. The file is then closed and a new one is created. /// # Notes: @@ -83,7 +83,7 @@ impl RotatingFile { /// - basename = 'logs/syslog.log' /// - max_time = 2 /// - app started on 2018-01-08 at 01:43 UTC - /// - time format is "%Y%m%dT%H%M%SZ" + /// - time format is "[year][month][day]T[hour][minute][second]Z" /// /// The following files will be generated: /// - Current file = 'logs/syslog-20180108T014343Z.log'