diff --git a/Cargo.toml b/Cargo.toml index d22156e..b8f60bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "ftlog" version = "0.2.14" edition = "2021" -authors = [ "Non-convex Tech" ] +authors = ["Non-convex Tech"] license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/nonconvextech/ftlog" @@ -10,41 +10,42 @@ documentation = "https://docs.rs/ftlog" description = """ An asynchronous logging library for high performance """ -categories = [ "development-tools::debugging" ] -keywords = [ "logging" ] -exclude = [ ".standard-version", ".versionrc", ".github" ] +categories = ["development-tools::debugging"] +keywords = ["logging"] +exclude = [".standard-version", ".versionrc", ".github"] [features] -default = [ "random_drop" ] -tsc = [ "minstant", "once_cell" ] -random_drop = [ "fastrand" ] +default = ["random_drop"] +tsc = ["minstant", "once_cell"] +random_drop = ["fastrand"] [dependencies] +arc-swap = "1" crossbeam-channel = "0.5.0" +env_filter = "0.1" hashbrown = "0.14" -arc-swap = "1" nohash-hasher = "0.2" typed-builder = "0.16" - [dependencies.fastrand] - version = "2" - optional = true +[dependencies.fastrand] +version = "2" +optional = true - [dependencies.time] - version = "0.3" - features = [ "local-offset", "formatting" ] +[dependencies.log] +version = "0.4" +features = ["std", "kv_unstable"] - [dependencies.minstant] - version = "0.1" - optional = true +[dependencies.minstant] +version = "0.1" +optional = true - [dependencies.once_cell] - version = "1" - optional = true +[dependencies.once_cell] +version = "1" +optional = true - [dependencies.log] - version = "0.4" - features = [ "std", "kv_unstable" ] +[dependencies.time] +version = "0.3" +features = ["local-offset", "formatting"] [target."cfg(target_family = \"unix\")".dependencies.tz-rs] version = "0.6.14" diff --git a/examples/custom-format.rs b/examples/custom-format.rs index db0e4df..accb054 100644 --- a/examples/custom-format.rs +++ b/examples/custom-format.rs @@ -2,9 +2,9 @@ use std::fmt::Display; use ftlog::{ appender::{file::Period, FileAppender}, - info, FtLogFormat, LoggerGuard, + info, FtLogFormat, LoggerGuard, Record, }; -use log::{Level, LevelFilter, Record}; +use log::{Level, LevelFilter}; use time::Duration; fn init() -> LoggerGuard { // Custom log style. @@ -41,7 +41,7 @@ fn init() -> LoggerGuard { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&format!( "{}@{}||{}:{}[{}] {}", - self.thread.as_ref().map(|x| x.as_str()).unwrap_or(""), + self.thread.as_deref().unwrap_or(""), self.module_path.unwrap_or(""), self.file.unwrap_or(""), self.line.unwrap_or(0), @@ -72,11 +72,20 @@ fn init() -> LoggerGuard { ) // ---------- configure additional filter ---------- // write to "ftlog-appender" appender, with different level filter - .filter("ftlog::appender", "ftlog-appender", LevelFilter::Error) + .filter_with( + |_msg, level, target| target == "ftlog::appender" && level == LevelFilter::Error, + "ftlog-appender", + ) // write to root appender, but with different level filter - .filter("ftlog", None, LevelFilter::Trace) + .filter_with( + |_msg, level, target| target == "ftlog" && level == LevelFilter::Trace, + "ftlog", + ) // write to "ftlog" appender, with default level filter - .filter("ftlog::appender::file", "ftlog", None) + .filter_with( + |_msg, _level, target| target == "ftlog::appender::file", + "ftlog", + ) // ---------- configure additional appender ---------- // new appender .appender("ftlog-appender", FileAppender::new("ftlog-appender.log")) diff --git a/examples/env_log.rs b/examples/env_log.rs new file mode 100644 index 0000000..feb9290 --- /dev/null +++ b/examples/env_log.rs @@ -0,0 +1,28 @@ +use ftlog::{appender::FileAppender, LoggerGuard}; +use log::{info, LevelFilter}; + +fn init() -> LoggerGuard { + // Rotate every day, clean stale logs that were modified 7 days ago on each rotation + let writer = FileAppender::builder().path("./env_log.log").build(); + ftlog::Builder::new() + // global max log level + .max_log_level(LevelFilter::Info) + .use_env_filter() + // define root appender, pass None would write to stderr + .root(writer) + // write logs in ftlog::appender to "./ftlog-appender.log" instead of "./current.log" + .try_init() + .expect("logger build or set failed") +} + +fn main() { + // RUST_LOG=env_log cargo run --example env_log or RUST_LOG=info cargo run --example env_log will show log lines + let _guard = init(); + info!("Hello, world!"); + for i in 0..120 { + info!("running {}!", i); + info!(limit=3000i64; "limit running{} !", i); + std::thread::sleep(std::time::Duration::from_secs(1)); + } + std::thread::sleep(std::time::Duration::from_secs(5)); +} diff --git a/examples/ftlog.rs b/examples/ftlog.rs index bc863a9..b341ef0 100644 --- a/examples/ftlog.rs +++ b/examples/ftlog.rs @@ -1,12 +1,12 @@ use ftlog::{ appender::{file::Period, FileAppender}, - info, LoggerGuard, + LoggerGuard, }; use log::LevelFilter; use time::Duration; fn init() -> LoggerGuard { - // Rotate every day, clean stale logs that were modified 7 days ago on each rotation + // Rotate every minute, clean stale logs that were modified 4 minutes ago on each rotation let writer = FileAppender::builder() .path("./current.log") .rotate(Period::Minute) @@ -18,7 +18,10 @@ fn init() -> LoggerGuard { // define root appender, pass None would write to stderr .root(writer) // write logs in ftlog::appender to "./ftlog-appender.log" instead of "./current.log" - .filter("ftlog::appender", "ftlog-appender", LevelFilter::Error) + .filter_with( + |_msg, level, target| target == "ftlog::appender" && level == LevelFilter::Error, + "ftlog-appender", + ) .appender("ftlog-appender", FileAppender::new("ftlog-appender.log")) .try_init() .expect("logger build or set failed") diff --git a/examples/multi-dest.rs b/examples/multi-dest.rs index 6b51339..19058e1 100644 --- a/examples/multi-dest.rs +++ b/examples/multi-dest.rs @@ -21,7 +21,10 @@ fn init() -> LoggerGuard { Box::new(std::io::stdout()), ])) // write logs in ftlog::appender to "./ftlog-appender.log" instead of "./current.log" - .filter("ftlog::appender", "ftlog-appender", LevelFilter::Error) + .filter_with( + |_msg, level, target| target == "ftlog::appender" && level == LevelFilter::Error, + "ftlog-appender", + ) .appender("ftlog-appender", FileAppender::new("ftlog-appender.log")) .try_init() .expect("logger build or set failed") diff --git a/src/appender/file.rs b/src/appender/file.rs index 0204647..6e03202 100644 --- a/src/appender/file.rs +++ b/src/appender/file.rs @@ -160,10 +160,9 @@ impl< let del_msg = clean_expire_log(p, period, expire); if !del_msg.is_empty() { file.write_fmt(format_args!("Log file deleted: {}", del_msg)) - .expect(&format!( - "Write msg to \"{}\" failed", - path.to_string_lossy() - )); + .unwrap_or_else(|_| { + panic!("Write msg to \"{}\" failed", path.to_string_lossy()) + }); } FileAppender { file, @@ -185,7 +184,7 @@ impl< OpenOptions::new() .create(true) .append(true) - .open(&path) + .open(path) .unwrap(), ); FileAppender { @@ -207,10 +206,12 @@ impl< .create(true) .append(true) .open(&builder.path) - .expect(&format!( - "Fail to create log file: {}", - builder.path.to_string_lossy() - )), + .unwrap_or_else(|_| { + panic!( + "Fail to create log file: {}", + builder.path.to_string_lossy() + ) + }), ), path: builder.path, rotate: None, @@ -295,7 +296,7 @@ impl FileAppender { match timezone { LogTimezone::Local => local_timezone(), LogTimezone::Utc => UtcOffset::UTC, - LogTimezone::Fixed(offset) => offset.clone(), + LogTimezone::Fixed(offset) => *offset, } } @@ -367,10 +368,10 @@ fn clean_expire_log(path: PathBuf, rotate_period: Period, keep_duration: Duratio .filter(|x| { let p = x.path(); let name = p.file_stem().unwrap().to_string_lossy(); - if let Some((stem, time)) = name.rsplit_once("-") { + if let Some((stem, time)) = name.rsplit_once('-') { let check = |(ix, x): (usize, char)| match ix { 8 => x == 'T', - _ => x.is_digit(10), + _ => x.is_ascii_digit(), }; let len = match rotate_period { Period::Minute => time.len() == 13, @@ -422,9 +423,9 @@ impl Write for FileAppender { let path = Self::file(&self.path, *period, &self.timezone); // remove outdated log files if let Some(keep_duration) = keep { - let keep_duration = keep_duration.clone(); + let keep_duration = *keep_duration; let path = self.path.clone(); - let period = period.clone(); + let period = *period; std::thread::spawn(move || { let del_msg = clean_expire_log(path, period, keep_duration); if !del_msg.is_empty() { diff --git a/src/lib.rs b/src/lib.rs index c0b2dad..276424e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,15 +76,10 @@ //! .expire(Duration::days(7)) //! .build(), //! ) -//! // timezone of log message timestamp, use local by default -//! // .local_timezone() -//! // or use fiexed timezone for better throughput, since retrieving timezone is a time consuming operation -//! // this does not affect worker threads (that call log), but can boost log thread performance (higher throughput). -//! .fixed_timezone(time::UtcOffset::current_local_offset().unwrap()) //! // level filter for root appender //! .root_log_level(LevelFilter::Warn) //! // write logs in ftlog::appender to "./ftlog-appender.log" instead of "./current.log" -//! .filter("ftlog::appender", "ftlog-appender", LevelFilter::Error) +//! .filter(|msg, level, target| target == "ftlog::appender" && level == LevelFilter::Error, "ftlog-appender") //! .appender("ftlog-appender", FileAppender::new("ftlog-appender.log")) //! .try_init() //! .expect("logger build or set failed"); @@ -312,6 +307,7 @@ use tm::{duration, now, to_utc, Time}; #[cfg(not(feature = "tsc"))] mod tm { + use super::*; pub type Time = std::time::SystemTime; @@ -365,30 +361,28 @@ fn local_timezone() -> UtcOffset { } struct LogMsg { - time: Time, msg: Box, level: Level, target: String, limit: u32, limit_key: u64, } + impl LogMsg { fn write( self, - filters: &Vec, + filters: &[Directive], appenders: &mut HashMap<&'static str, Box>, root: &mut Box, root_level: LevelFilter, missed_log: &mut HashMap>, last_log: &mut HashMap>, - offset: Option, - time_format: &time::format_description::OwnedFormatItem, ) { - let writer = if let Some(filter) = filters.iter().find(|x| self.target.starts_with(x.path)) + // Find an appender filter if one exists + let writer = if let Some(filter) = filters + .iter() + .find(|x| (*x.filter)(&self.msg, self.level, &self.target)) { - if filter.level.map(|l| l < self.level).unwrap_or(false) { - return; - } filter .appender .and_then(|n| appenders.get_mut(n)) @@ -406,8 +400,7 @@ impl LogMsg { } let now = now(); - - if self.limit > 0 { + let s = if self.limit > 0 { let missed_entry = missed_log.entry(self.limit_key).or_insert_with(|| 0); if let Some(last) = last_log.get(&self.limit_key) { if duration(*last, now) < Duration::from_millis(self.limit as u64) { @@ -416,48 +409,15 @@ impl LogMsg { } } last_log.insert(self.limit_key, now); - let delay = duration(self.time, now); - let utc_datetime = to_utc(self.time); - - let offset_datetime = offset - .map(|o| utc_datetime.to_offset(o)) - .unwrap_or(utc_datetime); - let s = format!( - "{} {}ms {} {}\n", - offset_datetime - .format(&time_format) - .unwrap_or_else(|_| offset_datetime - .format(&time::format_description::well_known::Rfc3339) - .unwrap()), - delay.as_millis(), - *missed_entry, - msg - ); - if let Err(e) = writer.write_all(s.as_bytes()) { - eprintln!("logger write message failed: {}", e); - }; *missed_entry = 0; + format!("{} {}\n", *missed_entry, msg) } else { - let delay = duration(self.time, now); - let utc_datetime = to_utc(self.time); - let offset_datetime = offset - .map(|o| utc_datetime.to_offset(o)) - .unwrap_or(utc_datetime); - let s = format!( - "{} {}ms {}\n", - offset_datetime - .format(&time_format) - .unwrap_or_else(|_| offset_datetime - .format(&time::format_description::well_known::Rfc3339) - .unwrap()), - delay.as_millis(), - msg - ); - if let Err(e) = writer.write_all(s.as_bytes()) { - eprintln!("logger write message failed: {}", e); - }; - } + format!("{}\n", msg) + }; + if let Err(e) = writer.write_all(s.as_bytes()) { + eprintln!("logger write message failed: {}", e); + }; } } @@ -549,24 +509,26 @@ impl FtLogFormat for FtLogFormatter { #[inline] fn msg(&self, record: &Record) -> Box { Box::new(Message { + time: now(), level: record.level(), thread: std::thread::current().name().map(|n| n.to_string()), file: record .file_static() - .map(|s| Cow::Borrowed(s)) + .map(Cow::Borrowed) .or_else(|| record.file().map(|s| Cow::Owned(s.to_owned()))) .unwrap_or(Cow::Borrowed("")), line: record.line(), args: record .args() .as_str() - .map(|s| Cow::Borrowed(s)) + .map(Cow::Borrowed) .unwrap_or_else(|| Cow::Owned(format!("{}", record.args()))), }) } } struct Message { + time: Time, level: Level, thread: Option, file: Cow<'static, str>, @@ -576,10 +538,15 @@ struct Message { impl Display for Message { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let utc_datetime = to_utc(self.time); + f.write_str(&format!( - "{} {} [{}:{}] {}", + "{} {} {} [{}:{}] {}", + utc_datetime + .format(&time::format_description::well_known::Rfc3339) + .unwrap(), self.level, - self.thread.as_ref().map(|x| x.as_str()).unwrap_or(""), + self.thread.as_deref().unwrap_or(""), self.file, self.line.unwrap_or(0), self.args @@ -613,7 +580,9 @@ impl Drop for LoggerGuard { /// ftlog global logger pub struct Logger { format: Box, + env_filter: Option, level: LevelFilter, + filters: Vec, queue: Sender, notification: Receiver, block: bool, @@ -661,7 +630,20 @@ impl Log for Logger { .and_then(|x| x.to_u64()) .unwrap_or(0) as u32; - let msg = self.format.msg(record); + // This will short circuit if any of the filters return false, meaning don't keep this record. + if !self.filters.is_empty() && self.filters.iter().all(|filter| filter(record)) { + // Drop this log record + println!("Dropping this record {:?}", record); + return; + } + + if let Some(filter) = &self.env_filter { + if !filter.matches(record) { + // Drop this log record + return; + } + } + let limit_key = if limit == 0 { 0 } else { @@ -674,16 +656,16 @@ impl Log for Logger { record.line().unwrap_or(0).hash(&mut b); b.finish() }; + let msg = self.format.msg(record); let msg = LoggerInput::LogMsg(LogMsg { - time: now(), - msg: msg, + msg, target: record.target().to_owned(), level: record.level(), limit, limit_key, }); if self.block { - if let Err(_) = self.queue.send(msg) { + if self.queue.send(msg).is_err() { let stop = self.stopped.load(Ordering::SeqCst); if !stop { eprintln!("logger queue closed when logging, this is a bug"); @@ -752,11 +734,11 @@ struct BoundedChannelOption { /// )) /// // ---------- configure additional filter ---------- /// // write to "ftlog-appender" appender, with different level filter -/// .filter("ftlog::appender", "ftlog-appender", LevelFilter::Error) +/// .filter(|msg, level, target| target == "ftlog::appender"&& level == LevelFilter::Error, "ftlog-appender") /// // write to root appender, but with different level filter -/// .filter("ftlog", None, LevelFilter::Trace) +/// .filter(|msg, level, target| target == "ftlog" && level == LevelFilter::Trace, None) /// // write to "ftlog" appender, with default level filter -/// .filter("ftlog::appender::file", "ftlog", None) +/// .filter(|msg, level, target| target == "ftlog::appender::file", "ftlog") /// // ---------- configure additional appender ---------- /// // new appender /// .appender("ftlog-appender", FileAppender::new("ftlog-appender.log")) @@ -771,6 +753,7 @@ struct BoundedChannelOption { /// local timezone offset forever. Thus timestamp in log does not aware of timezone /// change by OS. pub struct Builder { + env_filter: bool, format: Box, time_format: Option, level: Option, @@ -778,8 +761,8 @@ pub struct Builder { root: Box, appenders: HashMap<&'static str, Box>, filters: Vec, + drop_filters: Vec, bounded_channel_option: Option, - timezone: LogTimezone, } /// Handy function to get ftlog builder @@ -788,11 +771,17 @@ pub fn builder() -> Builder { Builder::new() } +/// Type alias for the closure intended to filter drops +pub type DropFilterFn = Box bool + Send + Sync>; + +/// Type alias for the closure for filtering/redirecting logs to appenders +pub type AppenderFilterFn = Box bool + Send>; + struct Directive { - path: &'static str, - level: Option, + filter: AppenderFilterFn, appender: Option<&'static str>, } + /// timezone for log pub enum LogTimezone { /// local timezone @@ -810,6 +799,7 @@ impl Builder { /// Create a ftlog builder with default settings: /// - global log level: INFO /// - root log level: INFO + /// - no drop filters /// - default formatter: `FtLogFormatter` /// - output to stderr /// - bounded channel between worker thread and log thread, with a size limit of 100_000 @@ -817,18 +807,19 @@ impl Builder { /// - log with timestamp of local timezone pub fn new() -> Builder { Builder { + env_filter: false, format: Box::new(FtLogFormatter), level: None, root_level: None, root: Box::new(stderr()) as Box, appenders: HashMap::new(), filters: Vec::new(), + drop_filters: Vec::new(), bounded_channel_option: Some(BoundedChannelOption { size: 100_000, block: false, print: true, }), - timezone: LogTimezone::Local, time_format: None, } } @@ -847,6 +838,16 @@ impl Builder { self } + /// This will drop log records before they are sent into the channel. + #[inline] + pub fn drop_filters(mut self, filter: F) -> Builder + where + F: Fn(&Record) -> bool + Send + Sync + 'static, + { + self.drop_filters.push(Box::new(filter)); + self + } + /// bound channel between worker thread and log thread /// /// When `block_when_full` is true, it will block current thread where @@ -869,9 +870,9 @@ impl Builder { /// thread is bounded, and set to discard excessive log messages #[inline] pub fn print_omitted_count(mut self, print: bool) -> Builder { - self.bounded_channel_option - .as_mut() - .map(|o| o.print = print); + if let Some(o) = self.bounded_channel_option.as_mut() { + o.print = print; + } self } @@ -904,28 +905,66 @@ impl Builder { /// Add a filter to redirect log to different output /// target (e.g. stderr, stdout, different files). /// - /// **ATTENTION**: level more verbose than `Builder::max_log_level` will be ignored. - /// Say we configure `max_log_level` to INFO, and even if filter's level is set to DEBUG, + /// **ATTENTION**: Any level more verbose than `Builder::max_log_level` will be ignored. + /// If we configure `max_log_level` to INFO, and even if filter's level is set to DEBUG, /// ftlog will still log up to INFO. #[inline] pub fn filter>, L: Into>>( - mut self, + self, module_path: &'static str, appender: A, level: L, ) -> Builder { let appender = appender.into(); let level = level.into(); - if appender.is_some() || level.is_some() { + match (appender, level) { + (Some(appender), Some(lvl)) => self.filter_with( + move |_msg, level, target| module_path == target && level == lvl, + appender, + ), + (Some(appender), None) => { + self.filter_with(move |_msg, _lvl, target| module_path == target, appender) + } + + _ => self, + } + } + + /// Add a filter to redirect log to different output + /// target (e.g. stderr, stdout, different files). The filter closure takes in a + /// message, a level and a target. The filter must return true if the log message + /// should use this appender. Note that this will use the first match that succeeds. + /// + /// **ATTENTION**: Any level more verbose than `Builder::max_log_level` will be ignored. + /// If we configure `max_log_level` to INFO, and even if filter's level is set to DEBUG, + /// ftlog will still log up to INFO. + #[inline] + pub fn filter_with>, F>( + mut self, + filter: F, + appender: A, + ) -> Builder + where + F: Fn(&dyn Display, Level, &str) -> bool + Send + Sync + 'static, + { + let appender = appender.into(); + if appender.is_some() { self.filters.push(Directive { - path: module_path, - appender: appender, - level: level, + filter: Box::new(filter), + appender, }); } self } + /// Use the RUST_LOG env variable to filter logs. Uses the same syntax that env_logger + /// supports because it uses the same Filter internally. + pub fn use_env_filter(mut self) -> Builder { + self.env_filter = true; + + self + } + #[inline] /// Configure the default log output target. /// @@ -953,60 +992,12 @@ impl Builder { self } - #[inline] - /// Log with timestamp of local timezone - /// - /// Timezone is fixed after logger setup for the following reasons: - /// 1. `time` v0.3 currently do not allow access to local offset for multithread process - /// in unix-like OS. - /// 1. timezone retrieval from OS is quite slow (around several microsecond) compare with - /// utc timestamp retrieval (around tens of nanoseconds) - pub fn local_timezone(mut self) -> Builder { - self.timezone = LogTimezone::Local; - self - } - - #[inline] - /// Log with timestamp of UTC timezone - pub fn utc(mut self) -> Builder { - self.timezone = LogTimezone::Utc; - self - } - - #[inline] - /// Log with timestamp of fixed timezone - pub fn fixed_timezone(mut self, timezone: UtcOffset) -> Builder { - self.timezone = LogTimezone::Fixed(timezone); - self - } - - #[inline] - /// Specify the timezone of log messages - pub fn timezone(mut self, timezone: LogTimezone) -> Builder { - self.timezone = timezone; - self - } - /// Finish building ftlog logger /// /// The call spawns a log thread to formatting log message into string, /// and write to output target. pub fn build(self) -> Result { - let offset = match self.timezone { - LogTimezone::Local => Some(local_timezone()), - LogTimezone::Utc => None, - LogTimezone::Fixed(offset) => Some(offset), - }; - let time_format = self.time_format.unwrap_or_else(|| { - time::format_description::parse_owned::<1>( - "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]+[offset_hour]", - ) - .unwrap() - }); - let mut filters = self.filters; - // sort filters' paths to ensure match for longest path - filters.sort_by(|a, b| a.path.len().cmp(&b.path.len())); - filters.reverse(); + let filters = self.filters; // check appender name in filters are all valid for appender_name in filters.iter().filter_map(|x| x.appender) { if !self.appenders.contains_key(appender_name) { @@ -1014,13 +1005,26 @@ impl Builder { } } let global_level = self.level.unwrap_or(LevelFilter::Info); - let root_level = self.root_level.unwrap_or(global_level); + let mut root_level = self.root_level.unwrap_or(global_level); if global_level < root_level { warn!( "Logs with level more verbose than {} will be ignored", global_level, ); } + let env_filter = if self.env_filter { + let mut builder = env_filter::Builder::new(); + // Parse a logging filter from an environment variable. + if let Ok(rust_log) = std::env::var("RUST_LOG") { + builder.parse(&rust_log); + } + let filter = builder.build(); + // Set the root level filter to this + root_level = filter.filter(); + Some(filter) + } else { + None + }; let (sync_sender, receiver) = match &self.bounded_channel_option { None => unbounded(), @@ -1033,17 +1037,6 @@ impl Builder { let mut appenders = self.appenders; let filters = filters; - for filter in &filters { - if let Some(level) = filter.level { - if global_level < level { - warn!( - "Logs with level more verbose than {} will be ignored in `{}` ", - global_level, filter.path, - ); - } - } - } - let mut root = self.root; let mut last_log = HashMap::default(); let mut missed_log = HashMap::default(); @@ -1059,8 +1052,6 @@ impl Builder { root_level, &mut missed_log, &mut last_log, - offset, - &time_format, ); } Ok(LoggerInput::Flush) => { @@ -1074,8 +1065,6 @@ impl Builder { root_level, &mut missed_log, &mut last_log, - offset, - &time_format, ) } else { break 'queue; @@ -1127,7 +1116,9 @@ impl Builder { .map(|x| x.print) .unwrap_or(false); Ok(Logger { + env_filter, format: self.format, + filters: self.drop_filters, level: global_level, queue: sync_sender, notification: notification_receiver, diff --git a/tests/local_tz.rs b/tests/local_tz.rs index a72350f..7a6b5c7 100644 --- a/tests/local_tz.rs +++ b/tests/local_tz.rs @@ -5,10 +5,26 @@ use std::{ use ftlog::appender::{Duration, FileAppender, Period}; -pub fn setup() -> () { +pub fn setup_with_closures() { let logger = ftlog::Builder::new() .bounded(10000, true) .root(FileAppender::new("./root.log")) + .filter_with(|_msg, _level, target| target == "rotate", "rotate") + .appender("rotate", FileAppender::rotate("rotate.log", Period::Minute)) + .filter_with(|_msg, _level, target| target == "expire", "expire") + .appender( + "expire", + FileAppender::rotate_with_expire("expire.log", Period::Day, Duration::days(7)), + ) + .build() + .expect("logger build failed"); + logger.init().expect("set logger failed"); +} +pub fn setup_with_filter() { + let logger = ftlog::Builder::new() + .bounded(10000, true) + .root(FileAppender::new("./root.log")) + // .utc() .filter("rotate", "rotate", None) .appender("rotate", FileAppender::rotate("rotate.log", Period::Minute)) .filter("expire", "expire", None) @@ -24,7 +40,6 @@ pub fn setup() -> () { fn clean(dir: &str) { for file in read_dir(dir) .unwrap() - .into_iter() .filter_map(|x| x.ok()) .filter(|f| f.file_type().unwrap().is_file()) { @@ -40,9 +55,57 @@ fn clean(dir: &str) { } } #[test] -fn test_speed() { +fn test_speed_local() { + // ~80MB + setup_with_filter(); + let elapsed1 = { + // file + let now = Instant::now(); + for i in 1..=1_000_000 { + ftlog::info!("file log {}", i); + } + ftlog::logger().flush(); + let elapsed = now.elapsed(); + println!("File elapsed: {}s", elapsed.as_secs_f64()); + elapsed + }; + + let elapsed2 = { + // file with rotate + let now = Instant::now(); + for i in 1..=1_000_000 { + ftlog::info!(target:"rotate", "file log {}", i); + } + ftlog::logger().flush(); + let elapsed = now.elapsed(); + println!("Rotate file elapsed: {}s", elapsed.as_secs_f64()); + elapsed + }; + + let elapsed3 = { + // file with rotate with expire + let now = Instant::now(); + for i in 1..=1_000_000 { + ftlog::info!(target:"expire", "file log {}", i); + } + ftlog::logger().flush(); + let elapsed = now.elapsed(); + println!( + "Rotate file with expire elapsed: {}s", + elapsed.as_secs_f64() + ); + elapsed + }; + clean("./"); + assert!(elapsed1.as_secs() < 8); + assert!(elapsed2.as_secs() < 8); + assert!(elapsed3.as_secs() < 8); +} + +#[test] +fn test_speed_local2() { // ~80MB - setup(); + setup_with_closures(); let elapsed1 = { // file let now = Instant::now(); diff --git a/tests/utc.rs b/tests/utc.rs index 70d3008..3bcbcae 100644 --- a/tests/utc.rs +++ b/tests/utc.rs @@ -5,14 +5,14 @@ use std::{ use ftlog::appender::{Duration, FileAppender, Period}; -pub fn setup() -> () { +pub fn setup() { let logger = ftlog::Builder::new() .bounded(10000, true) .root(FileAppender::new("./root.log")) // .utc() - .filter("rotate", "rotate", None) + .filter_with(|_msg, _level, target| target == "rotate", "rotate") .appender("rotate", FileAppender::rotate("rotate.log", Period::Minute)) - .filter("expire", "expire", None) + .filter_with(|_msg, _level, target| target == "expire", "expire") .appender( "expire", FileAppender::rotate_with_expire("expire.log", Period::Day, Duration::days(7)), @@ -25,7 +25,6 @@ pub fn setup() -> () { fn clean(dir: &str) { for file in read_dir(dir) .unwrap() - .into_iter() .filter_map(|x| x.ok()) .filter(|f| f.file_type().unwrap().is_file()) { @@ -41,7 +40,7 @@ fn clean(dir: &str) { } } #[test] -fn test_speed() { +fn test_speed_utc() { // ~80MB setup(); let elapsed1 = {