From 3fa15294cf24efec81f61fcc178cc52ba608b37e Mon Sep 17 00:00:00 2001 From: Lucas Colombo Date: Sun, 26 May 2024 03:34:11 -0300 Subject: [PATCH] =?UTF-8?q?feat(logger):=20=E2=9C=A8=20add:=20context=20ig?= =?UTF-8?q?noring=20+=20builder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 3 + examples/logger.rs | 12 ++- lib/logger/mod.rs | 180 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 171 insertions(+), 24 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6f8a6da..8eecca0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -21,5 +21,8 @@ ".gitignore": true, ".vscode": true, ".git": true, + }, + "[rust]": { + "editor.formatOnSave": true } } \ No newline at end of file diff --git a/examples/logger.rs b/examples/logger.rs index 4c7d278..c8ac36a 100644 --- a/examples/logger.rs +++ b/examples/logger.rs @@ -1,10 +1,18 @@ +use eyre::Result; use lool::logger::{debug, error, info, trace, warn, ConsoleLogger, Level}; -fn main() { - ConsoleLogger::default_setup(Level::Trace, "test").unwrap(); +fn main() -> Result<()> { + ConsoleLogger::builder() + .with_name("test") + .with_level(Level::Trace) + .ignore("examples/logger.rs") + .install()?; + info!("log line"); warn!("log line"); error!("log line"); debug!("log line"); trace!("log line"); + + Ok(()) } diff --git a/lib/logger/mod.rs b/lib/logger/mod.rs index 8196ad0..9d7e51d 100644 --- a/lib/logger/mod.rs +++ b/lib/logger/mod.rs @@ -14,7 +14,10 @@ struct StyledRecord { time: String, level: String, message: String, - file: String, + /// relative path if local file or ::<..>:: if + /// it's a record created by a dependency + ctx: String, + raw_ctx: String, line: String, } @@ -51,7 +54,8 @@ impl StyledRecord { Self { level: format!("{}{:<5}{}", ansi_style_level, record.level(), RESET), message: format!("{}", record.args()), - file: format!("{}{}{}", file_ansi_color, file.replace('\\', "/"), RESET), + ctx: format!("{}{}{}", file_ansi_color, file.replace('\\', "/"), RESET), + raw_ctx: file.replace('\\', "/"), line, time, } @@ -63,23 +67,25 @@ impl StyledRecord { /// /// this is a simple logger implementation (mounted on top of the `log` crate) that logs to stdout /// with ANSI colors and datetime stamps. -pub struct ConsoleLogger<'a> { - context: &'a str, +pub struct ConsoleLogger { + name: String, time_fn: fn() -> String, + ignored_ctx: Vec, } -impl<'a> ConsoleLogger<'a> { +impl ConsoleLogger { /// **🧉 » sets up the logger with the default settings** /// /// sets the logger to use the `datetime::utc_current_time` function to get the current time. - pub fn default_setup(max_level: Level, context: &'static str) -> Result<()> { + pub fn default_setup>(max_level: Level, context: S) -> Result<()> { let logger = Box::new(ConsoleLogger { - context, + name: context.as_ref().to_string(), time_fn: datetime::utc_current_time, + ignored_ctx: vec![], }); - log::set_logger(Box::leak(logger) as &'static ConsoleLogger) + log::set_logger(Box::leak(logger) as &'static dyn Log) .map(|()| log::set_max_level(max_level.to_level_filter())) - .map_err(|err| eyre!("failed to set logger: {}", err)) + .map_err(move |err| eyre!("failed to set logger: {}", err)) } /// **🧉 » sets up the logger with the given settings** @@ -88,42 +94,68 @@ impl<'a> ConsoleLogger<'a> { /// current time. /// /// the default `time_fn` is `datetime::utc_current_time` and it doesn't take TZ into account. - pub fn setup(max_level: Level, context: &'static str, time_fn: fn() -> String) -> Result<()> { - let logger = Box::new(ConsoleLogger { context, time_fn }); - log::set_logger(Box::leak(logger) as &'static ConsoleLogger) + pub fn setup>( + max_level: Level, + context: S, + time_fn: fn() -> String, + ) -> Result<()> { + let logger = Box::new(ConsoleLogger { + name: context.as_ref().to_string(), + time_fn, + ignored_ctx: vec![], + }); + log::set_logger(Box::leak(logger) as &'static dyn Log) .map(|()| log::set_max_level(max_level.to_level_filter())) - .map_err(|err| eyre!("failed to set logger: {}", err)) + .map_err(move |err| eyre!("failed to set logger: {}", err)) } - /// 🧉 » sets the logger's context - pub fn set_context(&mut self, context: &'a str) { - self.context = context; + /// **🧉 » creates a new setup builder** + /// + /// this is the preferred way to set up the logger as it allows for more flexibility. + /// + /// See the `SetupBuilder` struct for more information. + pub fn builder() -> SetupBuilder { + SetupBuilder::default() + } + + /// returns true if the context should be ignored + fn should_ignore(&self, ctx: &String) -> bool { + self.ignored_ctx.contains(ctx) } } -/// simple implementation of a themed logger -impl<'a> log::Log for ConsoleLogger<'a> { +impl Log for ConsoleLogger { fn enabled(&self, metadata: &Metadata) -> bool { metadata.level() <= Level::Info } - /// log the record fn log(&self, record: &Record) { let styled_record = StyledRecord::from(record, self.time_fn); + // ignore the record if the context is in the ignore list + if self.should_ignore(&styled_record.raw_ctx) { + return; + } + let time = if styled_record.time.is_empty() { " ".to_string() } else { format!(" {} ", styled_record.time) }; + let ctx = if self.name.is_empty() { + "".to_string() + } else { + format!("{} »", self.name) + }; + // print to stdout println!( - "[{}]{}| {} | {}:{} - {}", - self.context, + "{}{}| {} | {}:{} - {}", + ctx, time, styled_record.level, - styled_record.file, + styled_record.ctx, styled_record.line, styled_record.message ); @@ -131,3 +163,107 @@ impl<'a> log::Log for ConsoleLogger<'a> { fn flush(&self) {} } + +/// **🧉 » setup builder** +/// +/// this struct is used to set up the logger with more flexibility. +/// +/// It allows for setting the logger name, the time function, the ignored contexts, and the max +/// level. +/// +/// The `install` method is used to build and install the logger. Should be called at the end of the +/// builder chain. +/// +/// **Example** +/// +/// ```rust +/// use eyre::Result; +/// use lool::logger::{ConsoleLogger, Level, info}; +/// +/// fn main() -> Result<()> { +/// ConsoleLogger::builder() +/// .with_name("test") +/// .with_level(Level::Trace) +/// .ignore("crate::module::function") // ignore a specific context +/// .install()?; +/// +/// info!("log line"); +/// } +/// ``` +pub struct SetupBuilder { + name: Option, + time_fn: Option String>, + ignored_ctx: Option>, + max_level: Option, +} + +impl Default for SetupBuilder { + fn default() -> Self { + Self { + name: Some("".to_string()), + time_fn: Some(datetime::utc_current_time), + ignored_ctx: Some(vec![]), + max_level: Some(Level::Info), + } + } +} + +impl SetupBuilder { + /// **🧉 » `with_name`** + /// + /// Sets the logger name + pub fn with_name>(mut self, name: S) -> Self { + self.name = Some(name.as_ref().to_string()); + self + } + + /// **🧉 » `with_level`** + /// + /// Sets the max log level + pub fn with_level(mut self, level: Level) -> Self { + self.max_level = Some(level); + self + } + + /// **🧉 » `with_time_fn`** + /// + /// Sets the time function that will be used to get the current time + pub fn with_time_fn(mut self, time_fn: fn() -> String) -> Self { + self.time_fn = Some(time_fn); + self + } + + /// **🧉 » `ignore_all`** + /// + /// Sets the ignored contexts from a list of strings + pub fn ignore_all(mut self, ignored_ctx: Vec) -> Self { + self.ignored_ctx = Some(ignored_ctx); + self + } + + /// **🧉 » `ignore`** + /// + /// Adds a context to the ignored list. + /// + /// Unlike `ignore_all`, this method allows for adding a single ignored context at a time. + pub fn ignore>(mut self, ctx: Str) -> Self { + let mut ignored_ctx = self.ignored_ctx.unwrap(); + ignored_ctx.push(ctx.as_ref().to_string()); + self.ignored_ctx = Some(ignored_ctx); + self + } + + /// **🧉 » `install`** + /// + /// Builds and installs the logger. + pub fn install(self) -> Result<()> { + let logger = Box::new(ConsoleLogger { + name: self.name.unwrap_or("".to_string()), + time_fn: self.time_fn.unwrap_or(datetime::utc_current_time), + ignored_ctx: self.ignored_ctx.unwrap_or(vec![]), + }); + log::set_logger(Box::leak(logger) as &'static dyn Log) + .map(|()| log::set_max_level(self.max_level.unwrap().to_level_filter())) + .map_err(|err| eyre!("failed to set logger: {}", err)) + } +}