diff --git a/examples/rotate_custom_format.rs b/examples/rotate_custom_format.rs new file mode 100644 index 0000000..16095aa --- /dev/null +++ b/examples/rotate_custom_format.rs @@ -0,0 +1,43 @@ +use flexi_logger::{ + sort_by_creation_date, Age, Cleanup, Criterion, CustomFormatter, Duplicate, FileSorter, + FileSpec, FlexiLoggerError, LevelFilter, Logger, Naming, +}; +use std::{thread::sleep, time::Duration}; + +fn format_infix(o_last_infix: Option) -> String { + let id = match o_last_infix { + Some(infix) => { + let id: usize = infix.parse().unwrap(); + id + 1 + } + None => 0, + }; + id.to_string() +} + +fn main() -> Result<(), FlexiLoggerError> { + Logger::with(LevelFilter::Info) + .rotate( + Criterion::Age(Age::Second), + Naming::CustomFormat(CustomFormatter::new(format_infix)), + Cleanup::KeepLogFiles(4), + ) + .log_to_file( + FileSpec::default() + .directory(std::env::current_dir().unwrap().join("log_files")) + .basename("app-log") + .suffix("txt") + .file_sorter(FileSorter::new(sort_by_creation_date)), + ) + .duplicate_to_stdout(Duplicate::All) + .start()?; + + log::info!("start"); + for step in 0..30 { + log::info!("step {}", step); + sleep(Duration::from_millis(250)); + } + log::info!("done"); + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index fe71420..bcdc54a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,7 +72,10 @@ pub use crate::{ log_specification::{LogSpecBuilder, LogSpecification, ModuleFilter}, logger::{Duplicate, ErrorChannel, Logger}, logger_handle::{LogfileSelector, LoggerHandle}, - parameters::{Age, Cleanup, Criterion, FileSpec, Naming}, + parameters::{ + sort_by_creation_date, sort_by_default, Age, Cleanup, Criterion, CustomFormatter, + FileSorter, FileSpec, Naming, + }, write_mode::{WriteMode, DEFAULT_BUFFER_CAPACITY, DEFAULT_FLUSH_INTERVAL}, }; diff --git a/src/parameters.rs b/src/parameters.rs index 3bbaf18..d7a3b0a 100644 --- a/src/parameters.rs +++ b/src/parameters.rs @@ -7,5 +7,7 @@ mod naming; pub use age::Age; pub use cleanup::Cleanup; pub use criterion::Criterion; -pub use file_spec::FileSpec; +pub use file_spec::{sort_by_creation_date, sort_by_default}; +pub use file_spec::{FileSorter, FileSpec}; +pub use naming::CustomFormatter; pub use naming::Naming; diff --git a/src/parameters/file_spec.rs b/src/parameters/file_spec.rs index 73a5e32..648121e 100644 --- a/src/parameters/file_spec.rs +++ b/src/parameters/file_spec.rs @@ -1,5 +1,6 @@ use crate::writers::file_log_writer::InfixFilter; use crate::{DeferredNow, FlexiLoggerError}; +use std::fs; use std::{ ffi::{OsStr, OsString}, ops::Add, @@ -51,6 +52,7 @@ pub struct FileSpec { timestamp_cfg: TimestampCfg, o_suffix: Option, pub(crate) use_utc: bool, + file_sorter: FileSorter, } impl Default for FileSpec { /// Describes a file in the current folder, @@ -65,6 +67,7 @@ impl Default for FileSpec { timestamp_cfg: TimestampCfg::Default, o_suffix: Some(String::from("log")), use_utc: false, + file_sorter: FileSorter::default(), } } } @@ -108,6 +111,7 @@ impl FileSpec { o_suffix: p.extension().map(|s| s.to_string_lossy().to_string()), timestamp_cfg: TimestampCfg::No, use_utc: false, + file_sorter: FileSorter::default(), }) } } @@ -136,6 +140,11 @@ impl FileSpec { self } + ///Basename getter + pub fn get_basename(&self) -> &str { + &self.basename + } + /// Specifies a folder for the log files. /// /// If the specified folder does not exist, it will be created. @@ -185,6 +194,12 @@ impl FileSpec { self } + /// Specifies file sorter + pub fn file_sorter(mut self, file_sorter: FileSorter) -> Self { + self.file_sorter = file_sorter; + self + } + /// Makes the logger not include the start time into the names of the log files /// /// Equivalent to `use_timestamp(false)`. @@ -368,8 +383,7 @@ impl FileSpec { } }) .collect::>(); - log_files.sort_unstable(); - log_files.reverse(); + self.file_sorter.sort(&mut log_files); log_files } @@ -418,6 +432,45 @@ impl FileSpec { } } +/// File sorter stores and exposes interface to sorting strategy +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct FileSorter { + sort_fn: fn(&mut [PathBuf]), +} + +impl FileSorter { + /// Creates new FileSorter + pub fn new(sort_fn: fn(&mut [PathBuf])) -> Self { + Self { sort_fn } + } + + fn sort(&self, files: &mut [PathBuf]) { + (self.sort_fn)(files) + } +} + +impl Default for FileSorter { + fn default() -> Self { + Self::new(sort_by_default) + } +} + +/// sorts log files by the creation date +pub fn sort_by_creation_date(files: &mut [PathBuf]) { + files.sort_by_key(|path| { + fs::metadata(path) + .and_then(|metadata| metadata.created()) + .ok() + }); + files.reverse(); +} + +/// default sorting algorithm +pub fn sort_by_default(files: &mut [PathBuf]) { + files.sort_unstable(); + files.reverse(); +} + const TS_USCORE_DASHES_USCORE_DASHES: &str = "%Y-%m-%d_%H-%M-%S"; #[derive(Debug, Clone, Eq, PartialEq)] diff --git a/src/parameters/naming.rs b/src/parameters/naming.rs index 113ca87..0110509 100644 --- a/src/parameters/naming.rs +++ b/src/parameters/naming.rs @@ -103,7 +103,11 @@ pub enum Naming { /// /// File rotation switches over to the next file. NumbersDirect, + + /// Allows to specify custom infix and treat each file with basename as log file + CustomFormat(CustomFormatter), } + impl Naming { pub(crate) fn writes_direct(self) -> bool { matches!( @@ -117,3 +121,21 @@ impl Naming { ) } } + +/// Custom Formatter +#[derive(Copy, Clone, Debug)] +pub struct CustomFormatter { + format_fn: fn(Option) -> String, +} + +impl CustomFormatter { + /// Instantiate custom formatter + pub fn new(format_fn: fn(Option) -> String) -> Self { + CustomFormatter { format_fn } + } + + /// call custom formatter + pub fn call(&self, o_last_infix: Option) -> String { + (self.format_fn)(o_last_infix) + } +} diff --git a/src/writers/file_log_writer/infix_filter.rs b/src/writers/file_log_writer/infix_filter.rs index 28ba627..8ada0ae 100644 --- a/src/writers/file_log_writer/infix_filter.rs +++ b/src/writers/file_log_writer/infix_filter.rs @@ -27,7 +27,7 @@ impl InfixFilter { #[cfg(test)] InfixFilter::StartsWth(s) => infix.starts_with(s), InfixFilter::Equls(s) => infix.eq(s), - InfixFilter::None => false, + InfixFilter::None => true, } } } diff --git a/src/writers/file_log_writer/state.rs b/src/writers/file_log_writer/state.rs index ea1fba5..77741fe 100644 --- a/src/writers/file_log_writer/state.rs +++ b/src/writers/file_log_writer/state.rs @@ -2,6 +2,7 @@ mod list_and_cleanup; mod numbers; mod timestamps; +use list_and_cleanup::get_last_infix; pub(crate) use timestamps::timestamp_from_ts_infix; use super::{ @@ -12,7 +13,7 @@ use super::{ use crate::util::eprint_msg; use crate::{ util::{eprint_err, ErrorCode}, - Age, Cleanup, Criterion, FlexiLoggerError, LogfileSelector, Naming, + Age, Cleanup, Criterion, CustomFormatter, FlexiLoggerError, LogfileSelector, Naming, }; use chrono::{DateTime, Datelike, Local, Timelike}; #[cfg(feature = "async")] @@ -55,6 +56,9 @@ enum NamingState { // contains the index of the current output file NumbersDirect(u32), + + // contains custom formatter and last infix + CustomFormat(CustomFormatter, String), } impl NamingState { pub(crate) fn writes_direct(&self) -> bool { @@ -77,6 +81,7 @@ impl NamingState { infix_format, } => InfixFilter::Timstmps(infix_format.clone()), NamingState::NumbersDirect(_) | NamingState::NumbersRCurrent(_) => InfixFilter::Numbrs, + NamingState::CustomFormat(_, _) => InfixFilter::None, } } } @@ -403,6 +408,14 @@ impl State { }; (NamingState::NumbersDirect(idx), numbers::number_infix(idx)) } + Naming::CustomFormat(ref formatter) => { + let o_last_infix: Option = get_last_infix(&self.config.file_spec); + let current_infix = formatter.call(o_last_infix); + ( + NamingState::CustomFormat(*formatter, current_infix.clone()), + current_infix, + ) + } }; let (write, path) = open_log_file(&self.config, Some(&infix))?; let roll_state = RollState::new(rotate_config.criterion, self.config.append, &path)?; @@ -494,6 +507,11 @@ impl State { *idx_state += 1; numbers::number_infix(*idx_state) } + NamingState::CustomFormat(ref formatter, ref mut last_infix) => { + let infix = formatter.call(Some(last_infix.clone())); + *last_infix = infix.clone(); + infix + } }; let (new_write, new_path) = open_log_file(&self.config, Some(&infix))?; diff --git a/src/writers/file_log_writer/state/list_and_cleanup.rs b/src/writers/file_log_writer/state/list_and_cleanup.rs index 9c1a57f..35d45d6 100644 --- a/src/writers/file_log_writer/state/list_and_cleanup.rs +++ b/src/writers/file_log_writer/state/list_and_cleanup.rs @@ -197,3 +197,16 @@ pub(super) fn start_cleanup_thread( })?, }) } + +pub(super) fn get_last_infix(file_spec: &FileSpec) -> Option { + let log_files = + super::list_and_cleanup::list_of_log_and_compressed_files(file_spec, &InfixFilter::None); + // ascending ordering + let last_log_file = log_files.first()?; + let last_log_name = last_log_file.file_stem().unwrap(/*ok*/).to_string_lossy(); + Some( + last_log_name + .strip_prefix(&format!("{}_", file_spec.get_basename()))? + .to_owned(), + ) +}