diff --git a/.clippy.toml b/.clippy.toml index 146c2e97..18554b15 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,4 +1,4 @@ -msrv = "1.60.0" # MSRV +msrv = "1.71" # MSRV warn-on-all-wildcard-imports = true disallowed-methods = [ { path = "std::option::Option::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" }, diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de41ed3a..49e64dfc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,7 @@ jobs: - name: Run crate example run: cargo run --example default msrv: - name: "Check MSRV: 1.60.0" + name: "Check MSRV: 1.71" runs-on: ubuntu-latest steps: - name: Checkout repository @@ -56,7 +56,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.60" # MSRV + toolchain: "1.71" # MSRV - uses: Swatinem/rust-cache@v2 - uses: taiki-e/install-action@cargo-hack - name: Check @@ -115,7 +115,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.60" # MSRV + toolchain: "1.71" # MSRV components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools diff --git a/Cargo.lock b/Cargo.lock index 3b0e0086..69452d28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,103 +12,81 @@ dependencies = [ ] [[package]] -name = "bitflags" -version = "1.3.2" +name = "anstream" +version = "0.6.11" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "cc" -version = "1.0.77" -source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] [[package]] -name = "cfg-if" -version = "1.0.0" +name = "anstyle" +version = "1.0.4" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "env_logger" -version = "0.10.2" -dependencies = [ - "humantime", - "is-terminal", - "log", - "regex", - "termcolor", -] +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] -name = "errno" -version = "0.2.8" +name = "anstyle-parse" +version = "0.2.3" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ - "errno-dragonfly", - "libc", - "winapi", + "utf8parse", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "anstyle-query" +version = "1.0.2" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "cc", - "libc", + "windows-sys", ] [[package]] -name = "hermit-abi" -version = "0.2.6" +name = "anstyle-wincon" +version = "3.0.2" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ - "libc", + "anstyle", + "windows-sys", ] [[package]] -name = "humantime" -version = "2.1.0" +name = "cfg-if" +version = "1.0.0" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "io-lifetimes" -version = "1.0.1" +name = "colorchoice" +version = "1.0.0" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "a7d367024b3f3414d8e01f437f704f41a9f64ab36f9067fa73e526ad4c763c87" -dependencies = [ - "libc", - "windows-sys", -] +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] -name = "is-terminal" -version = "0.4.0" -source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "aae5bc6e2eb41c9def29a3e0f1306382807764b9b53112030eff57435667352d" +name = "env_logger" +version = "0.10.2" dependencies = [ - "hermit-abi", - "io-lifetimes", - "rustix", - "windows-sys", + "anstream", + "anstyle", + "humantime", + "log", + "regex", ] [[package]] -name = "libc" -version = "0.2.137" -source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" - -[[package]] -name = "linux-raw-sys" -version = "0.1.3" +name = "humantime" +version = "2.1.0" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "log" @@ -143,64 +121,25 @@ source = "registry+/~https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] -name = "rustix" -version = "0.36.3" +name = "utf8parse" +version = "0.2.1" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "0b1fbb4dfc4eb1d390c02df47760bb19a84bb80b301ecc947ab5406394d8223e" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys", -] +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" +name = "windows-sys" +version = "0.52.0" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "winapi", + "windows-targets", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.42.0" +name = "windows-targets" +version = "0.52.0" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -213,42 +152,42 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.52.0" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.52.0" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.52.0" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.52.0" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.52.0" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.52.0" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.52.0" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/Cargo.toml b/Cargo.toml index 08da79ce..a4ccec85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ categories = ["development-tools::debugging"] keywords = ["logging", "log", "logger"] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.60.0" # MSRV +rust-version = "1.71" # MSRV include = [ "build.rs", "src/**/*", @@ -38,17 +38,17 @@ pre-release-replacements = [ [features] default = ["auto-color", "humantime", "regex"] -color = ["dep:termcolor"] -auto-color = ["dep:is-terminal", "color"] +color = ["dep:anstream", "dep:anstyle"] +auto-color = ["color", "anstream/auto"] humantime = ["dep:humantime"] regex = ["dep:regex"] [dependencies] log = { version = "0.4.8", features = ["std"] } regex = { version = "1.0.3", optional = true, default-features=false, features=["std", "perf"] } -termcolor = { version = "1.1.1", optional = true } humantime = { version = "2.0.0", optional = true } -is-terminal = { version = "0.4.0", optional = true } +anstream = { version = "0.6.11", default-features = false, features = ["wincon"], optional = true } +anstyle = { version = "1.0.4", optional = true } [[test]] name = "regexp_filter" diff --git a/examples/custom_format.rs b/examples/custom_format.rs index cc16b336..8f575fd4 100644 --- a/examples/custom_format.rs +++ b/examples/custom_format.rs @@ -19,7 +19,7 @@ If you want to control the logging output completely, see the `custom_logger` ex #[cfg(all(feature = "color", feature = "humantime"))] fn main() { - use env_logger::{fmt::Color, Builder, Env}; + use env_logger::{Builder, Env}; use std::io::Write; @@ -30,16 +30,17 @@ fn main() { Builder::from_env(env) .format(|buf, record| { - let mut style = buf.style(); - style.set_bg(Color::Yellow).set_bold(true); - + // We are reusing `anstyle` but there are `anstyle-*` crates to adapt it to your + // preferred styling crate. + let warn_style = buf.default_level_style(log::Level::Warn); + let reset = warn_style.render_reset(); + let warn_style = warn_style.render(); let timestamp = buf.timestamp(); writeln!( buf, - "My formatted log ({}): {}", - timestamp, - style.value(record.args()) + "My formatted log ({timestamp}): {warn_style}{}{reset}", + record.args() ) }) .init(); diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs index 95feb782..faee69bb 100644 --- a/src/fmt/mod.rs +++ b/src/fmt/mod.rs @@ -44,9 +44,7 @@ mod humantime; pub(crate) mod writer; #[cfg(feature = "color")] -mod style; -#[cfg(feature = "color")] -pub use style::{Color, Style, StyledValue}; +pub use anstyle as style; #[cfg(feature = "humantime")] pub use self::humantime::Timestamp; @@ -128,57 +126,23 @@ impl Formatter { #[cfg(feature = "color")] impl Formatter { - /// Begin a new [`Style`]. - /// - /// # Examples - /// - /// Create a bold, red colored style and use it to print the log level: - /// - /// ``` - /// use std::io::Write; - /// use env_logger::fmt::Color; - /// - /// let mut builder = env_logger::Builder::new(); - /// - /// builder.format(|buf, record| { - /// let mut level_style = buf.style(); - /// - /// level_style.set_color(Color::Red).set_bold(true); - /// - /// writeln!(buf, "{}: {}", - /// level_style.value(record.level()), - /// record.args()) - /// }); - /// ``` - /// - /// [`Style`]: struct.Style.html - pub fn style(&self) -> Style { - Style { - buf: self.buf.clone(), - spec: termcolor::ColorSpec::new(), - } - } - - /// Get the default [`Style`] for the given level. + /// Get the default [`style::Style`] for the given level. /// /// The style can be used to print other values besides the level. - pub fn default_level_style(&self, level: Level) -> Style { - let mut level_style = self.style(); - match level { - Level::Trace => level_style.set_color(Color::Cyan), - Level::Debug => level_style.set_color(Color::Blue), - Level::Info => level_style.set_color(Color::Green), - Level::Warn => level_style.set_color(Color::Yellow), - Level::Error => level_style.set_color(Color::Red).set_bold(true), - }; - level_style - } - - /// Get a printable [`Style`] for the given level. - /// - /// The style can only be used to print the level. - pub fn default_styled_level(&self, level: Level) -> StyledValue<'static, Level> { - self.default_level_style(level).into_value(level) + pub fn default_level_style(&self, level: Level) -> style::Style { + if self.write_style == WriteStyle::Never { + style::Style::new() + } else { + match level { + Level::Trace => style::AnsiColor::Cyan.on_default(), + Level::Debug => style::AnsiColor::Blue.on_default(), + Level::Info => style::AnsiColor::Green.on_default(), + Level::Warn => style::AnsiColor::Yellow.on_default(), + Level::Error => style::AnsiColor::Red + .on_default() + .effects(style::Effects::BOLD), + } + } } } @@ -194,7 +158,11 @@ impl Write for Formatter { impl fmt::Debug for Formatter { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Formatter").finish() + let buf = self.buf.borrow(); + f.debug_struct("Formatter") + .field("buf", &buf) + .field("write_style", &self.write_style) + .finish() } } @@ -265,10 +233,36 @@ impl Default for Builder { } #[cfg(feature = "color")] -type SubtleStyle = StyledValue<'static, &'static str>; +type SubtleStyle = StyledValue<&'static str>; #[cfg(not(feature = "color"))] type SubtleStyle = &'static str; +/// A value that can be printed using the given styles. +/// +/// It is the result of calling [`Style::value`]. +/// +/// [`Style::value`]: struct.Style.html#method.value +#[cfg(feature = "color")] +struct StyledValue { + style: style::Style, + value: T, +} + +#[cfg(feature = "color")] +impl std::fmt::Display for StyledValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let style = self.style.render(); + let reset = self.style.render_reset(); + + // We need to make sure `f`s settings don't get passed onto the styling but do get passed + // to the value + write!(f, "{style}")?; + self.value.fmt(f)?; + write!(f, "{reset}")?; + Ok(()) + } +} + /// The default format. /// /// This format needs to work with any combination of crate features. @@ -297,12 +291,14 @@ impl<'a> DefaultFormat<'a> { fn subtle_style(&self, text: &'static str) -> SubtleStyle { #[cfg(feature = "color")] { - self.buf - .style() - .set_color(Color::Black) - .set_intense(true) - .clone() - .into_value(text) + StyledValue { + style: if self.buf.write_style == WriteStyle::Never { + style::Style::new() + } else { + style::AnsiColor::BrightBlack.on_default() + }, + value: text, + } } #[cfg(not(feature = "color"))] { @@ -330,13 +326,17 @@ impl<'a> DefaultFormat<'a> { } let level = { + let level = record.level(); #[cfg(feature = "color")] { - self.buf.default_styled_level(record.level()) + StyledValue { + style: self.buf.default_level_style(level), + value: level, + } } #[cfg(not(feature = "color"))] { - record.level() + level } }; diff --git a/src/fmt/style.rs b/src/fmt/style.rs deleted file mode 100644 index dd4463ac..00000000 --- a/src/fmt/style.rs +++ /dev/null @@ -1,351 +0,0 @@ -use std::borrow::Cow; -use std::cell::RefCell; -use std::fmt; -use std::rc::Rc; - -use super::Buffer; - -/// A set of styles to apply to the terminal output. -/// -/// Call [`Formatter::style`] to get a `Style` and use the builder methods to -/// set styling properties, like [color] and [weight]. -/// To print a value using the style, wrap it in a call to [`value`] when the log -/// record is formatted. -/// -/// # Examples -/// -/// Create a bold, red colored style and use it to print the log level: -/// -/// ``` -/// use std::io::Write; -/// use env_logger::fmt::Color; -/// -/// let mut builder = env_logger::Builder::new(); -/// -/// builder.format(|buf, record| { -/// let mut level_style = buf.style(); -/// -/// level_style.set_color(Color::Red).set_bold(true); -/// -/// writeln!(buf, "{}: {}", -/// level_style.value(record.level()), -/// record.args()) -/// }); -/// ``` -/// -/// Styles can be re-used to output multiple values: -/// -/// ``` -/// use std::io::Write; -/// use env_logger::fmt::Color; -/// -/// let mut builder = env_logger::Builder::new(); -/// -/// builder.format(|buf, record| { -/// let mut bold = buf.style(); -/// -/// bold.set_bold(true); -/// -/// writeln!(buf, "{}: {} {}", -/// bold.value(record.level()), -/// bold.value("some bold text"), -/// record.args()) -/// }); -/// ``` -/// -/// [`Formatter::style`]: struct.Formatter.html#method.style -/// [color]: #method.set_color -/// [weight]: #method.set_bold -/// [`value`]: #method.value -#[derive(Clone)] -pub struct Style { - pub(in crate::fmt) buf: Rc>, - pub(in crate::fmt) spec: termcolor::ColorSpec, -} - -impl Style { - /// Set the text color. - /// - /// # Examples - /// - /// Create a style with red text: - /// - /// ``` - /// use std::io::Write; - /// use env_logger::fmt::Color; - /// - /// let mut builder = env_logger::Builder::new(); - /// - /// builder.format(|buf, record| { - /// let mut style = buf.style(); - /// - /// style.set_color(Color::Red); - /// - /// writeln!(buf, "{}", style.value(record.args())) - /// }); - /// ``` - pub fn set_color(&mut self, color: Color) -> &mut Style { - self.spec.set_fg(Some(color.into_termcolor())); - self - } - - /// Set the text weight. - /// - /// If `yes` is true then text will be written in bold. - /// If `yes` is false then text will be written in the default weight. - /// - /// # Examples - /// - /// Create a style with bold text: - /// - /// ``` - /// use std::io::Write; - /// - /// let mut builder = env_logger::Builder::new(); - /// - /// builder.format(|buf, record| { - /// let mut style = buf.style(); - /// - /// style.set_bold(true); - /// - /// writeln!(buf, "{}", style.value(record.args())) - /// }); - /// ``` - pub fn set_bold(&mut self, yes: bool) -> &mut Style { - self.spec.set_bold(yes); - self - } - - /// Set the text intensity. - /// - /// If `yes` is true then text will be written in a brighter color. - /// If `yes` is false then text will be written in the default color. - /// - /// # Examples - /// - /// Create a style with intense text: - /// - /// ``` - /// use std::io::Write; - /// - /// let mut builder = env_logger::Builder::new(); - /// - /// builder.format(|buf, record| { - /// let mut style = buf.style(); - /// - /// style.set_intense(true); - /// - /// writeln!(buf, "{}", style.value(record.args())) - /// }); - /// ``` - pub fn set_intense(&mut self, yes: bool) -> &mut Style { - self.spec.set_intense(yes); - self - } - - /// Set whether the text is dimmed. - /// - /// If `yes` is true then text will be written in a dimmer color. - /// If `yes` is false then text will be written in the default color. - /// - /// # Examples - /// - /// Create a style with dimmed text: - /// - /// ``` - /// use std::io::Write; - /// - /// let mut builder = env_logger::Builder::new(); - /// - /// builder.format(|buf, record| { - /// let mut style = buf.style(); - /// - /// style.set_dimmed(true); - /// - /// writeln!(buf, "{}", style.value(record.args())) - /// }); - /// ``` - pub fn set_dimmed(&mut self, yes: bool) -> &mut Style { - self.spec.set_dimmed(yes); - self - } - - /// Set the background color. - /// - /// # Examples - /// - /// Create a style with a yellow background: - /// - /// ``` - /// use std::io::Write; - /// use env_logger::fmt::Color; - /// - /// let mut builder = env_logger::Builder::new(); - /// - /// builder.format(|buf, record| { - /// let mut style = buf.style(); - /// - /// style.set_bg(Color::Yellow); - /// - /// writeln!(buf, "{}", style.value(record.args())) - /// }); - /// ``` - pub fn set_bg(&mut self, color: Color) -> &mut Style { - self.spec.set_bg(Some(color.into_termcolor())); - self - } - - /// Wrap a value in the style. - /// - /// The same `Style` can be used to print multiple different values. - /// - /// # Examples - /// - /// Create a bold, red colored style and use it to print the log level: - /// - /// ``` - /// use std::io::Write; - /// use env_logger::fmt::Color; - /// - /// let mut builder = env_logger::Builder::new(); - /// - /// builder.format(|buf, record| { - /// let mut style = buf.style(); - /// - /// style.set_color(Color::Red).set_bold(true); - /// - /// writeln!(buf, "{}: {}", - /// style.value(record.level()), - /// record.args()) - /// }); - /// ``` - pub fn value(&self, value: T) -> StyledValue { - StyledValue { - style: Cow::Borrowed(self), - value, - } - } - - /// Wrap a value in the style by taking ownership of it. - pub(crate) fn into_value(self, value: T) -> StyledValue<'static, T> { - StyledValue { - style: Cow::Owned(self), - value, - } - } -} - -impl fmt::Debug for Style { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Style").field("spec", &self.spec).finish() - } -} - -/// A value that can be printed using the given styles. -/// -/// It is the result of calling [`Style::value`]. -/// -/// [`Style::value`]: struct.Style.html#method.value -pub struct StyledValue<'a, T> { - style: Cow<'a, Style>, - value: T, -} - -impl<'a, T> StyledValue<'a, T> { - fn write_fmt(&self, f: F) -> fmt::Result - where - F: FnOnce() -> fmt::Result, - { - self.style - .buf - .borrow_mut() - .set_color(&self.style.spec) - .map_err(|_| fmt::Error)?; - - // Always try to reset the terminal style, even if writing failed - let write = f(); - let reset = self.style.buf.borrow_mut().reset().map_err(|_| fmt::Error); - - write.and(reset) - } -} - -macro_rules! impl_styled_value_fmt { - ($($fmt_trait:path),*) => { - $( - impl<'a, T: $fmt_trait> $fmt_trait for StyledValue<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { - self.write_fmt(|| T::fmt(&self.value, f)) - } - } - )* - }; -} - -impl_styled_value_fmt!( - fmt::Debug, - fmt::Display, - fmt::Pointer, - fmt::Octal, - fmt::Binary, - fmt::UpperHex, - fmt::LowerHex, - fmt::UpperExp, - fmt::LowerExp -); - -// The `Color` type is copied from /~https://github.com/BurntSushi/termcolor - -/// The set of available colors for the terminal foreground/background. -/// -/// The `Ansi256` and `Rgb` colors will only output the correct codes when -/// paired with the `Ansi` `WriteColor` implementation. -/// -/// The `Ansi256` and `Rgb` color types are not supported when writing colors -/// on Windows using the console. If they are used on Windows, then they are -/// silently ignored and no colors will be emitted. -/// -/// This set may expand over time. -/// -/// This type has a `FromStr` impl that can parse colors from their human -/// readable form. The format is as follows: -/// -/// 1. Any of the explicitly listed colors in English. They are matched -/// case insensitively. -/// 2. A single 8-bit integer, in either decimal or hexadecimal format. -/// 3. A triple of 8-bit integers separated by a comma, where each integer is -/// in decimal or hexadecimal format. -/// -/// Hexadecimal numbers are written with a `0x` prefix. -#[allow(missing_docs)] -#[non_exhaustive] -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Color { - Black, - Blue, - Green, - Red, - Cyan, - Magenta, - Yellow, - White, - Ansi256(u8), - Rgb(u8, u8, u8), -} - -impl Color { - fn into_termcolor(self) -> termcolor::Color { - match self { - Color::Black => termcolor::Color::Black, - Color::Blue => termcolor::Color::Blue, - Color::Green => termcolor::Color::Green, - Color::Red => termcolor::Color::Red, - Color::Cyan => termcolor::Color::Cyan, - Color::Magenta => termcolor::Color::Magenta, - Color::Yellow => termcolor::Color::Yellow, - Color::White => termcolor::Color::White, - Color::Ansi256(value) => termcolor::Color::Ansi256(value), - Color::Rgb(r, g, b) => termcolor::Color::Rgb(r, g, b), - } - } -} diff --git a/src/fmt/writer/atty.rs b/src/fmt/writer/atty.rs deleted file mode 100644 index 1a133eef..00000000 --- a/src/fmt/writer/atty.rs +++ /dev/null @@ -1,33 +0,0 @@ -/* -This internal module contains the terminal detection implementation. - -If the `auto-color` feature is enabled then we detect whether we're attached to a particular TTY. -Otherwise, assume we're not attached to anything. This effectively prevents styles from being -printed. -*/ - -#[cfg(feature = "auto-color")] -mod imp { - use is_terminal::IsTerminal; - - pub(in crate::fmt) fn is_stdout() -> bool { - std::io::stdout().is_terminal() - } - - pub(in crate::fmt) fn is_stderr() -> bool { - std::io::stderr().is_terminal() - } -} - -#[cfg(not(feature = "auto-color"))] -mod imp { - pub(in crate::fmt) fn is_stdout() -> bool { - false - } - - pub(in crate::fmt) fn is_stderr() -> bool { - false - } -} - -pub(in crate::fmt) use self::imp::*; diff --git a/src/fmt/writer/buffer.rs b/src/fmt/writer/buffer.rs new file mode 100644 index 00000000..a7ea25bf --- /dev/null +++ b/src/fmt/writer/buffer.rs @@ -0,0 +1,169 @@ +use std::{io, sync::Mutex}; + +use crate::fmt::writer::WriteStyle; + +#[derive(Debug)] +pub(in crate::fmt::writer) struct BufferWriter { + target: WritableTarget, + write_style: WriteStyle, +} + +impl BufferWriter { + pub(in crate::fmt::writer) fn stderr(is_test: bool, write_style: WriteStyle) -> Self { + BufferWriter { + target: if is_test { + WritableTarget::PrintStderr + } else { + WritableTarget::WriteStderr + }, + write_style, + } + } + + pub(in crate::fmt::writer) fn stdout(is_test: bool, write_style: WriteStyle) -> Self { + BufferWriter { + target: if is_test { + WritableTarget::PrintStdout + } else { + WritableTarget::WriteStdout + }, + write_style, + } + } + + pub(in crate::fmt::writer) fn pipe(pipe: Box>) -> Self { + BufferWriter { + target: WritableTarget::Pipe(pipe), + write_style: WriteStyle::Never, + } + } + + pub(in crate::fmt::writer) fn write_style(&self) -> WriteStyle { + self.write_style + } + + pub(in crate::fmt::writer) fn buffer(&self) -> Buffer { + Buffer(Vec::new()) + } + + pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { + use std::io::Write as _; + + let buf = buf.as_bytes(); + match &self.target { + WritableTarget::WriteStdout => { + let stream = std::io::stdout(); + #[cfg(feature = "color")] + let stream = anstream::AutoStream::new(stream, self.write_style.into()); + let mut stream = stream.lock(); + stream.write_all(buf)?; + stream.flush()?; + } + WritableTarget::PrintStdout => { + #[cfg(feature = "color")] + let buf = adapt(buf, self.write_style)?; + #[cfg(feature = "color")] + let buf = &buf; + let buf = String::from_utf8_lossy(buf); + print!("{}", buf); + } + WritableTarget::WriteStderr => { + let stream = std::io::stderr(); + #[cfg(feature = "color")] + let stream = anstream::AutoStream::new(stream, self.write_style.into()); + let mut stream = stream.lock(); + stream.write_all(buf)?; + stream.flush()?; + } + WritableTarget::PrintStderr => { + #[cfg(feature = "color")] + let buf = adapt(buf, self.write_style)?; + #[cfg(feature = "color")] + let buf = &buf; + let buf = String::from_utf8_lossy(buf); + eprint!("{}", buf); + } + WritableTarget::Pipe(pipe) => { + #[cfg(feature = "color")] + let buf = adapt(buf, self.write_style)?; + #[cfg(feature = "color")] + let buf = &buf; + let mut stream = pipe.lock().unwrap(); + stream.write_all(buf)?; + stream.flush()?; + } + } + + Ok(()) + } +} + +#[cfg(feature = "color")] +fn adapt(buf: &[u8], write_style: WriteStyle) -> std::io::Result> { + use std::io::Write as _; + + let adapted = Vec::with_capacity(buf.len()); + let mut stream = anstream::AutoStream::new(adapted, write_style.into()); + stream.write_all(buf)?; + let adapted = stream.into_inner(); + Ok(adapted) +} + +pub(in crate::fmt) struct Buffer(Vec); + +impl Buffer { + pub(in crate::fmt) fn clear(&mut self) { + self.0.clear(); + } + + pub(in crate::fmt) fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.extend(buf); + Ok(buf.len()) + } + + pub(in crate::fmt) fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + + pub(in crate::fmt) fn as_bytes(&self) -> &[u8] { + &self.0 + } +} + +impl std::fmt::Debug for Buffer { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + String::from_utf8_lossy(self.as_bytes()).fmt(f) + } +} + +/// Log target, either `stdout`, `stderr` or a custom pipe. +/// +/// Same as `Target`, except the pipe is wrapped in a mutex for interior mutability. +pub(super) enum WritableTarget { + /// Logs will be written to standard output. + WriteStdout, + /// Logs will be printed to standard output. + PrintStdout, + /// Logs will be written to standard error. + WriteStderr, + /// Logs will be printed to standard error. + PrintStderr, + /// Logs will be sent to a custom pipe. + Pipe(Box>), +} + +impl std::fmt::Debug for WritableTarget { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::WriteStdout => "stdout", + Self::PrintStdout => "stdout", + Self::WriteStderr => "stderr", + Self::PrintStderr => "stderr", + Self::Pipe(_) => "pipe", + } + ) + } +} diff --git a/src/fmt/writer/buffer/mod.rs b/src/fmt/writer/buffer/mod.rs deleted file mode 100644 index 4e678b2d..00000000 --- a/src/fmt/writer/buffer/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -/* -This internal module contains the style and terminal writing implementation. - -Its public API is available when the `termcolor` crate is available. -The terminal printing is shimmed when the `termcolor` crate is not available. -*/ - -#[cfg(feature = "color")] -mod termcolor; -#[cfg(feature = "color")] -pub(in crate::fmt) use self::termcolor::*; -#[cfg(not(feature = "color"))] -mod plain; -#[cfg(not(feature = "color"))] -pub(in crate::fmt) use plain::*; diff --git a/src/fmt/writer/buffer/plain.rs b/src/fmt/writer/buffer/plain.rs deleted file mode 100644 index 99056783..00000000 --- a/src/fmt/writer/buffer/plain.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::{io, sync::Mutex}; - -use crate::fmt::writer::{WritableTarget, WriteStyle}; - -pub(in crate::fmt::writer) struct BufferWriter { - target: WritableTarget, -} - -impl BufferWriter { - pub(in crate::fmt::writer) fn stderr(is_test: bool, _write_style: WriteStyle) -> Self { - BufferWriter { - target: if is_test { - WritableTarget::PrintStderr - } else { - WritableTarget::WriteStderr - }, - } - } - - pub(in crate::fmt::writer) fn stdout(is_test: bool, _write_style: WriteStyle) -> Self { - BufferWriter { - target: if is_test { - WritableTarget::PrintStdout - } else { - WritableTarget::WriteStdout - }, - } - } - - pub(in crate::fmt::writer) fn pipe(pipe: Box>) -> Self { - BufferWriter { - target: WritableTarget::Pipe(pipe), - } - } - - pub(in crate::fmt::writer) fn write_style(&self) -> WriteStyle { - WriteStyle::Never - } - - pub(in crate::fmt::writer) fn buffer(&self) -> Buffer { - Buffer(Vec::new()) - } - - pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { - self.target.print(buf) - } -} - -pub(in crate::fmt) struct Buffer(Vec); - -impl Buffer { - pub(in crate::fmt) fn clear(&mut self) { - self.0.clear(); - } - - pub(in crate::fmt) fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.extend(buf); - Ok(buf.len()) - } - - pub(in crate::fmt) fn flush(&mut self) -> io::Result<()> { - Ok(()) - } - - pub(in crate::fmt) fn as_bytes(&self) -> &[u8] { - &self.0 - } -} diff --git a/src/fmt/writer/buffer/termcolor.rs b/src/fmt/writer/buffer/termcolor.rs deleted file mode 100644 index 648ed16f..00000000 --- a/src/fmt/writer/buffer/termcolor.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::io::{self, Write}; -use std::sync::Mutex; - -use termcolor::{self, ColorSpec, WriteColor}; - -use crate::fmt::writer::{WritableTarget, WriteStyle}; - -pub(in crate::fmt::writer) struct BufferWriter { - inner: termcolor::BufferWriter, - uncolored_target: Option, - write_style: WriteStyle, -} - -impl BufferWriter { - pub(in crate::fmt::writer) fn stderr(is_test: bool, write_style: WriteStyle) -> Self { - BufferWriter { - inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()), - uncolored_target: if is_test { - Some(WritableTarget::PrintStderr) - } else { - None - }, - write_style, - } - } - - pub(in crate::fmt::writer) fn stdout(is_test: bool, write_style: WriteStyle) -> Self { - BufferWriter { - inner: termcolor::BufferWriter::stdout(write_style.into_color_choice()), - uncolored_target: if is_test { - Some(WritableTarget::PrintStdout) - } else { - None - }, - write_style, - } - } - - pub(in crate::fmt::writer) fn pipe(pipe: Box>) -> Self { - let write_style = WriteStyle::Never; - BufferWriter { - // The inner Buffer is never printed from, but it is still needed to handle coloring and other formatting - inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()), - uncolored_target: Some(WritableTarget::Pipe(pipe)), - write_style, - } - } - - pub(in crate::fmt::writer) fn write_style(&self) -> WriteStyle { - self.write_style - } - - pub(in crate::fmt::writer) fn buffer(&self) -> Buffer { - Buffer { - inner: self.inner.buffer(), - has_uncolored_target: self.uncolored_target.is_some(), - } - } - - pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { - if let Some(target) = &self.uncolored_target { - target.print(buf) - } else { - self.inner.print(&buf.inner) - } - } -} - -pub(in crate::fmt) struct Buffer { - inner: termcolor::Buffer, - has_uncolored_target: bool, -} - -impl Buffer { - pub(in crate::fmt) fn clear(&mut self) { - self.inner.clear() - } - - pub(in crate::fmt) fn write(&mut self, buf: &[u8]) -> io::Result { - self.inner.write(buf) - } - - pub(in crate::fmt) fn flush(&mut self) -> io::Result<()> { - self.inner.flush() - } - - pub(in crate::fmt) fn as_bytes(&self) -> &[u8] { - self.inner.as_slice() - } - - pub(in crate::fmt) fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { - // Ignore styles for test captured logs because they can't be printed - if !self.has_uncolored_target { - self.inner.set_color(spec) - } else { - Ok(()) - } - } - - pub(in crate::fmt) fn reset(&mut self) -> io::Result<()> { - // Ignore styles for test captured logs because they can't be printed - if !self.has_uncolored_target { - self.inner.reset() - } else { - Ok(()) - } - } -} diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index c8753e0f..dbcf45b6 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -1,20 +1,18 @@ -mod atty; mod buffer; mod target; -use self::atty::{is_stderr, is_stdout}; use self::buffer::BufferWriter; -use std::{fmt, io, mem, sync::Mutex}; +use std::{io, mem, sync::Mutex}; pub(super) use self::buffer::Buffer; pub use target::Target; -use target::WritableTarget; /// Whether or not to print styles to the target. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Default)] pub enum WriteStyle { /// Try to print styles, but don't force the issue. + #[default] Auto, /// Try very hard to print styles. Always, @@ -22,24 +20,31 @@ pub enum WriteStyle { Never, } -impl Default for WriteStyle { - fn default() -> Self { - WriteStyle::Auto +#[cfg(feature = "color")] +impl From for WriteStyle { + fn from(choice: anstream::ColorChoice) -> Self { + match choice { + anstream::ColorChoice::Auto => Self::Auto, + anstream::ColorChoice::Always => Self::Always, + anstream::ColorChoice::AlwaysAnsi => Self::Always, + anstream::ColorChoice::Never => Self::Never, + } } } #[cfg(feature = "color")] -impl WriteStyle { - fn into_color_choice(self) -> ::termcolor::ColorChoice { - match self { - WriteStyle::Always => ::termcolor::ColorChoice::Always, - WriteStyle::Auto => ::termcolor::ColorChoice::Auto, - WriteStyle::Never => ::termcolor::ColorChoice::Never, +impl From for anstream::ColorChoice { + fn from(choice: WriteStyle) -> Self { + match choice { + WriteStyle::Auto => anstream::ColorChoice::Auto, + WriteStyle::Always => anstream::ColorChoice::Always, + WriteStyle::Never => anstream::ColorChoice::Never, } } } /// A terminal target with color awareness. +#[derive(Debug)] pub(crate) struct Writer { inner: BufferWriter, } @@ -58,12 +63,6 @@ impl Writer { } } -impl fmt::Debug for Writer { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Writer").finish() - } -} - /// A builder for a terminal writer. /// /// The target and style choice can be configured before building. @@ -119,29 +118,26 @@ impl Builder { assert!(!self.built, "attempt to re-use consumed builder"); self.built = true; - let color_choice = match self.write_style { - WriteStyle::Auto => { - if match &self.target { - Target::Stderr => is_stderr(), - Target::Stdout => is_stdout(), - Target::Pipe(_) => false, - } { - WriteStyle::Auto - } else { - WriteStyle::Never - } + let color_choice = self.write_style; + #[cfg(feature = "auto-color")] + let color_choice = if color_choice == WriteStyle::Auto { + match &self.target { + Target::Stdout => anstream::AutoStream::choice(&std::io::stdout()).into(), + Target::Stderr => anstream::AutoStream::choice(&std::io::stderr()).into(), + Target::Pipe(_) => color_choice, } - color_choice => color_choice, + } else { + color_choice }; - let color_choice = if self.is_test { + let color_choice = if color_choice == WriteStyle::Auto { WriteStyle::Never } else { color_choice }; let writer = match mem::take(&mut self.target) { - Target::Stderr => BufferWriter::stderr(self.is_test, color_choice), Target::Stdout => BufferWriter::stdout(self.is_test, color_choice), + Target::Stderr => BufferWriter::stderr(self.is_test, color_choice), Target::Pipe(pipe) => BufferWriter::pipe(Box::new(Mutex::new(pipe))), }; diff --git a/src/fmt/writer/target.rs b/src/fmt/writer/target.rs index 3d908665..a1220ffe 100644 --- a/src/fmt/writer/target.rs +++ b/src/fmt/writer/target.rs @@ -1,20 +1,16 @@ /// Log target, either `stdout`, `stderr` or a custom pipe. #[non_exhaustive] +#[derive(Default)] pub enum Target { /// Logs will be sent to standard output. Stdout, /// Logs will be sent to standard error. + #[default] Stderr, /// Logs will be sent to a custom pipe. Pipe(Box), } -impl Default for Target { - fn default() -> Self { - Target::Stderr - } -} - impl std::fmt::Debug for Target { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( @@ -28,69 +24,3 @@ impl std::fmt::Debug for Target { ) } } - -/// Log target, either `stdout`, `stderr` or a custom pipe. -/// -/// Same as `Target`, except the pipe is wrapped in a mutex for interior mutability. -pub(super) enum WritableTarget { - /// Logs will be written to standard output. - #[allow(dead_code)] - WriteStdout, - /// Logs will be printed to standard output. - PrintStdout, - /// Logs will be written to standard error. - #[allow(dead_code)] - WriteStderr, - /// Logs will be printed to standard error. - PrintStderr, - /// Logs will be sent to a custom pipe. - Pipe(Box>), -} - -impl WritableTarget { - pub(super) fn print(&self, buf: &super::Buffer) -> std::io::Result<()> { - use std::io::Write as _; - - let buf = buf.as_bytes(); - match self { - WritableTarget::WriteStdout => { - let stream = std::io::stdout(); - let mut stream = stream.lock(); - stream.write_all(buf)?; - stream.flush()?; - } - WritableTarget::PrintStdout => print!("{}", String::from_utf8_lossy(buf)), - WritableTarget::WriteStderr => { - let stream = std::io::stderr(); - let mut stream = stream.lock(); - stream.write_all(buf)?; - stream.flush()?; - } - WritableTarget::PrintStderr => eprint!("{}", String::from_utf8_lossy(buf)), - // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty. - WritableTarget::Pipe(pipe) => { - let mut stream = pipe.lock().unwrap(); - stream.write_all(buf)?; - stream.flush()?; - } - } - - Ok(()) - } -} - -impl std::fmt::Debug for WritableTarget { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::WriteStdout => "stdout", - Self::PrintStdout => "stdout", - Self::WriteStderr => "stderr", - Self::PrintStderr => "stderr", - Self::Pipe(_) => "pipe", - } - ) - } -} diff --git a/src/logger.rs b/src/logger.rs index 6c8a00d4..1e7d5894 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -223,6 +223,9 @@ impl Builder { /// to format and output without intermediate heap allocations. The default /// `env_logger` formatter takes advantage of this. /// + /// When the `color` feature is enabled, styling via ANSI escape codes is supported and the + /// output will automatically respect [`Builder::write_style`]. + /// /// # Examples /// /// Use a custom format to write only the log message: