diff --git a/Cargo.toml b/Cargo.toml index 8f03266..d1af131 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ edition = "2018" default = ["colors", "timestamps"] colors = ["colored"] threads = [] +source_location = [] timestamps = ["time"] nightly = [] stderr = [] @@ -18,7 +19,7 @@ stderr = [] [dependencies] log = { version = "^0.4.17", features = ["std"] } time = { version = "^0.3.16", features = ["formatting", "local-offset", "macros"], optional = true } -colored = { version = "2", optional = true } +colored = { version = "3", optional = true } [target.'cfg(windows)'.dependencies] windows-sys = { version = "^0.48.0", features = ["Win32_System_Console", "Win32_Foundation"] } @@ -35,6 +36,10 @@ required-features = ["colors", "stderr"] name = "threads" required-features = ["threads"] +[[example]] +name = "source_location" +required-features = ["source_location"] + [[example]] name = "timestamps_local" required-features = ["timestamps"] diff --git a/examples/source_location.rs b/examples/source_location.rs new file mode 100644 index 0000000..da052e1 --- /dev/null +++ b/examples/source_location.rs @@ -0,0 +1,7 @@ +use simple_logger::SimpleLogger; + +fn main() { + SimpleLogger::new().with_source_location(true).init().unwrap(); + + log::warn!("This is an example message."); +} diff --git a/src/lib.rs b/src/lib.rs index f29bf4d..0f32bd2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,7 @@ #[cfg(feature = "colors")] use colored::*; +use core::fmt; use log::{Level, LevelFilter, Log, Metadata, Record, SetLoggerError}; use std::{collections::HashMap, str::FromStr}; #[cfg(feature = "timestamps")] @@ -77,6 +78,12 @@ pub struct SimpleLogger { #[cfg(feature = "threads")] threads: bool, + /// Whether to include filename and line number or not + /// + /// This field is only available if the `source_location` feature is enabled. + #[cfg(feature = "source_location")] + source_location: bool, + /// Control how timestamps are displayed. /// /// This field is only available if the `timestamps` feature is enabled. @@ -112,6 +119,9 @@ impl SimpleLogger { #[cfg(feature = "threads")] threads: false, + #[cfg(feature = "source_location")] + source_location: false, + #[cfg(feature = "timestamps")] timestamps: Timestamps::Utc, @@ -245,6 +255,17 @@ impl SimpleLogger { self } + /// Control whether filename and line number are printed or not. + /// + /// This method is only available if the `source_location` feature is enabled. + /// Code locations are disabled by default. + #[must_use = "You must call init() to begin logging"] + #[cfg(feature = "source_location")] + pub fn with_source_location(mut self, source_location: bool) -> SimpleLogger { + self.source_location = source_location; + self + } + /// Control whether timestamps are printed or not. /// /// Timestamps will be displayed in the local timezone. @@ -381,63 +402,86 @@ impl Log for SimpleLogger { } fn log(&self, record: &Record) { - if self.enabled(record.metadata()) { - let level_string = { - #[cfg(feature = "colors")] - { - if self.colors { - match record.level() { - Level::Error => format!("{:<5}", record.level().to_string()).red().to_string(), - Level::Warn => format!("{:<5}", record.level().to_string()).yellow().to_string(), - Level::Info => format!("{:<5}", record.level().to_string()).cyan().to_string(), - Level::Debug => format!("{:<5}", record.level().to_string()).purple().to_string(), - Level::Trace => format!("{:<5}", record.level().to_string()).normal().to_string(), - } - } else { - format!("{:<5}", record.level().to_string()) + #[expect(dead_code)] + struct DisplayNone; + impl fmt::Display for DisplayNone { + fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { + Ok(()) + } + } + #[cfg(feature = "colors")] + struct DisplayLevel<'a>(bool, &'a Record<'a>); + #[cfg(feature = "colors")] + impl fmt::Display for DisplayLevel<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.0 { + let s = self.1.level().to_string(); + match self.1.level() { + Level::Error => write!(f, "{:<5}", s.red()), + Level::Warn => write!(f, "{:<5}", s.yellow()), + Level::Info => write!(f, "{:<5}", s.cyan()), + Level::Debug => write!(f, "{:<5}", s.purple()), + Level::Trace => write!(f, "{:<5}", s.normal()), } + } else { + write!(f, "{:<5}", self.1.level()) } - #[cfg(not(feature = "colors"))] - { - format!("{:<5}", record.level().to_string()) - } - }; - - let target = if !record.target().is_empty() { - record.target() - } else { - record.module_path().unwrap_or_default() - }; - - let thread = { - #[cfg(feature = "threads")] - if self.threads { + } + } + #[cfg(feature = "threads")] + struct DisplayThreats(bool); + #[cfg(feature = "threads")] + impl fmt::Display for DisplayThreats { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.0 { let thread = std::thread::current(); - - format!("@{}", { + write!(f, "@{}", { #[cfg(feature = "nightly")] { thread.name().unwrap_or(&thread.id().as_u64().to_string()) } - #[cfg(not(feature = "nightly"))] { thread.name().unwrap_or("?") } }) } else { - "".to_string() + Ok(()) } - - #[cfg(not(feature = "threads"))] - "" - }; - - let timestamp = { - #[cfg(feature = "timestamps")] - match self.timestamps { - Timestamps::None => "".to_string(), - Timestamps::Local => format!( + } + } + #[cfg(feature = "source_location")] + struct DisplaySourceLocation<'a>(bool, &'a Record<'a>); + #[cfg(feature = "source_location")] + impl fmt::Display for DisplaySourceLocation<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.0 { + write!( + f, + "{}:{} ", + match self.1.file() { + Some(s) => s, + None => "unknown", + }, + match self.1.line() { + Some(s) => s.to_string(), + None => String::from("unknown"), + } + ) + } else { + Ok(()) + } + } + } + #[cfg(feature = "timestamps")] + struct DisplayTimestamps<'a>(&'a SimpleLogger); + #[cfg(feature = "timestamps")] + impl fmt::Display for DisplayTimestamps<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0.timestamps { + Timestamps::None => Ok(()), + Timestamps::Local => write!( + f, "{} ", OffsetDateTime::now_local() .expect(concat!( @@ -449,35 +493,99 @@ impl Log for SimpleLogger { "behaviour. See the time crate's documentation for more information. ", "(https://time-rs.github.io/internal-api/time/index.html#feature-flags)" )) - .format(&self.timestamps_format.unwrap_or(TIMESTAMP_FORMAT_OFFSET)) + .format(&self.0.timestamps_format.unwrap_or(TIMESTAMP_FORMAT_OFFSET)) .unwrap() ), - Timestamps::Utc => format!( + Timestamps::Utc => write!( + f, "{} ", OffsetDateTime::now_utc() - .format(&self.timestamps_format.unwrap_or(TIMESTAMP_FORMAT_UTC)) + .format(&self.0.timestamps_format.unwrap_or(TIMESTAMP_FORMAT_UTC)) .unwrap() ), - Timestamps::UtcOffset(offset) => format!( + Timestamps::UtcOffset(offset) => write!( + f, "{} ", OffsetDateTime::now_utc() .to_offset(offset) - .format(&self.timestamps_format.unwrap_or(TIMESTAMP_FORMAT_OFFSET)) + .format(&self.0.timestamps_format.unwrap_or(TIMESTAMP_FORMAT_OFFSET)) .unwrap() ), } + } + } + if self.enabled(record.metadata()) { + let level_string = { + #[cfg(feature = "colors")] + { + DisplayLevel(self.colors, record) + } + #[cfg(not(feature = "colors"))] + { + DisplayNone + } + }; - #[cfg(not(feature = "timestamps"))] - "" + let target = if !record.target().is_empty() { + record.target() + } else { + record.module_path().unwrap_or_default() + }; + + let source_location = { + #[cfg(feature = "source_location")] + { + DisplaySourceLocation(self.source_location, record) + } + #[cfg(not(feature = "source_location"))] + { + DisplayNone + } }; - let message = format!("{}{} [{}{}] {}", timestamp, level_string, target, thread, record.args()); + let thread = { + #[cfg(feature = "threads")] + { + DisplayThreats(self.threads) + } + #[cfg(not(feature = "threads"))] + { + DisplayNone + } + }; + + let timestamp = { + #[cfg(feature = "timestamps")] + { + DisplayTimestamps(self) + } + #[cfg(not(feature = "timestamps"))] + { + DisplayNone + } + }; #[cfg(not(feature = "stderr"))] - println!("{}", message); + println!( + "{}{} [{}{}{}] {}", + timestamp, + level_string, + source_location, + target, + thread, + record.args() + ); #[cfg(feature = "stderr")] - eprintln!("{}", message); + eprintln!( + "{}{} [{}{}{}] {}", + timestamp, + level_string, + source_location, + target, + thread, + record.args() + ); } }