From a7d7d480396c6c4acb9049d9fb0cdc426e2e8e3b Mon Sep 17 00:00:00 2001 From: junzhuo Date: Sat, 31 May 2025 00:47:44 +0800 Subject: [PATCH 1/2] Add source_location feature to print source filename and line number --- Cargo.toml | 5 ++++ examples/source_location.rs | 7 +++++ src/lib.rs | 52 ++++++++++++++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 examples/source_location.rs diff --git a/Cargo.toml b/Cargo.toml index 8f03266..a8f1048 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 = [] @@ -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..009de3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,6 +77,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 +118,9 @@ impl SimpleLogger { #[cfg(feature = "threads")] threads: false, + #[cfg(feature = "source_location")] + source_location: false, + #[cfg(feature = "timestamps")] timestamps: Timestamps::Utc, @@ -245,6 +254,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. @@ -409,6 +429,28 @@ impl Log for SimpleLogger { record.module_path().unwrap_or_default() }; + let source_location = { + #[cfg(feature = "source_location")] + if self.source_location { + format!( + "{}:{} ", + match record.file() { + Some(s) => s, + None => "unknown", + }, + match record.line() { + Some(s) => s.to_string(), + None => String::from("unknown"), + } + ) + } else { + "".to_string() + } + + #[cfg(not(feature = "source_location"))] + "" + }; + let thread = { #[cfg(feature = "threads")] if self.threads { @@ -471,7 +513,15 @@ impl Log for SimpleLogger { "" }; - let message = format!("{}{} [{}{}] {}", timestamp, level_string, target, thread, record.args()); + let message = format!( + "{}{} [{}{}{}] {}", + timestamp, + level_string, + source_location, + target, + thread, + record.args() + ); #[cfg(not(feature = "stderr"))] println!("{}", message); From b7a4f0071646db63aec7079849b48166246f820a Mon Sep 17 00:00:00 2001 From: junzhuo Date: Sat, 31 May 2025 01:26:56 +0800 Subject: [PATCH 2/2] Improve fmt to lazy --- Cargo.toml | 2 +- src/lib.rs | 208 ++++++++++++++++++++++++++++++++++------------------- 2 files changed, 134 insertions(+), 76 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a8f1048..d1af131 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,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"] } diff --git a/src/lib.rs b/src/lib.rs index 009de3d..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")] @@ -401,85 +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()), } - } - #[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 source_location = { - #[cfg(feature = "source_location")] - if self.source_location { - format!( - "{}:{} ", - match record.file() { - Some(s) => s, - None => "unknown", - }, - match record.line() { - Some(s) => s.to_string(), - None => String::from("unknown"), - } - ) } else { - "".to_string() + write!(f, "{:<5}", self.1.level()) } - - #[cfg(not(feature = "source_location"))] - "" - }; - - 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!( @@ -491,29 +493,80 @@ 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 + } + }; + 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 thread = { + #[cfg(feature = "threads")] + { + DisplayThreats(self.threads) + } + #[cfg(not(feature = "threads"))] + { + DisplayNone + } + }; + + let timestamp = { + #[cfg(feature = "timestamps")] + { + DisplayTimestamps(self) + } #[cfg(not(feature = "timestamps"))] - "" + { + DisplayNone + } }; - let message = format!( + #[cfg(not(feature = "stderr"))] + println!( "{}{} [{}{}{}] {}", timestamp, level_string, @@ -523,11 +576,16 @@ impl Log for SimpleLogger { record.args() ); - #[cfg(not(feature = "stderr"))] - println!("{}", message); - #[cfg(feature = "stderr")] - eprintln!("{}", message); + eprintln!( + "{}{} [{}{}{}] {}", + timestamp, + level_string, + source_location, + target, + thread, + record.args() + ); } }