From 5ebe30b9722ac700d414043ff099bad7f3978582 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 26 Oct 2023 09:14:51 -0500 Subject: [PATCH 01/53] chore(ci): Update pre-commit hooks --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3d9e40fd..68db968e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-yaml stages: [commit] @@ -15,7 +15,7 @@ repos: - id: detect-private-key stages: [commit] - repo: https://github.com/crate-ci/typos - rev: v1.16.3 + rev: v1.16.20 hooks: - id: typos stages: [commit] From c63d8cf411d0b823ddef4385e8929398989a8b1d Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 10 Nov 2023 09:31:14 -0600 Subject: [PATCH 02/53] refactor: Move impls next to types --- src/filter/mod.rs | 152 +-- .../extern_impl.rs => humantime.rs} | 4 - src/fmt/humantime/mod.rs | 11 - src/fmt/humantime/shim_impl.rs | 5 - src/fmt/mod.rs | 34 +- src/lib.rs | 1023 +---------------- src/logger.rs | 1016 ++++++++++++++++ 7 files changed, 1113 insertions(+), 1132 deletions(-) rename src/fmt/{humantime/extern_impl.rs => humantime.rs} (98%) delete mode 100644 src/fmt/humantime/mod.rs delete mode 100644 src/fmt/humantime/shim_impl.rs create mode 100644 src/logger.rs diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 81bb017a..1e647236 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -71,18 +71,6 @@ mod inner; #[path = "string.rs"] mod inner; -/// A log filter. -/// -/// This struct can be used to determine whether or not a log record -/// should be written to the output. -/// Use the [`Builder`] type to parse and construct a `Filter`. -/// -/// [`Builder`]: struct.Builder.html -pub struct Filter { - directives: Vec, - filter: Option, -} - /// A builder for a log filter. /// /// It can be used to parse a set of directives from a string before building @@ -112,61 +100,6 @@ pub struct Builder { built: bool, } -#[derive(Debug)] -struct Directive { - name: Option, - level: LevelFilter, -} - -impl Filter { - /// Returns the maximum `LevelFilter` that this filter instance is - /// configured to output. - /// - /// # Example - /// - /// ```rust - /// use log::LevelFilter; - /// use env_logger::filter::Builder; - /// - /// let mut builder = Builder::new(); - /// builder.filter(Some("module1"), LevelFilter::Info); - /// builder.filter(Some("module2"), LevelFilter::Error); - /// - /// let filter = builder.build(); - /// assert_eq!(filter.filter(), LevelFilter::Info); - /// ``` - pub fn filter(&self) -> LevelFilter { - self.directives - .iter() - .map(|d| d.level) - .max() - .unwrap_or(LevelFilter::Off) - } - - /// Checks if this record matches the configured filter. - pub fn matches(&self, record: &Record) -> bool { - if !self.enabled(record.metadata()) { - return false; - } - - if let Some(filter) = self.filter.as_ref() { - if !filter.is_match(&record.args().to_string()) { - return false; - } - } - - true - } - - /// Determines if a log message with the specified metadata would be logged. - pub fn enabled(&self, metadata: &Metadata) -> bool { - let level = metadata.level(); - let target = metadata.target(); - - enabled(&self.directives, level, target) - } -} - impl Builder { /// Initializes the filter builder with defaults. pub fn new() -> Builder { @@ -276,15 +209,6 @@ impl Default for Builder { } } -impl fmt::Debug for Filter { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Filter") - .field("filter", &self.filter) - .field("directives", &self.directives) - .finish() - } -} - impl fmt::Debug for Builder { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.built { @@ -298,6 +222,82 @@ impl fmt::Debug for Builder { } } +#[derive(Debug)] +struct Directive { + name: Option, + level: LevelFilter, +} + +/// A log filter. +/// +/// This struct can be used to determine whether or not a log record +/// should be written to the output. +/// Use the [`Builder`] type to parse and construct a `Filter`. +/// +/// [`Builder`]: struct.Builder.html +pub struct Filter { + directives: Vec, + filter: Option, +} + +impl Filter { + /// Returns the maximum `LevelFilter` that this filter instance is + /// configured to output. + /// + /// # Example + /// + /// ```rust + /// use log::LevelFilter; + /// use env_logger::filter::Builder; + /// + /// let mut builder = Builder::new(); + /// builder.filter(Some("module1"), LevelFilter::Info); + /// builder.filter(Some("module2"), LevelFilter::Error); + /// + /// let filter = builder.build(); + /// assert_eq!(filter.filter(), LevelFilter::Info); + /// ``` + pub fn filter(&self) -> LevelFilter { + self.directives + .iter() + .map(|d| d.level) + .max() + .unwrap_or(LevelFilter::Off) + } + + /// Checks if this record matches the configured filter. + pub fn matches(&self, record: &Record) -> bool { + if !self.enabled(record.metadata()) { + return false; + } + + if let Some(filter) = self.filter.as_ref() { + if !filter.is_match(&record.args().to_string()) { + return false; + } + } + + true + } + + /// Determines if a log message with the specified metadata would be logged. + pub fn enabled(&self, metadata: &Metadata) -> bool { + let level = metadata.level(); + let target = metadata.target(); + + enabled(&self.directives, level, target) + } +} + +impl fmt::Debug for Filter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Filter") + .field("filter", &self.filter) + .field("directives", &self.directives) + .finish() + } +} + /// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=error/foo") /// and return a vector with log directives. fn parse_spec(spec: &str) -> (Vec, Option) { diff --git a/src/fmt/humantime/extern_impl.rs b/src/fmt/humantime.rs similarity index 98% rename from src/fmt/humantime/extern_impl.rs rename to src/fmt/humantime.rs index bdf165c4..582a6318 100644 --- a/src/fmt/humantime/extern_impl.rs +++ b/src/fmt/humantime.rs @@ -7,10 +7,6 @@ use humantime::{ use crate::fmt::{Formatter, TimestampPrecision}; -pub(in crate::fmt) mod glob { - pub use super::*; -} - impl Formatter { /// Get a [`Timestamp`] for the current date and time in UTC. /// diff --git a/src/fmt/humantime/mod.rs b/src/fmt/humantime/mod.rs deleted file mode 100644 index ac23ae24..00000000 --- a/src/fmt/humantime/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -/* -This internal module contains the timestamp implementation. - -Its public API is available when the `humantime` crate is available. -*/ - -#[cfg_attr(feature = "humantime", path = "extern_impl.rs")] -#[cfg_attr(not(feature = "humantime"), path = "shim_impl.rs")] -mod imp; - -pub(in crate::fmt) use self::imp::*; diff --git a/src/fmt/humantime/shim_impl.rs b/src/fmt/humantime/shim_impl.rs deleted file mode 100644 index 906bf9e4..00000000 --- a/src/fmt/humantime/shim_impl.rs +++ /dev/null @@ -1,5 +0,0 @@ -/* -Timestamps aren't available when we don't have a `humantime` dependency. -*/ - -pub(in crate::fmt) mod glob {} diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs index 86c093f0..ded40278 100644 --- a/src/fmt/mod.rs +++ b/src/fmt/mod.rs @@ -37,10 +37,12 @@ use std::{fmt, io, mem}; use log::Record; +#[cfg(feature = "humantime")] mod humantime; pub(crate) mod writer; -pub use self::humantime::glob::*; +#[cfg(feature = "humantime")] +pub use self::humantime::Timestamp; pub use self::writer::glob::*; use self::writer::{Buffer, Writer}; @@ -149,21 +151,6 @@ pub(crate) struct Builder { built: bool, } -impl Default for Builder { - fn default() -> Self { - Builder { - format_timestamp: Some(Default::default()), - format_module_path: false, - format_target: true, - format_level: true, - format_indent: Some(4), - custom_format: None, - format_suffix: "\n", - built: false, - } - } -} - impl Builder { /// Convert the format into a callable function. /// @@ -202,6 +189,21 @@ impl Builder { } } +impl Default for Builder { + fn default() -> Self { + Builder { + format_timestamp: Some(Default::default()), + format_module_path: false, + format_target: true, + format_level: true, + format_indent: Some(4), + custom_format: None, + format_suffix: "\n", + built: false, + } + } +} + #[cfg(feature = "color")] type SubtleStyle = StyledValue<'static, &'static str>; #[cfg(not(feature = "color"))] diff --git a/src/lib.rs b/src/lib.rs index 4e5d5428..cdc2badc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -229,7 +229,7 @@ //! ### Using a custom format //! //! Custom formats can be provided as closures to the [`Builder`]. -//! These closures take a [`Formatter`] and `log::Record` as arguments: +//! These closures take a [`Formatter`][crate::fmt::Formatter] and `log::Record` as arguments: //! //! ``` //! use std::io::Write; @@ -275,1027 +275,10 @@ #![cfg_attr(rustbuild, unstable(feature = "rustc_private", issue = "27812"))] #![deny(missing_debug_implementations, missing_docs)] -use std::{borrow::Cow, cell::RefCell, env, io}; - -use log::{LevelFilter, Log, Metadata, Record, SetLoggerError}; +mod logger; pub mod filter; pub mod fmt; pub use self::fmt::glob::*; - -use self::filter::Filter; -use self::fmt::writer::{self, Writer}; -use self::fmt::{FormatFn, Formatter}; - -/// The default name for the environment variable to read filters from. -pub const DEFAULT_FILTER_ENV: &str = "RUST_LOG"; - -/// The default name for the environment variable to read style preferences from. -pub const DEFAULT_WRITE_STYLE_ENV: &str = "RUST_LOG_STYLE"; - -/// Set of environment variables to configure from. -/// -/// # Default environment variables -/// -/// By default, the `Env` will read the following environment variables: -/// -/// - `RUST_LOG`: the level filter -/// - `RUST_LOG_STYLE`: whether or not to print styles with records. -/// -/// These sources can be configured using the builder methods on `Env`. -#[derive(Debug)] -pub struct Env<'a> { - filter: Var<'a>, - write_style: Var<'a>, -} - -#[derive(Debug)] -struct Var<'a> { - name: Cow<'a, str>, - default: Option>, -} - -/// The env logger. -/// -/// This struct implements the `Log` trait from the [`log` crate][log-crate-url], -/// which allows it to act as a logger. -/// -/// The [`init()`], [`try_init()`], [`Builder::init()`] and [`Builder::try_init()`] -/// methods will each construct a `Logger` and immediately initialize it as the -/// default global logger. -/// -/// If you'd instead need access to the constructed `Logger`, you can use -/// the associated [`Builder`] and install it with the -/// [`log` crate][log-crate-url] directly. -/// -/// [log-crate-url]: https://docs.rs/log -/// [`init()`]: fn.init.html -/// [`try_init()`]: fn.try_init.html -/// [`Builder::init()`]: struct.Builder.html#method.init -/// [`Builder::try_init()`]: struct.Builder.html#method.try_init -/// [`Builder`]: struct.Builder.html -pub struct Logger { - writer: Writer, - filter: Filter, - format: FormatFn, -} - -/// `Builder` acts as builder for initializing a `Logger`. -/// -/// It can be used to customize the log format, change the environment variable used -/// to provide the logging directives and also set the default log level filter. -/// -/// # Examples -/// -/// ``` -/// # use std::io::Write; -/// use env_logger::Builder; -/// use log::{LevelFilter, error, info}; -/// -/// let mut builder = Builder::from_default_env(); -/// -/// builder -/// .format(|buf, record| writeln!(buf, "{} - {}", record.level(), record.args())) -/// .filter(None, LevelFilter::Info) -/// .init(); -/// -/// error!("error message"); -/// info!("info message"); -/// ``` -#[derive(Default)] -pub struct Builder { - filter: filter::Builder, - writer: writer::Builder, - format: fmt::Builder, - built: bool, -} - -impl Builder { - /// Initializes the log builder with defaults. - /// - /// **NOTE:** This method won't read from any environment variables. - /// Use the [`filter`] and [`write_style`] methods to configure the builder - /// or use [`from_env`] or [`from_default_env`] instead. - /// - /// # Examples - /// - /// Create a new builder and configure filters and style: - /// - /// ``` - /// use log::LevelFilter; - /// use env_logger::{Builder, WriteStyle}; - /// - /// let mut builder = Builder::new(); - /// - /// builder - /// .filter(None, LevelFilter::Info) - /// .write_style(WriteStyle::Always) - /// .init(); - /// ``` - /// - /// [`filter`]: #method.filter - /// [`write_style`]: #method.write_style - /// [`from_env`]: #method.from_env - /// [`from_default_env`]: #method.from_default_env - pub fn new() -> Builder { - Default::default() - } - - /// Initializes the log builder from the environment. - /// - /// The variables used to read configuration from can be tweaked before - /// passing in. - /// - /// # Examples - /// - /// Initialise a logger reading the log filter from an environment variable - /// called `MY_LOG`: - /// - /// ``` - /// use env_logger::Builder; - /// - /// let mut builder = Builder::from_env("MY_LOG"); - /// builder.init(); - /// ``` - /// - /// Initialise a logger using the `MY_LOG` variable for filtering and - /// `MY_LOG_STYLE` for whether or not to write styles: - /// - /// ``` - /// use env_logger::{Builder, Env}; - /// - /// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); - /// - /// let mut builder = Builder::from_env(env); - /// builder.init(); - /// ``` - pub fn from_env<'a, E>(env: E) -> Self - where - E: Into>, - { - let mut builder = Builder::new(); - builder.parse_env(env); - builder - } - - /// Applies the configuration from the environment. - /// - /// This function allows a builder to be configured with default parameters, - /// to be then overridden by the environment. - /// - /// # Examples - /// - /// Initialise a logger with filter level `Off`, then override the log - /// filter from an environment variable called `MY_LOG`: - /// - /// ``` - /// use log::LevelFilter; - /// use env_logger::Builder; - /// - /// let mut builder = Builder::new(); - /// - /// builder.filter_level(LevelFilter::Off); - /// builder.parse_env("MY_LOG"); - /// builder.init(); - /// ``` - /// - /// Initialise a logger with filter level `Off`, then use the `MY_LOG` - /// variable to override filtering and `MY_LOG_STYLE` to override whether - /// or not to write styles: - /// - /// ``` - /// use log::LevelFilter; - /// use env_logger::{Builder, Env}; - /// - /// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); - /// - /// let mut builder = Builder::new(); - /// builder.filter_level(LevelFilter::Off); - /// builder.parse_env(env); - /// builder.init(); - /// ``` - pub fn parse_env<'a, E>(&mut self, env: E) -> &mut Self - where - E: Into>, - { - let env = env.into(); - - if let Some(s) = env.get_filter() { - self.parse_filters(&s); - } - - if let Some(s) = env.get_write_style() { - self.parse_write_style(&s); - } - - self - } - - /// Initializes the log builder from the environment using default variable names. - /// - /// This method is a convenient way to call `from_env(Env::default())` without - /// having to use the `Env` type explicitly. The builder will use the - /// [default environment variables]. - /// - /// # Examples - /// - /// Initialise a logger using the default environment variables: - /// - /// ``` - /// use env_logger::Builder; - /// - /// let mut builder = Builder::from_default_env(); - /// builder.init(); - /// ``` - /// - /// [default environment variables]: struct.Env.html#default-environment-variables - pub fn from_default_env() -> Self { - Self::from_env(Env::default()) - } - - /// Applies the configuration from the environment using default variable names. - /// - /// This method is a convenient way to call `parse_env(Env::default())` without - /// having to use the `Env` type explicitly. The builder will use the - /// [default environment variables]. - /// - /// # Examples - /// - /// Initialise a logger with filter level `Off`, then configure it using the - /// default environment variables: - /// - /// ``` - /// use log::LevelFilter; - /// use env_logger::Builder; - /// - /// let mut builder = Builder::new(); - /// builder.filter_level(LevelFilter::Off); - /// builder.parse_default_env(); - /// builder.init(); - /// ``` - /// - /// [default environment variables]: struct.Env.html#default-environment-variables - pub fn parse_default_env(&mut self) -> &mut Self { - self.parse_env(Env::default()) - } - - /// Sets the format function for formatting the log output. - /// - /// This function is called on each record logged and should format the - /// log record and output it to the given [`Formatter`]. - /// - /// The format function is expected to output the string directly to the - /// `Formatter` so that implementations can use the [`std::fmt`] macros - /// to format and output without intermediate heap allocations. The default - /// `env_logger` formatter takes advantage of this. - /// - /// # Examples - /// - /// Use a custom format to write only the log message: - /// - /// ``` - /// use std::io::Write; - /// use env_logger::Builder; - /// - /// let mut builder = Builder::new(); - /// - /// builder.format(|buf, record| writeln!(buf, "{}", record.args())); - /// ``` - /// - /// [`Formatter`]: fmt/struct.Formatter.html - /// [`String`]: https://doc.rust-lang.org/stable/std/string/struct.String.html - /// [`std::fmt`]: https://doc.rust-lang.org/std/fmt/index.html - pub fn format(&mut self, format: F) -> &mut Self - where - F: Fn(&mut Formatter, &Record) -> io::Result<()> + Sync + Send, - { - self.format.custom_format = Some(Box::new(format)); - self - } - - /// Use the default format. - /// - /// This method will clear any custom format set on the builder. - pub fn default_format(&mut self) -> &mut Self { - self.format = Default::default(); - self - } - - /// Whether or not to write the level in the default format. - pub fn format_level(&mut self, write: bool) -> &mut Self { - self.format.format_level = write; - self - } - - /// Whether or not to write the module path in the default format. - pub fn format_module_path(&mut self, write: bool) -> &mut Self { - self.format.format_module_path = write; - self - } - - /// Whether or not to write the target in the default format. - pub fn format_target(&mut self, write: bool) -> &mut Self { - self.format.format_target = write; - self - } - - /// Configures the amount of spaces to use to indent multiline log records. - /// A value of `None` disables any kind of indentation. - pub fn format_indent(&mut self, indent: Option) -> &mut Self { - self.format.format_indent = indent; - self - } - - /// Configures if timestamp should be included and in what precision. - pub fn format_timestamp(&mut self, timestamp: Option) -> &mut Self { - self.format.format_timestamp = timestamp; - self - } - - /// Configures the timestamp to use second precision. - pub fn format_timestamp_secs(&mut self) -> &mut Self { - self.format_timestamp(Some(fmt::TimestampPrecision::Seconds)) - } - - /// Configures the timestamp to use millisecond precision. - pub fn format_timestamp_millis(&mut self) -> &mut Self { - self.format_timestamp(Some(fmt::TimestampPrecision::Millis)) - } - - /// Configures the timestamp to use microsecond precision. - pub fn format_timestamp_micros(&mut self) -> &mut Self { - self.format_timestamp(Some(fmt::TimestampPrecision::Micros)) - } - - /// Configures the timestamp to use nanosecond precision. - pub fn format_timestamp_nanos(&mut self) -> &mut Self { - self.format_timestamp(Some(fmt::TimestampPrecision::Nanos)) - } - - /// Configures the end of line suffix. - pub fn format_suffix(&mut self, suffix: &'static str) -> &mut Self { - self.format.format_suffix = suffix; - self - } - - /// Adds a directive to the filter for a specific module. - /// - /// # Examples - /// - /// Only include messages for info and above for logs in `path::to::module`: - /// - /// ``` - /// use env_logger::Builder; - /// use log::LevelFilter; - /// - /// let mut builder = Builder::new(); - /// - /// builder.filter_module("path::to::module", LevelFilter::Info); - /// ``` - pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self { - self.filter.filter_module(module, level); - self - } - - /// Adds a directive to the filter for all modules. - /// - /// # Examples - /// - /// Only include messages for info and above for logs globally: - /// - /// ``` - /// use env_logger::Builder; - /// use log::LevelFilter; - /// - /// let mut builder = Builder::new(); - /// - /// builder.filter_level(LevelFilter::Info); - /// ``` - pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self { - self.filter.filter_level(level); - self - } - - /// Adds filters to the logger. - /// - /// The given module (if any) will log at most the specified level provided. - /// If no module is provided then the filter will apply to all log messages. - /// - /// # Examples - /// - /// Only include messages for info and above for logs in `path::to::module`: - /// - /// ``` - /// use env_logger::Builder; - /// use log::LevelFilter; - /// - /// let mut builder = Builder::new(); - /// - /// builder.filter(Some("path::to::module"), LevelFilter::Info); - /// ``` - pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self { - self.filter.filter(module, level); - self - } - - /// Parses the directives string in the same form as the `RUST_LOG` - /// environment variable. - /// - /// See the module documentation for more details. - pub fn parse_filters(&mut self, filters: &str) -> &mut Self { - self.filter.parse(filters); - self - } - - /// Sets the target for the log output. - /// - /// Env logger can log to either stdout, stderr or a custom pipe. The default is stderr. - /// - /// The custom pipe can be used to send the log messages to a custom sink (for example a file). - /// Do note that direct writes to a file can become a bottleneck due to IO operation times. - /// - /// # Examples - /// - /// Write log message to `stdout`: - /// - /// ``` - /// use env_logger::{Builder, Target}; - /// - /// let mut builder = Builder::new(); - /// - /// builder.target(Target::Stdout); - /// ``` - pub fn target(&mut self, target: fmt::Target) -> &mut Self { - self.writer.target(target); - self - } - - /// Sets whether or not styles will be written. - /// - /// This can be useful in environments that don't support control characters - /// for setting colors. - /// - /// # Examples - /// - /// Never attempt to write styles: - /// - /// ``` - /// use env_logger::{Builder, WriteStyle}; - /// - /// let mut builder = Builder::new(); - /// - /// builder.write_style(WriteStyle::Never); - /// ``` - pub fn write_style(&mut self, write_style: fmt::WriteStyle) -> &mut Self { - self.writer.write_style(write_style); - self - } - - /// Parses whether or not to write styles in the same form as the `RUST_LOG_STYLE` - /// environment variable. - /// - /// See the module documentation for more details. - pub fn parse_write_style(&mut self, write_style: &str) -> &mut Self { - self.writer.parse_write_style(write_style); - self - } - - /// Sets whether or not the logger will be used in unit tests. - /// - /// If `is_test` is `true` then the logger will allow the testing framework to - /// capture log records rather than printing them to the terminal directly. - pub fn is_test(&mut self, is_test: bool) -> &mut Self { - self.writer.is_test(is_test); - self - } - - /// Initializes the global logger with the built env logger. - /// - /// This should be called early in the execution of a Rust program. Any log - /// events that occur before initialization will be ignored. - /// - /// # Errors - /// - /// This function will fail if it is called more than once, or if another - /// library has already initialized a global logger. - pub fn try_init(&mut self) -> Result<(), SetLoggerError> { - let logger = self.build(); - - let max_level = logger.filter(); - let r = log::set_boxed_logger(Box::new(logger)); - - if r.is_ok() { - log::set_max_level(max_level); - } - - r - } - - /// Initializes the global logger with the built env logger. - /// - /// This should be called early in the execution of a Rust program. Any log - /// events that occur before initialization will be ignored. - /// - /// # Panics - /// - /// This function will panic if it is called more than once, or if another - /// library has already initialized a global logger. - pub fn init(&mut self) { - self.try_init() - .expect("Builder::init should not be called after logger initialized"); - } - - /// Build an env logger. - /// - /// The returned logger implements the `Log` trait and can be installed manually - /// or nested within another logger. - pub fn build(&mut self) -> Logger { - assert!(!self.built, "attempt to re-use consumed builder"); - self.built = true; - - Logger { - writer: self.writer.build(), - filter: self.filter.build(), - format: self.format.build(), - } - } -} - -impl Logger { - /// Creates the logger from the environment. - /// - /// The variables used to read configuration from can be tweaked before - /// passing in. - /// - /// # Examples - /// - /// Create a logger reading the log filter from an environment variable - /// called `MY_LOG`: - /// - /// ``` - /// use env_logger::Logger; - /// - /// let logger = Logger::from_env("MY_LOG"); - /// ``` - /// - /// Create a logger using the `MY_LOG` variable for filtering and - /// `MY_LOG_STYLE` for whether or not to write styles: - /// - /// ``` - /// use env_logger::{Logger, Env}; - /// - /// let env = Env::new().filter_or("MY_LOG", "info").write_style_or("MY_LOG_STYLE", "always"); - /// - /// let logger = Logger::from_env(env); - /// ``` - pub fn from_env<'a, E>(env: E) -> Self - where - E: Into>, - { - Builder::from_env(env).build() - } - - /// Creates the logger from the environment using default variable names. - /// - /// This method is a convenient way to call `from_env(Env::default())` without - /// having to use the `Env` type explicitly. The logger will use the - /// [default environment variables]. - /// - /// # Examples - /// - /// Creates a logger using the default environment variables: - /// - /// ``` - /// use env_logger::Logger; - /// - /// let logger = Logger::from_default_env(); - /// ``` - /// - /// [default environment variables]: struct.Env.html#default-environment-variables - pub fn from_default_env() -> Self { - Builder::from_default_env().build() - } - - /// Returns the maximum `LevelFilter` that this env logger instance is - /// configured to output. - pub fn filter(&self) -> LevelFilter { - self.filter.filter() - } - - /// Checks if this record matches the configured filter. - pub fn matches(&self, record: &Record) -> bool { - self.filter.matches(record) - } -} - -impl Log for Logger { - fn enabled(&self, metadata: &Metadata) -> bool { - self.filter.enabled(metadata) - } - - fn log(&self, record: &Record) { - if self.matches(record) { - // Log records are written to a thread-local buffer before being printed - // to the terminal. We clear these buffers afterwards, but they aren't shrunk - // so will always at least have capacity for the largest log record formatted - // on that thread. - // - // If multiple `Logger`s are used by the same threads then the thread-local - // formatter might have different color support. If this is the case the - // formatter and its buffer are discarded and recreated. - - thread_local! { - static FORMATTER: RefCell> = RefCell::new(None); - } - - let print = |formatter: &mut Formatter, record: &Record| { - let _ = - (self.format)(formatter, record).and_then(|_| formatter.print(&self.writer)); - - // Always clear the buffer afterwards - formatter.clear(); - }; - - let printed = FORMATTER - .try_with(|tl_buf| { - match tl_buf.try_borrow_mut() { - // There are no active borrows of the buffer - Ok(mut tl_buf) => match *tl_buf { - // We have a previously set formatter - Some(ref mut formatter) => { - // Check the buffer style. If it's different from the logger's - // style then drop the buffer and recreate it. - if formatter.write_style() != self.writer.write_style() { - *formatter = Formatter::new(&self.writer); - } - - print(formatter, record); - } - // We don't have a previously set formatter - None => { - let mut formatter = Formatter::new(&self.writer); - print(&mut formatter, record); - - *tl_buf = Some(formatter); - } - }, - // There's already an active borrow of the buffer (due to re-entrancy) - Err(_) => { - print(&mut Formatter::new(&self.writer), record); - } - } - }) - .is_ok(); - - if !printed { - // The thread-local storage was not available (because its - // destructor has already run). Create a new single-use - // Formatter on the stack for this call. - print(&mut Formatter::new(&self.writer), record); - } - } - } - - fn flush(&self) {} -} - -impl<'a> Env<'a> { - /// Get a default set of environment variables. - pub fn new() -> Self { - Self::default() - } - - /// Specify an environment variable to read the filter from. - pub fn filter(mut self, filter_env: E) -> Self - where - E: Into>, - { - self.filter = Var::new(filter_env); - - self - } - - /// Specify an environment variable to read the filter from. - /// - /// If the variable is not set, the default value will be used. - pub fn filter_or(mut self, filter_env: E, default: V) -> Self - where - E: Into>, - V: Into>, - { - self.filter = Var::new_with_default(filter_env, default); - - self - } - - /// Use the default environment variable to read the filter from. - /// - /// If the variable is not set, the default value will be used. - pub fn default_filter_or(mut self, default: V) -> Self - where - V: Into>, - { - self.filter = Var::new_with_default(DEFAULT_FILTER_ENV, default); - - self - } - - fn get_filter(&self) -> Option { - self.filter.get() - } - - /// Specify an environment variable to read the style from. - pub fn write_style(mut self, write_style_env: E) -> Self - where - E: Into>, - { - self.write_style = Var::new(write_style_env); - - self - } - - /// Specify an environment variable to read the style from. - /// - /// If the variable is not set, the default value will be used. - pub fn write_style_or(mut self, write_style_env: E, default: V) -> Self - where - E: Into>, - V: Into>, - { - self.write_style = Var::new_with_default(write_style_env, default); - - self - } - - /// Use the default environment variable to read the style from. - /// - /// If the variable is not set, the default value will be used. - pub fn default_write_style_or(mut self, default: V) -> Self - where - V: Into>, - { - self.write_style = Var::new_with_default(DEFAULT_WRITE_STYLE_ENV, default); - - self - } - - fn get_write_style(&self) -> Option { - self.write_style.get() - } -} - -impl<'a> Var<'a> { - fn new(name: E) -> Self - where - E: Into>, - { - Var { - name: name.into(), - default: None, - } - } - - fn new_with_default(name: E, default: V) -> Self - where - E: Into>, - V: Into>, - { - Var { - name: name.into(), - default: Some(default.into()), - } - } - - fn get(&self) -> Option { - env::var(&*self.name) - .ok() - .or_else(|| self.default.to_owned().map(|v| v.into_owned())) - } -} - -impl<'a, T> From for Env<'a> -where - T: Into>, -{ - fn from(filter_env: T) -> Self { - Env::default().filter(filter_env.into()) - } -} - -impl<'a> Default for Env<'a> { - fn default() -> Self { - Env { - filter: Var::new(DEFAULT_FILTER_ENV), - write_style: Var::new(DEFAULT_WRITE_STYLE_ENV), - } - } -} - -mod std_fmt_impls { - use super::*; - use std::fmt; - - impl fmt::Debug for Logger { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Logger") - .field("filter", &self.filter) - .finish() - } - } - - impl fmt::Debug for Builder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.built { - f.debug_struct("Logger").field("built", &true).finish() - } else { - f.debug_struct("Logger") - .field("filter", &self.filter) - .field("writer", &self.writer) - .finish() - } - } - } -} - -/// Attempts to initialize the global logger with an env logger. -/// -/// This should be called early in the execution of a Rust program. Any log -/// events that occur before initialization will be ignored. -/// -/// # Errors -/// -/// This function will fail if it is called more than once, or if another -/// library has already initialized a global logger. -pub fn try_init() -> Result<(), SetLoggerError> { - try_init_from_env(Env::default()) -} - -/// Initializes the global logger with an env logger. -/// -/// This should be called early in the execution of a Rust program. Any log -/// events that occur before initialization will be ignored. -/// -/// # Panics -/// -/// This function will panic if it is called more than once, or if another -/// library has already initialized a global logger. -pub fn init() { - try_init().expect("env_logger::init should not be called after logger initialized"); -} - -/// Attempts to initialize the global logger with an env logger from the given -/// environment variables. -/// -/// This should be called early in the execution of a Rust program. Any log -/// events that occur before initialization will be ignored. -/// -/// # Examples -/// -/// Initialise a logger using the `MY_LOG` environment variable for filters -/// and `MY_LOG_STYLE` for writing colors: -/// -/// ``` -/// use env_logger::{Builder, Env}; -/// -/// # fn run() -> Result<(), Box> { -/// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); -/// -/// env_logger::try_init_from_env(env)?; -/// -/// Ok(()) -/// # } -/// # run().unwrap(); -/// ``` -/// -/// # Errors -/// -/// This function will fail if it is called more than once, or if another -/// library has already initialized a global logger. -pub fn try_init_from_env<'a, E>(env: E) -> Result<(), SetLoggerError> -where - E: Into>, -{ - let mut builder = Builder::from_env(env); - - builder.try_init() -} - -/// Initializes the global logger with an env logger from the given environment -/// variables. -/// -/// This should be called early in the execution of a Rust program. Any log -/// events that occur before initialization will be ignored. -/// -/// # Examples -/// -/// Initialise a logger using the `MY_LOG` environment variable for filters -/// and `MY_LOG_STYLE` for writing colors: -/// -/// ``` -/// use env_logger::{Builder, Env}; -/// -/// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); -/// -/// env_logger::init_from_env(env); -/// ``` -/// -/// # Panics -/// -/// This function will panic if it is called more than once, or if another -/// library has already initialized a global logger. -pub fn init_from_env<'a, E>(env: E) -where - E: Into>, -{ - try_init_from_env(env) - .expect("env_logger::init_from_env should not be called after logger initialized"); -} - -/// Create a new builder with the default environment variables. -/// -/// The builder can be configured before being initialized. -/// This is a convenient way of calling [`Builder::from_default_env`]. -/// -/// [`Builder::from_default_env`]: struct.Builder.html#method.from_default_env -pub fn builder() -> Builder { - Builder::from_default_env() -} - -/// Create a builder from the given environment variables. -/// -/// The builder can be configured before being initialized. -#[deprecated( - since = "0.8.0", - note = "Prefer `env_logger::Builder::from_env()` instead." -)] -pub fn from_env<'a, E>(env: E) -> Builder -where - E: Into>, -{ - Builder::from_env(env) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn env_get_filter_reads_from_var_if_set() { - env::set_var("env_get_filter_reads_from_var_if_set", "from var"); - - let env = Env::new().filter_or("env_get_filter_reads_from_var_if_set", "from default"); - - assert_eq!(Some("from var".to_owned()), env.get_filter()); - } - - #[test] - fn env_get_filter_reads_from_default_if_var_not_set() { - env::remove_var("env_get_filter_reads_from_default_if_var_not_set"); - - let env = Env::new().filter_or( - "env_get_filter_reads_from_default_if_var_not_set", - "from default", - ); - - assert_eq!(Some("from default".to_owned()), env.get_filter()); - } - - #[test] - fn env_get_write_style_reads_from_var_if_set() { - env::set_var("env_get_write_style_reads_from_var_if_set", "from var"); - - let env = - Env::new().write_style_or("env_get_write_style_reads_from_var_if_set", "from default"); - - assert_eq!(Some("from var".to_owned()), env.get_write_style()); - } - - #[test] - fn env_get_write_style_reads_from_default_if_var_not_set() { - env::remove_var("env_get_write_style_reads_from_default_if_var_not_set"); - - let env = Env::new().write_style_or( - "env_get_write_style_reads_from_default_if_var_not_set", - "from default", - ); - - assert_eq!(Some("from default".to_owned()), env.get_write_style()); - } - - #[test] - fn builder_parse_env_overrides_existing_filters() { - env::set_var( - "builder_parse_default_env_overrides_existing_filters", - "debug", - ); - let env = Env::new().filter("builder_parse_default_env_overrides_existing_filters"); - - let mut builder = Builder::new(); - builder.filter_level(LevelFilter::Trace); - // Overrides global level to debug - builder.parse_env(env); - - assert_eq!(builder.filter.build().filter(), LevelFilter::Debug); - } -} +pub use self::logger::*; diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 00000000..6c8a00d4 --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,1016 @@ +use std::{borrow::Cow, cell::RefCell, env, io}; + +use log::{LevelFilter, Log, Metadata, Record, SetLoggerError}; + +use crate::filter; +use crate::filter::Filter; +use crate::fmt; +use crate::fmt::writer::{self, Writer}; +use crate::fmt::{FormatFn, Formatter}; + +/// The default name for the environment variable to read filters from. +pub const DEFAULT_FILTER_ENV: &str = "RUST_LOG"; + +/// The default name for the environment variable to read style preferences from. +pub const DEFAULT_WRITE_STYLE_ENV: &str = "RUST_LOG_STYLE"; + +/// `Builder` acts as builder for initializing a `Logger`. +/// +/// It can be used to customize the log format, change the environment variable used +/// to provide the logging directives and also set the default log level filter. +/// +/// # Examples +/// +/// ``` +/// # use std::io::Write; +/// use env_logger::Builder; +/// use log::{LevelFilter, error, info}; +/// +/// let mut builder = Builder::from_default_env(); +/// +/// builder +/// .format(|buf, record| writeln!(buf, "{} - {}", record.level(), record.args())) +/// .filter(None, LevelFilter::Info) +/// .init(); +/// +/// error!("error message"); +/// info!("info message"); +/// ``` +#[derive(Default)] +pub struct Builder { + filter: filter::Builder, + writer: writer::Builder, + format: fmt::Builder, + built: bool, +} + +impl Builder { + /// Initializes the log builder with defaults. + /// + /// **NOTE:** This method won't read from any environment variables. + /// Use the [`filter`] and [`write_style`] methods to configure the builder + /// or use [`from_env`] or [`from_default_env`] instead. + /// + /// # Examples + /// + /// Create a new builder and configure filters and style: + /// + /// ``` + /// use log::LevelFilter; + /// use env_logger::{Builder, WriteStyle}; + /// + /// let mut builder = Builder::new(); + /// + /// builder + /// .filter(None, LevelFilter::Info) + /// .write_style(WriteStyle::Always) + /// .init(); + /// ``` + /// + /// [`filter`]: #method.filter + /// [`write_style`]: #method.write_style + /// [`from_env`]: #method.from_env + /// [`from_default_env`]: #method.from_default_env + pub fn new() -> Builder { + Default::default() + } + + /// Initializes the log builder from the environment. + /// + /// The variables used to read configuration from can be tweaked before + /// passing in. + /// + /// # Examples + /// + /// Initialise a logger reading the log filter from an environment variable + /// called `MY_LOG`: + /// + /// ``` + /// use env_logger::Builder; + /// + /// let mut builder = Builder::from_env("MY_LOG"); + /// builder.init(); + /// ``` + /// + /// Initialise a logger using the `MY_LOG` variable for filtering and + /// `MY_LOG_STYLE` for whether or not to write styles: + /// + /// ``` + /// use env_logger::{Builder, Env}; + /// + /// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); + /// + /// let mut builder = Builder::from_env(env); + /// builder.init(); + /// ``` + pub fn from_env<'a, E>(env: E) -> Self + where + E: Into>, + { + let mut builder = Builder::new(); + builder.parse_env(env); + builder + } + + /// Applies the configuration from the environment. + /// + /// This function allows a builder to be configured with default parameters, + /// to be then overridden by the environment. + /// + /// # Examples + /// + /// Initialise a logger with filter level `Off`, then override the log + /// filter from an environment variable called `MY_LOG`: + /// + /// ``` + /// use log::LevelFilter; + /// use env_logger::Builder; + /// + /// let mut builder = Builder::new(); + /// + /// builder.filter_level(LevelFilter::Off); + /// builder.parse_env("MY_LOG"); + /// builder.init(); + /// ``` + /// + /// Initialise a logger with filter level `Off`, then use the `MY_LOG` + /// variable to override filtering and `MY_LOG_STYLE` to override whether + /// or not to write styles: + /// + /// ``` + /// use log::LevelFilter; + /// use env_logger::{Builder, Env}; + /// + /// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); + /// + /// let mut builder = Builder::new(); + /// builder.filter_level(LevelFilter::Off); + /// builder.parse_env(env); + /// builder.init(); + /// ``` + pub fn parse_env<'a, E>(&mut self, env: E) -> &mut Self + where + E: Into>, + { + let env = env.into(); + + if let Some(s) = env.get_filter() { + self.parse_filters(&s); + } + + if let Some(s) = env.get_write_style() { + self.parse_write_style(&s); + } + + self + } + + /// Initializes the log builder from the environment using default variable names. + /// + /// This method is a convenient way to call `from_env(Env::default())` without + /// having to use the `Env` type explicitly. The builder will use the + /// [default environment variables]. + /// + /// # Examples + /// + /// Initialise a logger using the default environment variables: + /// + /// ``` + /// use env_logger::Builder; + /// + /// let mut builder = Builder::from_default_env(); + /// builder.init(); + /// ``` + /// + /// [default environment variables]: struct.Env.html#default-environment-variables + pub fn from_default_env() -> Self { + Self::from_env(Env::default()) + } + + /// Applies the configuration from the environment using default variable names. + /// + /// This method is a convenient way to call `parse_env(Env::default())` without + /// having to use the `Env` type explicitly. The builder will use the + /// [default environment variables]. + /// + /// # Examples + /// + /// Initialise a logger with filter level `Off`, then configure it using the + /// default environment variables: + /// + /// ``` + /// use log::LevelFilter; + /// use env_logger::Builder; + /// + /// let mut builder = Builder::new(); + /// builder.filter_level(LevelFilter::Off); + /// builder.parse_default_env(); + /// builder.init(); + /// ``` + /// + /// [default environment variables]: struct.Env.html#default-environment-variables + pub fn parse_default_env(&mut self) -> &mut Self { + self.parse_env(Env::default()) + } + + /// Sets the format function for formatting the log output. + /// + /// This function is called on each record logged and should format the + /// log record and output it to the given [`Formatter`]. + /// + /// The format function is expected to output the string directly to the + /// `Formatter` so that implementations can use the [`std::fmt`] macros + /// to format and output without intermediate heap allocations. The default + /// `env_logger` formatter takes advantage of this. + /// + /// # Examples + /// + /// Use a custom format to write only the log message: + /// + /// ``` + /// use std::io::Write; + /// use env_logger::Builder; + /// + /// let mut builder = Builder::new(); + /// + /// builder.format(|buf, record| writeln!(buf, "{}", record.args())); + /// ``` + /// + /// [`Formatter`]: fmt/struct.Formatter.html + /// [`String`]: https://doc.rust-lang.org/stable/std/string/struct.String.html + /// [`std::fmt`]: https://doc.rust-lang.org/std/fmt/index.html + pub fn format(&mut self, format: F) -> &mut Self + where + F: Fn(&mut Formatter, &Record) -> io::Result<()> + Sync + Send, + { + self.format.custom_format = Some(Box::new(format)); + self + } + + /// Use the default format. + /// + /// This method will clear any custom format set on the builder. + pub fn default_format(&mut self) -> &mut Self { + self.format = Default::default(); + self + } + + /// Whether or not to write the level in the default format. + pub fn format_level(&mut self, write: bool) -> &mut Self { + self.format.format_level = write; + self + } + + /// Whether or not to write the module path in the default format. + pub fn format_module_path(&mut self, write: bool) -> &mut Self { + self.format.format_module_path = write; + self + } + + /// Whether or not to write the target in the default format. + pub fn format_target(&mut self, write: bool) -> &mut Self { + self.format.format_target = write; + self + } + + /// Configures the amount of spaces to use to indent multiline log records. + /// A value of `None` disables any kind of indentation. + pub fn format_indent(&mut self, indent: Option) -> &mut Self { + self.format.format_indent = indent; + self + } + + /// Configures if timestamp should be included and in what precision. + pub fn format_timestamp(&mut self, timestamp: Option) -> &mut Self { + self.format.format_timestamp = timestamp; + self + } + + /// Configures the timestamp to use second precision. + pub fn format_timestamp_secs(&mut self) -> &mut Self { + self.format_timestamp(Some(fmt::TimestampPrecision::Seconds)) + } + + /// Configures the timestamp to use millisecond precision. + pub fn format_timestamp_millis(&mut self) -> &mut Self { + self.format_timestamp(Some(fmt::TimestampPrecision::Millis)) + } + + /// Configures the timestamp to use microsecond precision. + pub fn format_timestamp_micros(&mut self) -> &mut Self { + self.format_timestamp(Some(fmt::TimestampPrecision::Micros)) + } + + /// Configures the timestamp to use nanosecond precision. + pub fn format_timestamp_nanos(&mut self) -> &mut Self { + self.format_timestamp(Some(fmt::TimestampPrecision::Nanos)) + } + + /// Configures the end of line suffix. + pub fn format_suffix(&mut self, suffix: &'static str) -> &mut Self { + self.format.format_suffix = suffix; + self + } + + /// Adds a directive to the filter for a specific module. + /// + /// # Examples + /// + /// Only include messages for info and above for logs in `path::to::module`: + /// + /// ``` + /// use env_logger::Builder; + /// use log::LevelFilter; + /// + /// let mut builder = Builder::new(); + /// + /// builder.filter_module("path::to::module", LevelFilter::Info); + /// ``` + pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self { + self.filter.filter_module(module, level); + self + } + + /// Adds a directive to the filter for all modules. + /// + /// # Examples + /// + /// Only include messages for info and above for logs globally: + /// + /// ``` + /// use env_logger::Builder; + /// use log::LevelFilter; + /// + /// let mut builder = Builder::new(); + /// + /// builder.filter_level(LevelFilter::Info); + /// ``` + pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self { + self.filter.filter_level(level); + self + } + + /// Adds filters to the logger. + /// + /// The given module (if any) will log at most the specified level provided. + /// If no module is provided then the filter will apply to all log messages. + /// + /// # Examples + /// + /// Only include messages for info and above for logs in `path::to::module`: + /// + /// ``` + /// use env_logger::Builder; + /// use log::LevelFilter; + /// + /// let mut builder = Builder::new(); + /// + /// builder.filter(Some("path::to::module"), LevelFilter::Info); + /// ``` + pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self { + self.filter.filter(module, level); + self + } + + /// Parses the directives string in the same form as the `RUST_LOG` + /// environment variable. + /// + /// See the module documentation for more details. + pub fn parse_filters(&mut self, filters: &str) -> &mut Self { + self.filter.parse(filters); + self + } + + /// Sets the target for the log output. + /// + /// Env logger can log to either stdout, stderr or a custom pipe. The default is stderr. + /// + /// The custom pipe can be used to send the log messages to a custom sink (for example a file). + /// Do note that direct writes to a file can become a bottleneck due to IO operation times. + /// + /// # Examples + /// + /// Write log message to `stdout`: + /// + /// ``` + /// use env_logger::{Builder, Target}; + /// + /// let mut builder = Builder::new(); + /// + /// builder.target(Target::Stdout); + /// ``` + pub fn target(&mut self, target: fmt::Target) -> &mut Self { + self.writer.target(target); + self + } + + /// Sets whether or not styles will be written. + /// + /// This can be useful in environments that don't support control characters + /// for setting colors. + /// + /// # Examples + /// + /// Never attempt to write styles: + /// + /// ``` + /// use env_logger::{Builder, WriteStyle}; + /// + /// let mut builder = Builder::new(); + /// + /// builder.write_style(WriteStyle::Never); + /// ``` + pub fn write_style(&mut self, write_style: fmt::WriteStyle) -> &mut Self { + self.writer.write_style(write_style); + self + } + + /// Parses whether or not to write styles in the same form as the `RUST_LOG_STYLE` + /// environment variable. + /// + /// See the module documentation for more details. + pub fn parse_write_style(&mut self, write_style: &str) -> &mut Self { + self.writer.parse_write_style(write_style); + self + } + + /// Sets whether or not the logger will be used in unit tests. + /// + /// If `is_test` is `true` then the logger will allow the testing framework to + /// capture log records rather than printing them to the terminal directly. + pub fn is_test(&mut self, is_test: bool) -> &mut Self { + self.writer.is_test(is_test); + self + } + + /// Initializes the global logger with the built env logger. + /// + /// This should be called early in the execution of a Rust program. Any log + /// events that occur before initialization will be ignored. + /// + /// # Errors + /// + /// This function will fail if it is called more than once, or if another + /// library has already initialized a global logger. + pub fn try_init(&mut self) -> Result<(), SetLoggerError> { + let logger = self.build(); + + let max_level = logger.filter(); + let r = log::set_boxed_logger(Box::new(logger)); + + if r.is_ok() { + log::set_max_level(max_level); + } + + r + } + + /// Initializes the global logger with the built env logger. + /// + /// This should be called early in the execution of a Rust program. Any log + /// events that occur before initialization will be ignored. + /// + /// # Panics + /// + /// This function will panic if it is called more than once, or if another + /// library has already initialized a global logger. + pub fn init(&mut self) { + self.try_init() + .expect("Builder::init should not be called after logger initialized"); + } + + /// Build an env logger. + /// + /// The returned logger implements the `Log` trait and can be installed manually + /// or nested within another logger. + pub fn build(&mut self) -> Logger { + assert!(!self.built, "attempt to re-use consumed builder"); + self.built = true; + + Logger { + writer: self.writer.build(), + filter: self.filter.build(), + format: self.format.build(), + } + } +} + +impl std::fmt::Debug for Builder { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if self.built { + f.debug_struct("Logger").field("built", &true).finish() + } else { + f.debug_struct("Logger") + .field("filter", &self.filter) + .field("writer", &self.writer) + .finish() + } + } +} + +/// The env logger. +/// +/// This struct implements the `Log` trait from the [`log` crate][log-crate-url], +/// which allows it to act as a logger. +/// +/// The [`init()`], [`try_init()`], [`Builder::init()`] and [`Builder::try_init()`] +/// methods will each construct a `Logger` and immediately initialize it as the +/// default global logger. +/// +/// If you'd instead need access to the constructed `Logger`, you can use +/// the associated [`Builder`] and install it with the +/// [`log` crate][log-crate-url] directly. +/// +/// [log-crate-url]: https://docs.rs/log +/// [`init()`]: fn.init.html +/// [`try_init()`]: fn.try_init.html +/// [`Builder::init()`]: struct.Builder.html#method.init +/// [`Builder::try_init()`]: struct.Builder.html#method.try_init +/// [`Builder`]: struct.Builder.html +pub struct Logger { + writer: Writer, + filter: Filter, + format: FormatFn, +} + +impl Logger { + /// Creates the logger from the environment. + /// + /// The variables used to read configuration from can be tweaked before + /// passing in. + /// + /// # Examples + /// + /// Create a logger reading the log filter from an environment variable + /// called `MY_LOG`: + /// + /// ``` + /// use env_logger::Logger; + /// + /// let logger = Logger::from_env("MY_LOG"); + /// ``` + /// + /// Create a logger using the `MY_LOG` variable for filtering and + /// `MY_LOG_STYLE` for whether or not to write styles: + /// + /// ``` + /// use env_logger::{Logger, Env}; + /// + /// let env = Env::new().filter_or("MY_LOG", "info").write_style_or("MY_LOG_STYLE", "always"); + /// + /// let logger = Logger::from_env(env); + /// ``` + pub fn from_env<'a, E>(env: E) -> Self + where + E: Into>, + { + Builder::from_env(env).build() + } + + /// Creates the logger from the environment using default variable names. + /// + /// This method is a convenient way to call `from_env(Env::default())` without + /// having to use the `Env` type explicitly. The logger will use the + /// [default environment variables]. + /// + /// # Examples + /// + /// Creates a logger using the default environment variables: + /// + /// ``` + /// use env_logger::Logger; + /// + /// let logger = Logger::from_default_env(); + /// ``` + /// + /// [default environment variables]: struct.Env.html#default-environment-variables + pub fn from_default_env() -> Self { + Builder::from_default_env().build() + } + + /// Returns the maximum `LevelFilter` that this env logger instance is + /// configured to output. + pub fn filter(&self) -> LevelFilter { + self.filter.filter() + } + + /// Checks if this record matches the configured filter. + pub fn matches(&self, record: &Record) -> bool { + self.filter.matches(record) + } +} + +impl Log for Logger { + fn enabled(&self, metadata: &Metadata) -> bool { + self.filter.enabled(metadata) + } + + fn log(&self, record: &Record) { + if self.matches(record) { + // Log records are written to a thread-local buffer before being printed + // to the terminal. We clear these buffers afterwards, but they aren't shrunk + // so will always at least have capacity for the largest log record formatted + // on that thread. + // + // If multiple `Logger`s are used by the same threads then the thread-local + // formatter might have different color support. If this is the case the + // formatter and its buffer are discarded and recreated. + + thread_local! { + static FORMATTER: RefCell> = RefCell::new(None); + } + + let print = |formatter: &mut Formatter, record: &Record| { + let _ = + (self.format)(formatter, record).and_then(|_| formatter.print(&self.writer)); + + // Always clear the buffer afterwards + formatter.clear(); + }; + + let printed = FORMATTER + .try_with(|tl_buf| { + match tl_buf.try_borrow_mut() { + // There are no active borrows of the buffer + Ok(mut tl_buf) => match *tl_buf { + // We have a previously set formatter + Some(ref mut formatter) => { + // Check the buffer style. If it's different from the logger's + // style then drop the buffer and recreate it. + if formatter.write_style() != self.writer.write_style() { + *formatter = Formatter::new(&self.writer); + } + + print(formatter, record); + } + // We don't have a previously set formatter + None => { + let mut formatter = Formatter::new(&self.writer); + print(&mut formatter, record); + + *tl_buf = Some(formatter); + } + }, + // There's already an active borrow of the buffer (due to re-entrancy) + Err(_) => { + print(&mut Formatter::new(&self.writer), record); + } + } + }) + .is_ok(); + + if !printed { + // The thread-local storage was not available (because its + // destructor has already run). Create a new single-use + // Formatter on the stack for this call. + print(&mut Formatter::new(&self.writer), record); + } + } + } + + fn flush(&self) {} +} + +impl std::fmt::Debug for Logger { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct("Logger") + .field("filter", &self.filter) + .finish() + } +} + +/// Set of environment variables to configure from. +/// +/// # Default environment variables +/// +/// By default, the `Env` will read the following environment variables: +/// +/// - `RUST_LOG`: the level filter +/// - `RUST_LOG_STYLE`: whether or not to print styles with records. +/// +/// These sources can be configured using the builder methods on `Env`. +#[derive(Debug)] +pub struct Env<'a> { + filter: Var<'a>, + write_style: Var<'a>, +} + +impl<'a> Env<'a> { + /// Get a default set of environment variables. + pub fn new() -> Self { + Self::default() + } + + /// Specify an environment variable to read the filter from. + pub fn filter(mut self, filter_env: E) -> Self + where + E: Into>, + { + self.filter = Var::new(filter_env); + + self + } + + /// Specify an environment variable to read the filter from. + /// + /// If the variable is not set, the default value will be used. + pub fn filter_or(mut self, filter_env: E, default: V) -> Self + where + E: Into>, + V: Into>, + { + self.filter = Var::new_with_default(filter_env, default); + + self + } + + /// Use the default environment variable to read the filter from. + /// + /// If the variable is not set, the default value will be used. + pub fn default_filter_or(mut self, default: V) -> Self + where + V: Into>, + { + self.filter = Var::new_with_default(DEFAULT_FILTER_ENV, default); + + self + } + + fn get_filter(&self) -> Option { + self.filter.get() + } + + /// Specify an environment variable to read the style from. + pub fn write_style(mut self, write_style_env: E) -> Self + where + E: Into>, + { + self.write_style = Var::new(write_style_env); + + self + } + + /// Specify an environment variable to read the style from. + /// + /// If the variable is not set, the default value will be used. + pub fn write_style_or(mut self, write_style_env: E, default: V) -> Self + where + E: Into>, + V: Into>, + { + self.write_style = Var::new_with_default(write_style_env, default); + + self + } + + /// Use the default environment variable to read the style from. + /// + /// If the variable is not set, the default value will be used. + pub fn default_write_style_or(mut self, default: V) -> Self + where + V: Into>, + { + self.write_style = Var::new_with_default(DEFAULT_WRITE_STYLE_ENV, default); + + self + } + + fn get_write_style(&self) -> Option { + self.write_style.get() + } +} + +impl<'a, T> From for Env<'a> +where + T: Into>, +{ + fn from(filter_env: T) -> Self { + Env::default().filter(filter_env.into()) + } +} + +impl<'a> Default for Env<'a> { + fn default() -> Self { + Env { + filter: Var::new(DEFAULT_FILTER_ENV), + write_style: Var::new(DEFAULT_WRITE_STYLE_ENV), + } + } +} + +#[derive(Debug)] +struct Var<'a> { + name: Cow<'a, str>, + default: Option>, +} + +impl<'a> Var<'a> { + fn new(name: E) -> Self + where + E: Into>, + { + Var { + name: name.into(), + default: None, + } + } + + fn new_with_default(name: E, default: V) -> Self + where + E: Into>, + V: Into>, + { + Var { + name: name.into(), + default: Some(default.into()), + } + } + + fn get(&self) -> Option { + env::var(&*self.name) + .ok() + .or_else(|| self.default.to_owned().map(|v| v.into_owned())) + } +} + +/// Attempts to initialize the global logger with an env logger. +/// +/// This should be called early in the execution of a Rust program. Any log +/// events that occur before initialization will be ignored. +/// +/// # Errors +/// +/// This function will fail if it is called more than once, or if another +/// library has already initialized a global logger. +pub fn try_init() -> Result<(), SetLoggerError> { + try_init_from_env(Env::default()) +} + +/// Initializes the global logger with an env logger. +/// +/// This should be called early in the execution of a Rust program. Any log +/// events that occur before initialization will be ignored. +/// +/// # Panics +/// +/// This function will panic if it is called more than once, or if another +/// library has already initialized a global logger. +pub fn init() { + try_init().expect("env_logger::init should not be called after logger initialized"); +} + +/// Attempts to initialize the global logger with an env logger from the given +/// environment variables. +/// +/// This should be called early in the execution of a Rust program. Any log +/// events that occur before initialization will be ignored. +/// +/// # Examples +/// +/// Initialise a logger using the `MY_LOG` environment variable for filters +/// and `MY_LOG_STYLE` for writing colors: +/// +/// ``` +/// use env_logger::{Builder, Env}; +/// +/// # fn run() -> Result<(), Box> { +/// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); +/// +/// env_logger::try_init_from_env(env)?; +/// +/// Ok(()) +/// # } +/// # run().unwrap(); +/// ``` +/// +/// # Errors +/// +/// This function will fail if it is called more than once, or if another +/// library has already initialized a global logger. +pub fn try_init_from_env<'a, E>(env: E) -> Result<(), SetLoggerError> +where + E: Into>, +{ + let mut builder = Builder::from_env(env); + + builder.try_init() +} + +/// Initializes the global logger with an env logger from the given environment +/// variables. +/// +/// This should be called early in the execution of a Rust program. Any log +/// events that occur before initialization will be ignored. +/// +/// # Examples +/// +/// Initialise a logger using the `MY_LOG` environment variable for filters +/// and `MY_LOG_STYLE` for writing colors: +/// +/// ``` +/// use env_logger::{Builder, Env}; +/// +/// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); +/// +/// env_logger::init_from_env(env); +/// ``` +/// +/// # Panics +/// +/// This function will panic if it is called more than once, or if another +/// library has already initialized a global logger. +pub fn init_from_env<'a, E>(env: E) +where + E: Into>, +{ + try_init_from_env(env) + .expect("env_logger::init_from_env should not be called after logger initialized"); +} + +/// Create a new builder with the default environment variables. +/// +/// The builder can be configured before being initialized. +/// This is a convenient way of calling [`Builder::from_default_env`]. +/// +/// [`Builder::from_default_env`]: struct.Builder.html#method.from_default_env +pub fn builder() -> Builder { + Builder::from_default_env() +} + +/// Create a builder from the given environment variables. +/// +/// The builder can be configured before being initialized. +#[deprecated( + since = "0.8.0", + note = "Prefer `env_logger::Builder::from_env()` instead." +)] +pub fn from_env<'a, E>(env: E) -> Builder +where + E: Into>, +{ + Builder::from_env(env) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn env_get_filter_reads_from_var_if_set() { + env::set_var("env_get_filter_reads_from_var_if_set", "from var"); + + let env = Env::new().filter_or("env_get_filter_reads_from_var_if_set", "from default"); + + assert_eq!(Some("from var".to_owned()), env.get_filter()); + } + + #[test] + fn env_get_filter_reads_from_default_if_var_not_set() { + env::remove_var("env_get_filter_reads_from_default_if_var_not_set"); + + let env = Env::new().filter_or( + "env_get_filter_reads_from_default_if_var_not_set", + "from default", + ); + + assert_eq!(Some("from default".to_owned()), env.get_filter()); + } + + #[test] + fn env_get_write_style_reads_from_var_if_set() { + env::set_var("env_get_write_style_reads_from_var_if_set", "from var"); + + let env = + Env::new().write_style_or("env_get_write_style_reads_from_var_if_set", "from default"); + + assert_eq!(Some("from var".to_owned()), env.get_write_style()); + } + + #[test] + fn env_get_write_style_reads_from_default_if_var_not_set() { + env::remove_var("env_get_write_style_reads_from_default_if_var_not_set"); + + let env = Env::new().write_style_or( + "env_get_write_style_reads_from_default_if_var_not_set", + "from default", + ); + + assert_eq!(Some("from default".to_owned()), env.get_write_style()); + } + + #[test] + fn builder_parse_env_overrides_existing_filters() { + env::set_var( + "builder_parse_default_env_overrides_existing_filters", + "debug", + ); + let env = Env::new().filter("builder_parse_default_env_overrides_existing_filters"); + + let mut builder = Builder::new(); + builder.filter_level(LevelFilter::Trace); + // Overrides global level to debug + builder.parse_env(env); + + assert_eq!(builder.filter.build().filter(), LevelFilter::Debug); + } +} From b7695406d749b25ebdf41b21e8df03b214a8332a Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 10 Nov 2023 09:31:21 -0600 Subject: [PATCH 03/53] style: Make clippy happy --- src/filter/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 1e647236..d4acf632 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -198,7 +198,7 @@ impl Builder { Filter { directives: mem::take(&mut directives), - filter: mem::replace(&mut self.filter, None), + filter: mem::take(&mut self.filter), } } } From 598c6244983fb392457f3fbec9badf25fab6d051 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 3 Dec 2023 09:19:35 +0000 Subject: [PATCH 04/53] chore(config): migrate config .github/renovate.json5 --- .github/renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 72d05795..3119c425 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -29,7 +29,7 @@ { commitMessageTopic: 'MSRV', matchManagers: [ - 'regex', + 'custom.regex', ], matchPackageNames: [ 'rust', From 88a9f483185454a3505ffc3b5b2e4a66ab4c778c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 02:20:07 +0000 Subject: [PATCH 05/53] chore(deps): update actions/setup-python action to v5 --- .github/workflows/pre-commit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 80447507..95514075 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -19,5 +19,5 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 - uses: pre-commit/action@v3.0.0 From 189e574cb342a15aba54ea4238ea1727a10e0f97 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 02:20:11 +0000 Subject: [PATCH 06/53] chore(deps): update github/codeql-action action to v3 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ecff712..de41ed3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -130,7 +130,7 @@ jobs: | sarif-fmt continue-on-error: true - name: Upload - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: clippy-results.sarif wait-for-processing: true From 7bc51b0a62ecb4e012481efea6af9038c8433dd1 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 8 Jan 2024 10:51:20 -0600 Subject: [PATCH 07/53] docs: Update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb73af76..437ec6af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [0.10.1] - 2023-11-10 +### Performance + +- Avoid hashing directives and accessing RNG on startup + +### Documentation + +- Tweak `RUST_LOG` documentation + ## [0.10.0] - 2022-11-24 MSRV changed to 1.60 to hide optional dependencies From a2d55f81bc99762c7797b95dcf97736713539a49 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 12:50:12 -0600 Subject: [PATCH 08/53] refactor(fmt): Move impls next to types --- src/fmt/mod.rs | 58 ++++++++++++ src/fmt/writer/mod.rs | 23 +++-- src/fmt/writer/termcolor/extern_impl.rs | 116 +++++------------------- src/fmt/writer/termcolor/shim_impl.rs | 4 +- 4 files changed, 102 insertions(+), 99 deletions(-) diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs index ded40278..bc5006cc 100644 --- a/src/fmt/mod.rs +++ b/src/fmt/mod.rs @@ -35,6 +35,8 @@ use std::io::prelude::*; use std::rc::Rc; use std::{fmt, io, mem}; +#[cfg(feature = "color")] +use log::Level; use log::Record; #[cfg(feature = "humantime")] @@ -122,6 +124,62 @@ 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. + /// + /// 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) + } +} + impl Write for Formatter { fn write(&mut self, buf: &[u8]) -> io::Result { self.buf.borrow_mut().write(buf) diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 7f4b6f94..bb57ec6b 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -101,6 +101,17 @@ impl Default for WriteStyle { } } +#[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, + } + } +} + /// A terminal target with color awareness. pub(crate) struct Writer { inner: BufferWriter, @@ -121,6 +132,12 @@ 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. @@ -210,12 +227,6 @@ impl Default for Builder { } } -impl fmt::Debug for Writer { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Writer").finish() - } -} - fn parse_write_style(spec: &str) -> WriteStyle { match spec { "auto" => WriteStyle::Auto, diff --git a/src/fmt/writer/termcolor/extern_impl.rs b/src/fmt/writer/termcolor/extern_impl.rs index 89c38223..5d7304fd 100644 --- a/src/fmt/writer/termcolor/extern_impl.rs +++ b/src/fmt/writer/termcolor/extern_impl.rs @@ -5,80 +5,19 @@ use std::io::{self, Write}; use std::rc::Rc; use std::sync::Mutex; -use log::Level; -use termcolor::{self, ColorChoice, ColorSpec, WriteColor}; +use termcolor::{self, ColorSpec, WriteColor}; -use crate::fmt::{Formatter, WritableTarget, WriteStyle}; +use crate::fmt::{WritableTarget, WriteStyle}; pub(in crate::fmt::writer) mod glob { pub use super::*; } -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: ColorSpec::new(), - } - } - - /// Get the default [`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(in crate::fmt::writer) struct BufferWriter { inner: termcolor::BufferWriter, uncolored_target: Option, } -pub(in crate::fmt) struct Buffer { - inner: termcolor::Buffer, - has_uncolored_target: bool, -} - impl BufferWriter { pub(in crate::fmt::writer) fn stderr(is_test: bool, write_style: WriteStyle) -> Self { BufferWriter { @@ -140,6 +79,11 @@ impl BufferWriter { } } +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() @@ -176,16 +120,6 @@ impl Buffer { } } -impl WriteStyle { - fn into_color_choice(self) -> ColorChoice { - match self { - WriteStyle::Always => ColorChoice::Always, - WriteStyle::Auto => ColorChoice::Auto, - WriteStyle::Never => ColorChoice::Never, - } - } -} - /// A set of styles to apply to the terminal output. /// /// Call [`Formatter::style`] to get a `Style` and use the builder methods to @@ -240,18 +174,8 @@ impl WriteStyle { /// [`value`]: #method.value #[derive(Clone)] pub struct Style { - buf: Rc>, - spec: ColorSpec, -} - -/// 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, + pub(in crate::fmt) buf: Rc>, + pub(in crate::fmt) spec: ColorSpec, } impl Style { @@ -426,6 +350,22 @@ impl Style { } } +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 @@ -445,12 +385,6 @@ impl<'a, T> StyledValue<'a, T> { } } -impl fmt::Debug for Style { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Style").field("spec", &self.spec).finish() - } -} - macro_rules! impl_styled_value_fmt { ($($fmt_trait:path),*) => { $( diff --git a/src/fmt/writer/termcolor/shim_impl.rs b/src/fmt/writer/termcolor/shim_impl.rs index 0705770c..84449092 100644 --- a/src/fmt/writer/termcolor/shim_impl.rs +++ b/src/fmt/writer/termcolor/shim_impl.rs @@ -8,8 +8,6 @@ pub(in crate::fmt::writer) struct BufferWriter { target: WritableTarget, } -pub(in crate::fmt) struct Buffer(Vec); - impl BufferWriter { pub(in crate::fmt::writer) fn stderr(_is_test: bool, _write_style: WriteStyle) -> Self { BufferWriter { @@ -51,6 +49,8 @@ impl BufferWriter { } } +pub(in crate::fmt) struct Buffer(Vec); + impl Buffer { pub(in crate::fmt) fn clear(&mut self) { self.0.clear(); From 62e7fc2a2fdb257a83862cbcb2e022862edcd157 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 13:15:48 -0600 Subject: [PATCH 09/53] refactor(fmt): Move Style closer to API representation --- src/fmt/mod.rs | 5 + src/fmt/style.rs | 351 +++++++++++++++++++++++ src/fmt/writer/mod.rs | 1 - src/fmt/writer/termcolor/extern_impl.rs | 357 +----------------------- src/fmt/writer/termcolor/shim_impl.rs | 2 - 5 files changed, 358 insertions(+), 358 deletions(-) create mode 100644 src/fmt/style.rs diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs index bc5006cc..30b1384e 100644 --- a/src/fmt/mod.rs +++ b/src/fmt/mod.rs @@ -43,6 +43,11 @@ use log::Record; mod humantime; pub(crate) mod writer; +#[cfg(feature = "color")] +mod style; +#[cfg(feature = "color")] +pub use style::{Color, Style, StyledValue}; + #[cfg(feature = "humantime")] pub use self::humantime::Timestamp; pub use self::writer::glob::*; diff --git a/src/fmt/style.rs b/src/fmt/style.rs new file mode 100644 index 00000000..dd4463ac --- /dev/null +++ b/src/fmt/style.rs @@ -0,0 +1,351 @@ +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/mod.rs b/src/fmt/writer/mod.rs index bb57ec6b..e1365a49 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -6,7 +6,6 @@ use self::termcolor::BufferWriter; use std::{fmt, io, mem, sync::Mutex}; pub(super) mod glob { - pub use super::termcolor::glob::*; pub use super::*; } diff --git a/src/fmt/writer/termcolor/extern_impl.rs b/src/fmt/writer/termcolor/extern_impl.rs index 5d7304fd..efcf0c3d 100644 --- a/src/fmt/writer/termcolor/extern_impl.rs +++ b/src/fmt/writer/termcolor/extern_impl.rs @@ -1,18 +1,10 @@ -use std::borrow::Cow; -use std::cell::RefCell; -use std::fmt; use std::io::{self, Write}; -use std::rc::Rc; use std::sync::Mutex; use termcolor::{self, ColorSpec, WriteColor}; use crate::fmt::{WritableTarget, WriteStyle}; -pub(in crate::fmt::writer) mod glob { - pub use super::*; -} - pub(in crate::fmt::writer) struct BufferWriter { inner: termcolor::BufferWriter, uncolored_target: Option, @@ -101,7 +93,7 @@ impl Buffer { self.inner.as_slice() } - fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + 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) @@ -110,7 +102,7 @@ impl Buffer { } } - fn reset(&mut self) -> io::Result<()> { + 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() @@ -119,348 +111,3 @@ impl 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: 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/termcolor/shim_impl.rs b/src/fmt/writer/termcolor/shim_impl.rs index 84449092..7e702627 100644 --- a/src/fmt/writer/termcolor/shim_impl.rs +++ b/src/fmt/writer/termcolor/shim_impl.rs @@ -2,8 +2,6 @@ use std::{io, sync::Mutex}; use crate::fmt::{WritableTarget, WriteStyle}; -pub(in crate::fmt::writer) mod glob {} - pub(in crate::fmt::writer) struct BufferWriter { target: WritableTarget, } From b92dd4511ee96ed610d55214063b613d83878d42 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 13:24:27 -0600 Subject: [PATCH 10/53] refactor(fmt): Rename termcolor mod to buffer --- src/fmt/writer/{termcolor => buffer}/extern_impl.rs | 0 src/fmt/writer/{termcolor => buffer}/mod.rs | 0 src/fmt/writer/{termcolor => buffer}/shim_impl.rs | 0 src/fmt/writer/mod.rs | 6 +++--- 4 files changed, 3 insertions(+), 3 deletions(-) rename src/fmt/writer/{termcolor => buffer}/extern_impl.rs (100%) rename src/fmt/writer/{termcolor => buffer}/mod.rs (100%) rename src/fmt/writer/{termcolor => buffer}/shim_impl.rs (100%) diff --git a/src/fmt/writer/termcolor/extern_impl.rs b/src/fmt/writer/buffer/extern_impl.rs similarity index 100% rename from src/fmt/writer/termcolor/extern_impl.rs rename to src/fmt/writer/buffer/extern_impl.rs diff --git a/src/fmt/writer/termcolor/mod.rs b/src/fmt/writer/buffer/mod.rs similarity index 100% rename from src/fmt/writer/termcolor/mod.rs rename to src/fmt/writer/buffer/mod.rs diff --git a/src/fmt/writer/termcolor/shim_impl.rs b/src/fmt/writer/buffer/shim_impl.rs similarity index 100% rename from src/fmt/writer/termcolor/shim_impl.rs rename to src/fmt/writer/buffer/shim_impl.rs diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index e1365a49..785c78c7 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -1,15 +1,15 @@ mod atty; -mod termcolor; +mod buffer; use self::atty::{is_stderr, is_stdout}; -use self::termcolor::BufferWriter; +use self::buffer::BufferWriter; use std::{fmt, io, mem, sync::Mutex}; pub(super) mod glob { pub use super::*; } -pub(super) use self::termcolor::Buffer; +pub(super) use self::buffer::Buffer; /// Log target, either `stdout`, `stderr` or a custom pipe. #[non_exhaustive] From aac6824ef072932963e24b1fd67735477fd7aeca Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 13:39:32 -0600 Subject: [PATCH 11/53] refactor(fmt): Rename buffer strategy mods --- src/fmt/writer/buffer/mod.rs | 13 ++++++++----- src/fmt/writer/buffer/{shim_impl.rs => plain.rs} | 0 .../writer/buffer/{extern_impl.rs => termcolor.rs} | 0 3 files changed, 8 insertions(+), 5 deletions(-) rename src/fmt/writer/buffer/{shim_impl.rs => plain.rs} (100%) rename src/fmt/writer/buffer/{extern_impl.rs => termcolor.rs} (100%) diff --git a/src/fmt/writer/buffer/mod.rs b/src/fmt/writer/buffer/mod.rs index 20f01979..d3b44de6 100644 --- a/src/fmt/writer/buffer/mod.rs +++ b/src/fmt/writer/buffer/mod.rs @@ -5,8 +5,11 @@ Its public API is available when the `termcolor` crate is available. The terminal printing is shimmed when the `termcolor` crate is not available. */ -#[cfg_attr(feature = "color", path = "extern_impl.rs")] -#[cfg_attr(not(feature = "color"), path = "shim_impl.rs")] -mod imp; - -pub(in crate::fmt) use self::imp::*; +#[cfg(feature = "color")] +mod termcolor; +#[cfg(feature = "color")] +pub(in crate::fmt) use termcolor::*; +#[cfg(not(feature = "color"))] +mod plain; +#[cfg(not(feature = "color"))] +pub(in crate::fmt) use plain::*; diff --git a/src/fmt/writer/buffer/shim_impl.rs b/src/fmt/writer/buffer/plain.rs similarity index 100% rename from src/fmt/writer/buffer/shim_impl.rs rename to src/fmt/writer/buffer/plain.rs diff --git a/src/fmt/writer/buffer/extern_impl.rs b/src/fmt/writer/buffer/termcolor.rs similarity index 100% rename from src/fmt/writer/buffer/extern_impl.rs rename to src/fmt/writer/buffer/termcolor.rs From f4a0c2c34d82b23664f1292944f1540b9c2ebb15 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 14:47:59 -0600 Subject: [PATCH 12/53] refactor(fmt): Decouple target from buffer --- src/fmt/writer/mod.rs | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 785c78c7..55384b9f 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -54,22 +54,6 @@ pub(super) enum WritableTarget { Pipe(Box>), } -impl From for WritableTarget { - fn from(target: Target) -> Self { - match target { - Target::Stdout => Self::Stdout, - Target::Stderr => Self::Stderr, - Target::Pipe(pipe) => Self::Pipe(Box::new(Mutex::new(pipe))), - } - } -} - -impl Default for WritableTarget { - fn default() -> Self { - Self::from(Target::default()) - } -} - impl fmt::Debug for WritableTarget { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( @@ -142,7 +126,7 @@ impl fmt::Debug for Writer { /// The target and style choice can be configured before building. #[derive(Debug)] pub(crate) struct Builder { - target: WritableTarget, + target: Target, write_style: WriteStyle, is_test: bool, built: bool, @@ -161,7 +145,7 @@ impl Builder { /// Set the target to write to. pub(crate) fn target(&mut self, target: Target) -> &mut Self { - self.target = target.into(); + self.target = target; self } @@ -195,9 +179,9 @@ impl Builder { let color_choice = match self.write_style { WriteStyle::Auto => { if match &self.target { - WritableTarget::Stderr => is_stderr(), - WritableTarget::Stdout => is_stdout(), - WritableTarget::Pipe(_) => false, + Target::Stderr => is_stderr(), + Target::Stdout => is_stdout(), + Target::Pipe(_) => false, } { WriteStyle::Auto } else { @@ -208,9 +192,9 @@ impl Builder { }; let writer = match mem::take(&mut self.target) { - WritableTarget::Stderr => BufferWriter::stderr(self.is_test, color_choice), - WritableTarget::Stdout => BufferWriter::stdout(self.is_test, color_choice), - WritableTarget::Pipe(pipe) => BufferWriter::pipe(color_choice, pipe), + Target::Stderr => BufferWriter::stderr(self.is_test, color_choice), + Target::Stdout => BufferWriter::stdout(self.is_test, color_choice), + Target::Pipe(pipe) => BufferWriter::pipe(color_choice, Box::new(Mutex::new(pipe))), }; Writer { From 6d55509b3eb9645945bffd555f418382a378751f Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 15:11:06 -0600 Subject: [PATCH 13/53] refactor(fmt): Track WriteStyle with the BufferWriter --- src/fmt/writer/buffer/plain.rs | 9 +++++---- src/fmt/writer/buffer/termcolor.rs | 14 ++++++++++---- src/fmt/writer/mod.rs | 15 ++++++++------- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/fmt/writer/buffer/plain.rs b/src/fmt/writer/buffer/plain.rs index 7e702627..b4356af1 100644 --- a/src/fmt/writer/buffer/plain.rs +++ b/src/fmt/writer/buffer/plain.rs @@ -19,15 +19,16 @@ impl BufferWriter { } } - pub(in crate::fmt::writer) fn pipe( - _write_style: WriteStyle, - pipe: Box>, - ) -> Self { + 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()) } diff --git a/src/fmt/writer/buffer/termcolor.rs b/src/fmt/writer/buffer/termcolor.rs index efcf0c3d..9d7374a7 100644 --- a/src/fmt/writer/buffer/termcolor.rs +++ b/src/fmt/writer/buffer/termcolor.rs @@ -8,6 +8,7 @@ use crate::fmt::{WritableTarget, WriteStyle}; pub(in crate::fmt::writer) struct BufferWriter { inner: termcolor::BufferWriter, uncolored_target: Option, + write_style: WriteStyle, } impl BufferWriter { @@ -19,6 +20,7 @@ impl BufferWriter { } else { None }, + write_style, } } @@ -30,20 +32,24 @@ impl BufferWriter { } else { None }, + write_style, } } - pub(in crate::fmt::writer) fn pipe( - write_style: WriteStyle, - pipe: Box>, - ) -> Self { + 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(), diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 55384b9f..d3e7345a 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -98,12 +98,11 @@ impl WriteStyle { /// A terminal target with color awareness. pub(crate) struct Writer { inner: BufferWriter, - write_style: WriteStyle, } impl Writer { pub fn write_style(&self) -> WriteStyle { - self.write_style + self.inner.write_style() } pub(super) fn buffer(&self) -> Buffer { @@ -190,17 +189,19 @@ impl Builder { } color_choice => color_choice, }; + let color_choice = if self.is_test { + 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::Pipe(pipe) => BufferWriter::pipe(color_choice, Box::new(Mutex::new(pipe))), + Target::Pipe(pipe) => BufferWriter::pipe(Box::new(Mutex::new(pipe))), }; - Writer { - inner: writer, - write_style: self.write_style, - } + Writer { inner: writer } } } From 87008fdf78b861202ca9da4869475649e72b8e82 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 16:19:58 -0600 Subject: [PATCH 14/53] fix(fmt): Don't panic on broken pipes without termcolor Fixes #221 --- src/fmt/writer/buffer/plain.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/fmt/writer/buffer/plain.rs b/src/fmt/writer/buffer/plain.rs index b4356af1..094f80b4 100644 --- a/src/fmt/writer/buffer/plain.rs +++ b/src/fmt/writer/buffer/plain.rs @@ -4,24 +4,28 @@ use crate::fmt::{WritableTarget, WriteStyle}; pub(in crate::fmt::writer) struct BufferWriter { target: WritableTarget, + is_test: bool, } impl BufferWriter { - pub(in crate::fmt::writer) fn stderr(_is_test: bool, _write_style: WriteStyle) -> Self { + pub(in crate::fmt::writer) fn stderr(is_test: bool, _write_style: WriteStyle) -> Self { BufferWriter { target: WritableTarget::Stderr, + is_test, } } - pub(in crate::fmt::writer) fn stdout(_is_test: bool, _write_style: WriteStyle) -> Self { + pub(in crate::fmt::writer) fn stdout(is_test: bool, _write_style: WriteStyle) -> Self { BufferWriter { target: WritableTarget::Stdout, + is_test, } } pub(in crate::fmt::writer) fn pipe(pipe: Box>) -> Self { BufferWriter { target: WritableTarget::Pipe(pipe), + is_test: false, } } @@ -34,14 +38,22 @@ impl BufferWriter { } pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { + use std::io::Write as _; + // This impl uses the `eprint` and `print` macros // instead of using the streams directly. // This is so their output can be captured by `cargo test`. - match &self.target { + match (&self.target, self.is_test) { // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty. - WritableTarget::Pipe(pipe) => pipe.lock().unwrap().write_all(&buf.0)?, - WritableTarget::Stdout => print!("{}", String::from_utf8_lossy(&buf.0)), - WritableTarget::Stderr => eprint!("{}", String::from_utf8_lossy(&buf.0)), + (WritableTarget::Pipe(pipe), _) => pipe.lock().unwrap().write_all(&buf.0)?, + (WritableTarget::Stdout, true) => print!("{}", String::from_utf8_lossy(&buf.0)), + (WritableTarget::Stdout, false) => { + write!(std::io::stdout(), "{}", String::from_utf8_lossy(&buf.0))? + } + (WritableTarget::Stderr, true) => eprint!("{}", String::from_utf8_lossy(&buf.0)), + (WritableTarget::Stderr, false) => { + write!(std::io::stderr(), "{}", String::from_utf8_lossy(&buf.0))? + } } Ok(()) From f5f3392886cb3f2a1a3180122666fa40a2150efe Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 16:31:19 -0600 Subject: [PATCH 15/53] refactor(fmt): Pull is_test into the target --- src/fmt/writer/buffer/plain.rs | 30 +++++++++++++++++------------- src/fmt/writer/buffer/termcolor.rs | 10 ++++++---- src/fmt/writer/mod.rs | 20 ++++++++++++++------ 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/fmt/writer/buffer/plain.rs b/src/fmt/writer/buffer/plain.rs index 094f80b4..0ebd6533 100644 --- a/src/fmt/writer/buffer/plain.rs +++ b/src/fmt/writer/buffer/plain.rs @@ -4,28 +4,32 @@ use crate::fmt::{WritableTarget, WriteStyle}; pub(in crate::fmt::writer) struct BufferWriter { target: WritableTarget, - is_test: bool, } impl BufferWriter { pub(in crate::fmt::writer) fn stderr(is_test: bool, _write_style: WriteStyle) -> Self { BufferWriter { - target: WritableTarget::Stderr, - is_test, + 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: WritableTarget::Stdout, - is_test, + target: if is_test { + WritableTarget::PrintStdout + } else { + WritableTarget::WriteStdout + }, } } pub(in crate::fmt::writer) fn pipe(pipe: Box>) -> Self { BufferWriter { target: WritableTarget::Pipe(pipe), - is_test: false, } } @@ -43,17 +47,17 @@ impl BufferWriter { // This impl uses the `eprint` and `print` macros // instead of using the streams directly. // This is so their output can be captured by `cargo test`. - match (&self.target, self.is_test) { - // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty. - (WritableTarget::Pipe(pipe), _) => pipe.lock().unwrap().write_all(&buf.0)?, - (WritableTarget::Stdout, true) => print!("{}", String::from_utf8_lossy(&buf.0)), - (WritableTarget::Stdout, false) => { + match &self.target { + WritableTarget::WriteStdout => { write!(std::io::stdout(), "{}", String::from_utf8_lossy(&buf.0))? } - (WritableTarget::Stderr, true) => eprint!("{}", String::from_utf8_lossy(&buf.0)), - (WritableTarget::Stderr, false) => { + WritableTarget::PrintStdout => print!("{}", String::from_utf8_lossy(&buf.0)), + WritableTarget::WriteStderr => { write!(std::io::stderr(), "{}", String::from_utf8_lossy(&buf.0))? } + WritableTarget::PrintStderr => eprint!("{}", String::from_utf8_lossy(&buf.0)), + // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty. + WritableTarget::Pipe(pipe) => pipe.lock().unwrap().write_all(&buf.0)?, } Ok(()) diff --git a/src/fmt/writer/buffer/termcolor.rs b/src/fmt/writer/buffer/termcolor.rs index 9d7374a7..f854ae6e 100644 --- a/src/fmt/writer/buffer/termcolor.rs +++ b/src/fmt/writer/buffer/termcolor.rs @@ -16,7 +16,7 @@ impl BufferWriter { BufferWriter { inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()), uncolored_target: if is_test { - Some(WritableTarget::Stderr) + Some(WritableTarget::PrintStderr) } else { None }, @@ -28,7 +28,7 @@ impl BufferWriter { BufferWriter { inner: termcolor::BufferWriter::stdout(write_style.into_color_choice()), uncolored_target: if is_test { - Some(WritableTarget::Stdout) + Some(WritableTarget::PrintStdout) } else { None }, @@ -65,8 +65,10 @@ impl BufferWriter { let log = String::from_utf8_lossy(buf.bytes()); match target { - WritableTarget::Stderr => eprint!("{}", log), - WritableTarget::Stdout => print!("{}", log), + WritableTarget::WriteStdout => print!("{}", log), + WritableTarget::PrintStdout => print!("{}", log), + WritableTarget::WriteStderr => eprint!("{}", log), + WritableTarget::PrintStderr => eprint!("{}", log), WritableTarget::Pipe(pipe) => write!(pipe.lock().unwrap(), "{}", log)?, } diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index d3e7345a..67648435 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -46,10 +46,16 @@ impl fmt::Debug for Target { /// /// Same as `Target`, except the pipe is wrapped in a mutex for interior mutability. pub(super) enum WritableTarget { - /// Logs will be sent to standard output. - Stdout, - /// Logs will be sent to standard error. - Stderr, + /// 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>), } @@ -60,8 +66,10 @@ impl fmt::Debug for WritableTarget { f, "{}", match self { - Self::Stdout => "stdout", - Self::Stderr => "stderr", + Self::WriteStdout => "stdout", + Self::PrintStdout => "stdout", + Self::WriteStderr => "stderr", + Self::PrintStderr => "stderr", Self::Pipe(_) => "pipe", } ) From e8674a237b62460b6dbc4ed8d8b48b6db0a97de4 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 16:41:36 -0600 Subject: [PATCH 16/53] refactor(fmt): Consolidate target printing --- src/fmt/mod.rs | 2 +- src/fmt/writer/buffer/plain.rs | 23 ++--------------------- src/fmt/writer/buffer/termcolor.rs | 17 ++--------------- src/fmt/writer/mod.rs | 22 ++++++++++++++++++++++ 4 files changed, 27 insertions(+), 37 deletions(-) diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs index 30b1384e..5f1d5c8f 100644 --- a/src/fmt/mod.rs +++ b/src/fmt/mod.rs @@ -468,7 +468,7 @@ mod tests { fmt.write(&record).expect("failed to write record"); let buf = buf.borrow(); - String::from_utf8(buf.bytes().to_vec()).expect("failed to read record") + String::from_utf8(buf.as_bytes().to_vec()).expect("failed to read record") } fn write_target(target: &str, fmt: DefaultFormat) -> String { diff --git a/src/fmt/writer/buffer/plain.rs b/src/fmt/writer/buffer/plain.rs index 0ebd6533..e6809b06 100644 --- a/src/fmt/writer/buffer/plain.rs +++ b/src/fmt/writer/buffer/plain.rs @@ -42,25 +42,7 @@ impl BufferWriter { } pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { - use std::io::Write as _; - - // This impl uses the `eprint` and `print` macros - // instead of using the streams directly. - // This is so their output can be captured by `cargo test`. - match &self.target { - WritableTarget::WriteStdout => { - write!(std::io::stdout(), "{}", String::from_utf8_lossy(&buf.0))? - } - WritableTarget::PrintStdout => print!("{}", String::from_utf8_lossy(&buf.0)), - WritableTarget::WriteStderr => { - write!(std::io::stderr(), "{}", String::from_utf8_lossy(&buf.0))? - } - WritableTarget::PrintStderr => eprint!("{}", String::from_utf8_lossy(&buf.0)), - // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty. - WritableTarget::Pipe(pipe) => pipe.lock().unwrap().write_all(&buf.0)?, - } - - Ok(()) + self.target.print(buf) } } @@ -80,8 +62,7 @@ impl Buffer { Ok(()) } - #[cfg(test)] - pub(in crate::fmt) fn bytes(&self) -> &[u8] { + 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 index f854ae6e..d3090a17 100644 --- a/src/fmt/writer/buffer/termcolor.rs +++ b/src/fmt/writer/buffer/termcolor.rs @@ -59,20 +59,7 @@ impl BufferWriter { pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { if let Some(target) = &self.uncolored_target { - // This impl uses the `eprint` and `print` macros - // instead of `termcolor`'s buffer. - // This is so their output can be captured by `cargo test` - let log = String::from_utf8_lossy(buf.bytes()); - - match target { - WritableTarget::WriteStdout => print!("{}", log), - WritableTarget::PrintStdout => print!("{}", log), - WritableTarget::WriteStderr => eprint!("{}", log), - WritableTarget::PrintStderr => eprint!("{}", log), - WritableTarget::Pipe(pipe) => write!(pipe.lock().unwrap(), "{}", log)?, - } - - Ok(()) + target.print(buf) } else { self.inner.print(&buf.inner) } @@ -97,7 +84,7 @@ impl Buffer { self.inner.flush() } - pub(in crate::fmt) fn bytes(&self) -> &[u8] { + pub(in crate::fmt) fn as_bytes(&self) -> &[u8] { self.inner.as_slice() } diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 67648435..8dafd264 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -60,6 +60,28 @@ pub(super) enum WritableTarget { Pipe(Box>), } +impl WritableTarget { + fn print(&self, buf: &Buffer) -> io::Result<()> { + use std::io::Write as _; + + let buf = buf.as_bytes(); + match self { + WritableTarget::WriteStdout => { + write!(std::io::stdout(), "{}", String::from_utf8_lossy(buf))? + } + WritableTarget::PrintStdout => print!("{}", String::from_utf8_lossy(buf)), + WritableTarget::WriteStderr => { + write!(std::io::stderr(), "{}", String::from_utf8_lossy(buf))? + } + 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) => pipe.lock().unwrap().write_all(buf)?, + } + + Ok(()) + } +} + impl fmt::Debug for WritableTarget { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( From 7428386da32c2a78b80f7e67a04b5fd5449601d5 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 17:07:13 -0600 Subject: [PATCH 17/53] refactor(fmt): Pull out stream lookup from write --- src/fmt/writer/mod.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 8dafd264..52265d46 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -67,15 +67,20 @@ impl WritableTarget { let buf = buf.as_bytes(); match self { WritableTarget::WriteStdout => { - write!(std::io::stdout(), "{}", String::from_utf8_lossy(buf))? + let mut stream = std::io::stdout().lock(); + write!(stream, "{}", String::from_utf8_lossy(buf))?; } WritableTarget::PrintStdout => print!("{}", String::from_utf8_lossy(buf)), WritableTarget::WriteStderr => { - write!(std::io::stderr(), "{}", String::from_utf8_lossy(buf))? + let mut stream = std::io::stderr().lock(); + write!(stream, "{}", String::from_utf8_lossy(buf))?; } 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) => pipe.lock().unwrap().write_all(buf)?, + WritableTarget::Pipe(pipe) => { + let mut stream = pipe.lock().unwrap(); + write!(stream, "{}", String::from_utf8_lossy(buf))?; + } } Ok(()) From 2b3f26fc29952296fdf79fa43d5beb35ddacbf40 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 17:07:58 -0600 Subject: [PATCH 18/53] perf(fmt): Avoid UTF-8 validation --- src/fmt/writer/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 52265d46..fa7ce1f1 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -68,18 +68,18 @@ impl WritableTarget { match self { WritableTarget::WriteStdout => { let mut stream = std::io::stdout().lock(); - write!(stream, "{}", String::from_utf8_lossy(buf))?; + stream.write_all(buf)?; } WritableTarget::PrintStdout => print!("{}", String::from_utf8_lossy(buf)), WritableTarget::WriteStderr => { let mut stream = std::io::stderr().lock(); - write!(stream, "{}", String::from_utf8_lossy(buf))?; + stream.write_all(buf)?; } 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(); - write!(stream, "{}", String::from_utf8_lossy(buf))?; + stream.write_all(buf)?; } } From c088820ae84318077e5a37a3bed6d20b3f7aa3f3 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 17:10:24 -0600 Subject: [PATCH 19/53] fix(fmt): Ensure stream gets flushed Fixes #278 --- src/fmt/writer/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index fa7ce1f1..ba0ec7ed 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -69,17 +69,20 @@ impl WritableTarget { WritableTarget::WriteStdout => { let mut stream = std::io::stdout().lock(); stream.write_all(buf)?; + stream.flush()?; } WritableTarget::PrintStdout => print!("{}", String::from_utf8_lossy(buf)), WritableTarget::WriteStderr => { let mut stream = std::io::stderr().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()?; } } From 939687dd75b606186989aa72fe43e716eaca5659 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 19:37:48 -0600 Subject: [PATCH 20/53] style: Make clippy happy --- src/fmt/writer/buffer/mod.rs | 2 +- src/fmt/writer/mod.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/fmt/writer/buffer/mod.rs b/src/fmt/writer/buffer/mod.rs index d3b44de6..4e678b2d 100644 --- a/src/fmt/writer/buffer/mod.rs +++ b/src/fmt/writer/buffer/mod.rs @@ -8,7 +8,7 @@ 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 termcolor::*; +pub(in crate::fmt) use self::termcolor::*; #[cfg(not(feature = "color"))] mod plain; #[cfg(not(feature = "color"))] diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index ba0ec7ed..41466b92 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -67,13 +67,15 @@ impl WritableTarget { let buf = buf.as_bytes(); match self { WritableTarget::WriteStdout => { - let mut stream = std::io::stdout().lock(); + 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 mut stream = std::io::stderr().lock(); + let stream = std::io::stderr(); + let mut stream = stream.lock(); stream.write_all(buf)?; stream.flush()?; } From 3df552c315f00f4bce6e8dfd7b0ab56210e8a74b Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 19:46:34 -0600 Subject: [PATCH 21/53] docs: Update changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 437ec6af..313b86f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate +### Performance + +- Avoid extra UTF-8 validation performed in some cases + +### Fixes + +- Ensure custom pipes/stdout get flushed +- Don't panic on broken pipes when `color` is disabled + ## [0.10.1] - 2023-11-10 ### Performance From 7929b7fc3df3223320c3cdd346138965f2c7e52f Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 19:46:44 -0600 Subject: [PATCH 22/53] chore: Release env_logger version 0.10.2 --- CHANGELOG.md | 5 ++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 313b86f8..6b911e5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate +## [0.10.2] - 2024-01-18 + ### Performance - Avoid extra UTF-8 validation performed in some cases @@ -59,7 +61,8 @@ To open room for changing dependencies: - Added a method to print the module instead of the target -[Unreleased]: https://github.com/rust-cli/env_logger/compare/v0.10.1...HEAD +[Unreleased]: https://github.com/rust-cli/env_logger/compare/v0.10.2...HEAD +[0.10.2]: https://github.com/rust-cli/env_logger/compare/v0.10.1...v0.10.2 [0.10.1]: https://github.com/rust-cli/env_logger/compare/v0.10.0...v0.10.1 [0.10.0]: https://github.com/rust-cli/env_logger/compare/v0.9.3...v0.10.0 [0.9.3]: https://github.com/rust-cli/env_logger/compare/v0.9.2...v0.9.3 diff --git a/Cargo.lock b/Cargo.lock index 2bb97a6a..3b0e0086 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,7 +31,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "env_logger" -version = "0.10.1" +version = "0.10.2" dependencies = [ "humantime", "is-terminal", diff --git a/Cargo.toml b/Cargo.toml index 7a1ae457..08da79ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "env_logger" -version = "0.10.1" +version = "0.10.2" description = """ A logging implementation for `log` which is configured via an environment variable. From 8018168cf14d46e32a9674df97a8db64730575d3 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 20:26:36 -0600 Subject: [PATCH 23/53] refactor(fmt): Remove glob mods --- src/fmt/mod.rs | 7 ++----- src/fmt/writer/buffer/plain.rs | 2 +- src/fmt/writer/buffer/termcolor.rs | 2 +- src/fmt/writer/mod.rs | 4 ---- src/lib.rs | 2 +- 5 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs index 5f1d5c8f..95feb782 100644 --- a/src/fmt/mod.rs +++ b/src/fmt/mod.rs @@ -50,14 +50,11 @@ pub use style::{Color, Style, StyledValue}; #[cfg(feature = "humantime")] pub use self::humantime::Timestamp; -pub use self::writer::glob::*; +pub use self::writer::Target; +pub use self::writer::WriteStyle; use self::writer::{Buffer, Writer}; -pub(crate) mod glob { - pub use super::{Target, TimestampPrecision, WriteStyle}; -} - /// Formatting precision of timestamps. /// /// Seconds give precision of full seconds, milliseconds give thousands of a diff --git a/src/fmt/writer/buffer/plain.rs b/src/fmt/writer/buffer/plain.rs index e6809b06..99056783 100644 --- a/src/fmt/writer/buffer/plain.rs +++ b/src/fmt/writer/buffer/plain.rs @@ -1,6 +1,6 @@ use std::{io, sync::Mutex}; -use crate::fmt::{WritableTarget, WriteStyle}; +use crate::fmt::writer::{WritableTarget, WriteStyle}; pub(in crate::fmt::writer) struct BufferWriter { target: WritableTarget, diff --git a/src/fmt/writer/buffer/termcolor.rs b/src/fmt/writer/buffer/termcolor.rs index d3090a17..648ed16f 100644 --- a/src/fmt/writer/buffer/termcolor.rs +++ b/src/fmt/writer/buffer/termcolor.rs @@ -3,7 +3,7 @@ use std::sync::Mutex; use termcolor::{self, ColorSpec, WriteColor}; -use crate::fmt::{WritableTarget, WriteStyle}; +use crate::fmt::writer::{WritableTarget, WriteStyle}; pub(in crate::fmt::writer) struct BufferWriter { inner: termcolor::BufferWriter, diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 41466b92..986b75c6 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -5,10 +5,6 @@ use self::atty::{is_stderr, is_stdout}; use self::buffer::BufferWriter; use std::{fmt, io, mem, sync::Mutex}; -pub(super) mod glob { - pub use super::*; -} - pub(super) use self::buffer::Buffer; /// Log target, either `stdout`, `stderr` or a custom pipe. diff --git a/src/lib.rs b/src/lib.rs index cdc2badc..93eba6ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -280,5 +280,5 @@ mod logger; pub mod filter; pub mod fmt; -pub use self::fmt::glob::*; +pub use self::fmt::{Target, TimestampPrecision, WriteStyle}; pub use self::logger::*; From f4808e46e8adf6f4d0182538070ce98f11723bb1 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 20:29:23 -0600 Subject: [PATCH 24/53] refactor(fmt): Pull out target mod --- src/fmt/writer/mod.rs | 98 ++-------------------------------------- src/fmt/writer/target.rs | 96 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 95 deletions(-) create mode 100644 src/fmt/writer/target.rs diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 986b75c6..c8753e0f 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -1,5 +1,6 @@ mod atty; mod buffer; +mod target; use self::atty::{is_stderr, is_stdout}; use self::buffer::BufferWriter; @@ -7,102 +8,9 @@ use std::{fmt, io, mem, sync::Mutex}; pub(super) use self::buffer::Buffer; -/// Log target, either `stdout`, `stderr` or a custom pipe. -#[non_exhaustive] -pub enum Target { - /// Logs will be sent to standard output. - Stdout, - /// Logs will be sent to standard error. - Stderr, - /// Logs will be sent to a custom pipe. - Pipe(Box), -} - -impl Default for Target { - fn default() -> Self { - Target::Stderr - } -} - -impl fmt::Debug for Target { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - Self::Stdout => "stdout", - Self::Stderr => "stderr", - Self::Pipe(_) => "pipe", - } - ) - } -} - -/// 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 { - fn print(&self, buf: &Buffer) -> io::Result<()> { - use std::io::Write as _; +pub use target::Target; +use target::WritableTarget; - 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 fmt::Debug for WritableTarget { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - Self::WriteStdout => "stdout", - Self::PrintStdout => "stdout", - Self::WriteStderr => "stderr", - Self::PrintStderr => "stderr", - Self::Pipe(_) => "pipe", - } - ) - } -} /// Whether or not to print styles to the target. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum WriteStyle { diff --git a/src/fmt/writer/target.rs b/src/fmt/writer/target.rs new file mode 100644 index 00000000..3d908665 --- /dev/null +++ b/src/fmt/writer/target.rs @@ -0,0 +1,96 @@ +/// Log target, either `stdout`, `stderr` or a custom pipe. +#[non_exhaustive] +pub enum Target { + /// Logs will be sent to standard output. + Stdout, + /// Logs will be sent to standard error. + 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!( + f, + "{}", + match self { + Self::Stdout => "stdout", + Self::Stderr => "stderr", + Self::Pipe(_) => "pipe", + } + ) + } +} + +/// 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", + } + ) + } +} From a77a76138dc28723d07ad8816b5f524d71778bd0 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 16 Jan 2024 14:35:21 -0600 Subject: [PATCH 25/53] chore: Bump MSRV to 1.71 - 1.70.0 is needed for `anstream` to be added - 1.71.0 is needed for changes to how cargo treats features --- .clippy.toml | 2 +- .github/workflows/ci.yml | 6 +++--- Cargo.toml | 2 +- src/fmt/writer/mod.rs | 8 +++----- src/fmt/writer/target.rs | 8 +++----- 5 files changed, 11 insertions(+), 15 deletions(-) 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.toml b/Cargo.toml index 08da79ce..3d19de74 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/**/*", diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index c8753e0f..4195e788 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -13,8 +13,10 @@ use target::WritableTarget; /// Whether or not to print styles to the target. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Default)] pub enum WriteStyle { /// Try to print styles, but don't force the issue. + #[default] Auto, /// Try very hard to print styles. Always, @@ -22,11 +24,7 @@ pub enum WriteStyle { Never, } -impl Default for WriteStyle { - fn default() -> Self { - WriteStyle::Auto - } -} + #[cfg(feature = "color")] impl WriteStyle { diff --git a/src/fmt/writer/target.rs b/src/fmt/writer/target.rs index 3d908665..c3fe4825 100644 --- a/src/fmt/writer/target.rs +++ b/src/fmt/writer/target.rs @@ -1,19 +1,17 @@ /// 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 { From fb603ba2e711b743398ba54c263396d797ead2c0 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 16 Jan 2024 14:36:37 -0600 Subject: [PATCH 26/53] perf: Remove is-terminal dependency Fixes #276 --- Cargo.lock | 148 ----------------------------------------- Cargo.toml | 3 +- src/fmt/writer/atty.rs | 26 ++------ 3 files changed, 6 insertions(+), 171 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b0e0086..cca961ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,18 +11,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "bitflags" -version = "1.3.2" -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" - [[package]] name = "cfg-if" version = "1.0.0" @@ -34,82 +22,17 @@ name = "env_logger" version = "0.10.2" dependencies = [ "humantime", - "is-terminal", "log", "regex", "termcolor", ] -[[package]] -name = "errno" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" -[[package]] -name = "io-lifetimes" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7d367024b3f3414d8e01f437f704f41a9f64ab36f9067fa73e526ad4c763c87" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "is-terminal" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae5bc6e2eb41c9def29a3e0f1306382807764b9b53112030eff57435667352d" -dependencies = [ - "hermit-abi", - "io-lifetimes", - "rustix", - "windows-sys", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" - [[package]] name = "log" version = "0.4.17" @@ -142,20 +65,6 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" -[[package]] -name = "rustix" -version = "0.36.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1fbb4dfc4eb1d390c02df47760bb19a84bb80b301ecc947ab5406394d8223e" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys", -] - [[package]] name = "termcolor" version = "1.1.3" @@ -195,60 +104,3 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/Cargo.toml b/Cargo.toml index 3d19de74..2f831016 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ pre-release-replacements = [ [features] default = ["auto-color", "humantime", "regex"] color = ["dep:termcolor"] -auto-color = ["dep:is-terminal", "color"] +auto-color = ["color"] humantime = ["dep:humantime"] regex = ["dep:regex"] @@ -48,7 +48,6 @@ 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 } [[test]] name = "regexp_filter" diff --git a/src/fmt/writer/atty.rs b/src/fmt/writer/atty.rs index 1a133eef..13c994f5 100644 --- a/src/fmt/writer/atty.rs +++ b/src/fmt/writer/atty.rs @@ -6,28 +6,12 @@ Otherwise, assume we're not attached to anything. This effectively prevents styl printed. */ -#[cfg(feature = "auto-color")] -mod imp { - use is_terminal::IsTerminal; +use std::io::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() - } +pub(in crate::fmt) fn is_stdout() -> bool { + cfg!(feature = "auto-color") && std::io::stdout().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) fn is_stderr() -> bool { + cfg!(feature = "auto-color") && std::io::stderr().is_terminal() } - -pub(in crate::fmt) use self::imp::*; From 5cbbff926fafac2cefd301d6f57c51aaa7b5d7d1 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 20:05:05 -0600 Subject: [PATCH 27/53] fix(fmt)!: Remove color support --- Cargo.lock | 41 ---- Cargo.toml | 3 +- examples/custom_format.rs | 12 +- src/fmt/mod.rs | 81 ------- src/fmt/style.rs | 351 ----------------------------- src/fmt/writer/buffer/mod.rs | 13 -- src/fmt/writer/buffer/termcolor.rs | 108 --------- src/fmt/writer/mod.rs | 11 - src/fmt/writer/target.rs | 2 - 9 files changed, 3 insertions(+), 619 deletions(-) delete mode 100644 src/fmt/style.rs delete mode 100644 src/fmt/writer/buffer/termcolor.rs diff --git a/Cargo.lock b/Cargo.lock index cca961ea..80a265e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,7 +24,6 @@ dependencies = [ "humantime", "log", "regex", - "termcolor", ] [[package]] @@ -64,43 +63,3 @@ name = "regex-syntax" version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 2f831016..499fbbb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ pre-release-replacements = [ [features] default = ["auto-color", "humantime", "regex"] -color = ["dep:termcolor"] +color = [] auto-color = ["color"] humantime = ["dep:humantime"] regex = ["dep:regex"] @@ -46,7 +46,6 @@ 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 } [[test]] diff --git a/examples/custom_format.rs b/examples/custom_format.rs index cc16b336..1cc4bdec 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,17 +30,9 @@ fn main() { Builder::from_env(env) .format(|buf, record| { - let mut style = buf.style(); - style.set_bg(Color::Yellow).set_bold(true); - let timestamp = buf.timestamp(); - writeln!( - buf, - "My formatted log ({}): {}", - timestamp, - style.value(record.args()) - ) + writeln!(buf, "My formatted log ({}): {}", timestamp, record.args()) }) .init(); } diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs index 95feb782..dd78402f 100644 --- a/src/fmt/mod.rs +++ b/src/fmt/mod.rs @@ -35,19 +35,12 @@ use std::io::prelude::*; use std::rc::Rc; use std::{fmt, io, mem}; -#[cfg(feature = "color")] -use log::Level; use log::Record; #[cfg(feature = "humantime")] mod humantime; pub(crate) mod writer; -#[cfg(feature = "color")] -mod style; -#[cfg(feature = "color")] -pub use style::{Color, Style, StyledValue}; - #[cfg(feature = "humantime")] pub use self::humantime::Timestamp; pub use self::writer::Target; @@ -126,62 +119,6 @@ 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. - /// - /// 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) - } -} - impl Write for Formatter { fn write(&mut self, buf: &[u8]) -> io::Result { self.buf.borrow_mut().write(buf) @@ -264,9 +201,6 @@ impl Default for Builder { } } -#[cfg(feature = "color")] -type SubtleStyle = StyledValue<'static, &'static str>; -#[cfg(not(feature = "color"))] type SubtleStyle = &'static str; /// The default format. @@ -295,16 +229,6 @@ 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) - } - #[cfg(not(feature = "color"))] { text } @@ -330,11 +254,6 @@ impl<'a> DefaultFormat<'a> { } let level = { - #[cfg(feature = "color")] - { - self.buf.default_styled_level(record.level()) - } - #[cfg(not(feature = "color"))] { record.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/buffer/mod.rs b/src/fmt/writer/buffer/mod.rs index 4e678b2d..d0dbb5f5 100644 --- a/src/fmt/writer/buffer/mod.rs +++ b/src/fmt/writer/buffer/mod.rs @@ -1,15 +1,2 @@ -/* -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/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 4195e788..74ab560d 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -26,17 +26,6 @@ pub enum WriteStyle { -#[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, - } - } -} - /// A terminal target with color awareness. pub(crate) struct Writer { inner: BufferWriter, diff --git a/src/fmt/writer/target.rs b/src/fmt/writer/target.rs index c3fe4825..0b806f9e 100644 --- a/src/fmt/writer/target.rs +++ b/src/fmt/writer/target.rs @@ -32,12 +32,10 @@ impl std::fmt::Debug for Target { /// 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, From 0016e2c51212ea192bec1beaeb5e01fef5285eb9 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 20:21:11 -0600 Subject: [PATCH 28/53] refactor(fmt): Flatten mods --- src/fmt/writer/{buffer/plain.rs => buffer.rs} | 0 src/fmt/writer/buffer/mod.rs | 2 -- 2 files changed, 2 deletions(-) rename src/fmt/writer/{buffer/plain.rs => buffer.rs} (100%) delete mode 100644 src/fmt/writer/buffer/mod.rs diff --git a/src/fmt/writer/buffer/plain.rs b/src/fmt/writer/buffer.rs similarity index 100% rename from src/fmt/writer/buffer/plain.rs rename to src/fmt/writer/buffer.rs diff --git a/src/fmt/writer/buffer/mod.rs b/src/fmt/writer/buffer/mod.rs deleted file mode 100644 index d0dbb5f5..00000000 --- a/src/fmt/writer/buffer/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod plain; -pub(in crate::fmt) use plain::*; From 1c50358db72afd46bf38922279ed7d492f472b29 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 21:12:39 -0600 Subject: [PATCH 29/53] refactor(fmt): Inline the print call --- src/fmt/writer/buffer.rs | 28 +++++++++++++++++++++++++++- src/fmt/writer/target.rs | 32 -------------------------------- 2 files changed, 27 insertions(+), 33 deletions(-) diff --git a/src/fmt/writer/buffer.rs b/src/fmt/writer/buffer.rs index 99056783..64f9f087 100644 --- a/src/fmt/writer/buffer.rs +++ b/src/fmt/writer/buffer.rs @@ -42,7 +42,33 @@ impl BufferWriter { } pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { - self.target.print(buf) + use std::io::Write as _; + + let buf = buf.as_bytes(); + match &self.target { + 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(()) } } diff --git a/src/fmt/writer/target.rs b/src/fmt/writer/target.rs index 0b806f9e..02f69c75 100644 --- a/src/fmt/writer/target.rs +++ b/src/fmt/writer/target.rs @@ -43,38 +43,6 @@ pub(super) enum WritableTarget { 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!( From f3de55b9eb44a0da90242961e581bac58817cc9c Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 21:40:58 -0600 Subject: [PATCH 30/53] refactor(fmt): Pull in Target to its only use --- src/fmt/writer/buffer.rs | 34 +++++++++++++++++++++++++++++++++- src/fmt/writer/mod.rs | 1 - src/fmt/writer/target.rs | 34 ---------------------------------- 3 files changed, 33 insertions(+), 36 deletions(-) diff --git a/src/fmt/writer/buffer.rs b/src/fmt/writer/buffer.rs index 64f9f087..9b7cbf3c 100644 --- a/src/fmt/writer/buffer.rs +++ b/src/fmt/writer/buffer.rs @@ -1,6 +1,6 @@ use std::{io, sync::Mutex}; -use crate::fmt::writer::{WritableTarget, WriteStyle}; +use crate::fmt::writer::WriteStyle; pub(in crate::fmt::writer) struct BufferWriter { target: WritableTarget, @@ -92,3 +92,35 @@ impl Buffer { &self.0 } } + +/// 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/mod.rs b/src/fmt/writer/mod.rs index 74ab560d..f5f7de32 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -9,7 +9,6 @@ use std::{fmt, 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)] diff --git a/src/fmt/writer/target.rs b/src/fmt/writer/target.rs index 02f69c75..a1220ffe 100644 --- a/src/fmt/writer/target.rs +++ b/src/fmt/writer/target.rs @@ -11,8 +11,6 @@ pub enum Target { Pipe(Box), } - - impl std::fmt::Debug for Target { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( @@ -26,35 +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. - 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", - } - ) - } -} From bce8463804c027d2e2aca8a07ef6c4cd29ab98e9 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 20:30:39 -0600 Subject: [PATCH 31/53] feat(fmt): Add back color support This also adds support for `NO_COLOR` and `CLICOLOR_FORCE`, see https://bixense.com/clicolors/ --- Cargo.lock | 127 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 5 +- src/fmt/writer/atty.rs | 17 ------ src/fmt/writer/buffer.rs | 44 ++++++++++++-- src/fmt/writer/mod.rs | 59 ++++++++++++------ src/logger.rs | 3 + 6 files changed, 213 insertions(+), 42 deletions(-) delete mode 100644 src/fmt/writer/atty.rs diff --git a/Cargo.lock b/Cargo.lock index 80a265e7..ac42c663 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,16 +11,71 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "env_logger" version = "0.10.2" dependencies = [ + "anstream", "humantime", "log", "regex", @@ -63,3 +118,75 @@ name = "regex-syntax" version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/Cargo.toml b/Cargo.toml index 499fbbb9..06871c62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,8 +38,8 @@ pre-release-replacements = [ [features] default = ["auto-color", "humantime", "regex"] -color = [] -auto-color = ["color"] +color = ["dep:anstream"] +auto-color = ["color", "anstream/auto"] humantime = ["dep:humantime"] regex = ["dep:regex"] @@ -47,6 +47,7 @@ regex = ["dep:regex"] log = { version = "0.4.8", features = ["std"] } regex = { version = "1.0.3", optional = true, default-features=false, features=["std", "perf"] } humantime = { version = "2.0.0", optional = true } +anstream = { version = "0.6.11", default-features = false, features = ["wincon"], optional = true } [[test]] name = "regexp_filter" diff --git a/src/fmt/writer/atty.rs b/src/fmt/writer/atty.rs deleted file mode 100644 index 13c994f5..00000000 --- a/src/fmt/writer/atty.rs +++ /dev/null @@ -1,17 +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. -*/ - -use std::io::IsTerminal; - -pub(in crate::fmt) fn is_stdout() -> bool { - cfg!(feature = "auto-color") && std::io::stdout().is_terminal() -} - -pub(in crate::fmt) fn is_stderr() -> bool { - cfg!(feature = "auto-color") && std::io::stderr().is_terminal() -} diff --git a/src/fmt/writer/buffer.rs b/src/fmt/writer/buffer.rs index 9b7cbf3c..aeffde05 100644 --- a/src/fmt/writer/buffer.rs +++ b/src/fmt/writer/buffer.rs @@ -4,37 +4,41 @@ use crate::fmt::writer::WriteStyle; 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 { + 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 { + 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 { - WriteStyle::Never + self.write_style } pub(in crate::fmt::writer) fn buffer(&self) -> Buffer { @@ -48,19 +52,36 @@ impl BufferWriter { 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 => print!("{}", String::from_utf8_lossy(buf)), + 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 => eprint!("{}", String::from_utf8_lossy(buf)), - // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty. + 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) => { let mut stream = pipe.lock().unwrap(); stream.write_all(buf)?; @@ -72,6 +93,17 @@ impl BufferWriter { } } +#[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::StripStream::new(adapted); + stream.write_all(buf)?; + let adapted = stream.into_inner(); + Ok(adapted) +} + pub(in crate::fmt) struct Buffer(Vec); impl Buffer { diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index f5f7de32..65f3445e 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -1,8 +1,6 @@ -mod atty; mod buffer; mod target; -use self::atty::{is_stderr, is_stdout}; use self::buffer::BufferWriter; use std::{fmt, io, mem, sync::Mutex}; @@ -11,8 +9,7 @@ pub(super) use self::buffer::Buffer; pub use target::Target; /// Whether or not to print styles to the target. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -#[derive(Default)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Default)] pub enum WriteStyle { /// Try to print styles, but don't force the issue. #[default] @@ -23,7 +20,28 @@ pub enum WriteStyle { Never, } +#[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 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. pub(crate) struct Writer { @@ -105,29 +123,36 @@ 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(_) => WriteStyle::Never, } - color_choice => color_choice, + } else { + color_choice + }; + let color_choice = match &self.target { + Target::Stdout => color_choice, + Target::Stderr => color_choice, + Target::Pipe(_) => WriteStyle::Never, }; let color_choice = if self.is_test { WriteStyle::Never } else { color_choice }; + 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/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: From 7d5b72a76e62616a4d3328bde084a95af052ba8b Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jan 2024 10:29:49 -0600 Subject: [PATCH 32/53] feat(fmt): Add back in Formatter::default_level_style --- Cargo.lock | 1 + Cargo.toml | 3 ++- src/fmt/mod.rs | 27 +++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index ac42c663..69452d28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,7 @@ name = "env_logger" version = "0.10.2" dependencies = [ "anstream", + "anstyle", "humantime", "log", "regex", diff --git a/Cargo.toml b/Cargo.toml index 06871c62..a4ccec85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ pre-release-replacements = [ [features] default = ["auto-color", "humantime", "regex"] -color = ["dep:anstream"] +color = ["dep:anstream", "dep:anstyle"] auto-color = ["color", "anstream/auto"] humantime = ["dep:humantime"] regex = ["dep:regex"] @@ -48,6 +48,7 @@ log = { version = "0.4.8", features = ["std"] } regex = { version = "1.0.3", optional = true, default-features=false, features=["std", "perf"] } humantime = { version = "2.0.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/src/fmt/mod.rs b/src/fmt/mod.rs index dd78402f..bc5e6134 100644 --- a/src/fmt/mod.rs +++ b/src/fmt/mod.rs @@ -35,12 +35,17 @@ use std::io::prelude::*; use std::rc::Rc; use std::{fmt, io, mem}; +#[cfg(feature = "color")] +use log::Level; use log::Record; #[cfg(feature = "humantime")] mod humantime; pub(crate) mod writer; +#[cfg(feature = "color")] +pub use anstyle as style; + #[cfg(feature = "humantime")] pub use self::humantime::Timestamp; pub use self::writer::Target; @@ -119,6 +124,28 @@ impl Formatter { } } +#[cfg(feature = "color")] +impl Formatter { + /// 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::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), + } + } + } +} + impl Write for Formatter { fn write(&mut self, buf: &[u8]) -> io::Result { self.buf.borrow_mut().write(buf) From 2f3ca7d6956fa014927bf1a7316e6981e3829309 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jan 2024 10:36:03 -0600 Subject: [PATCH 33/53] docs: Add back in custom colors --- examples/custom_format.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/custom_format.rs b/examples/custom_format.rs index 1cc4bdec..8f575fd4 100644 --- a/examples/custom_format.rs +++ b/examples/custom_format.rs @@ -30,9 +30,18 @@ fn main() { Builder::from_env(env) .format(|buf, record| { + // 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, record.args()) + writeln!( + buf, + "My formatted log ({timestamp}): {warn_style}{}{reset}", + record.args() + ) }) .init(); } From 303a9c0ed288c5c7c15b6a74e29bfc1467bf9eab Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jan 2024 10:56:55 -0600 Subject: [PATCH 34/53] feat(fmt): Add back in level styling --- src/fmt/mod.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs index bc5e6134..771fd5cd 100644 --- a/src/fmt/mod.rs +++ b/src/fmt/mod.rs @@ -228,8 +228,37 @@ impl Default for Builder { } } +#[cfg(feature = "color")] +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. @@ -256,6 +285,18 @@ impl<'a> DefaultFormat<'a> { } fn subtle_style(&self, text: &'static str) -> SubtleStyle { + #[cfg(feature = "color")] + { + StyledValue { + style: if self.buf.write_style == WriteStyle::Never { + style::Style::new() + } else { + style::AnsiColor::BrightBlack.on_default() + }, + value: text, + } + } + #[cfg(not(feature = "color"))] { text } @@ -281,8 +322,17 @@ impl<'a> DefaultFormat<'a> { } let level = { + let level = record.level(); + #[cfg(feature = "color")] + { + StyledValue { + style: self.buf.default_level_style(level), + value: level, + } + } + #[cfg(not(feature = "color"))] { - record.level() + level } }; From 4276f5fa7efc08408e1c2f6dcd9cc8b042afd6c1 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jan 2024 11:14:10 -0600 Subject: [PATCH 35/53] fix(fmt): Print colors for tests This was discussed in #225 but no dedicated issue for it. --- src/fmt/writer/buffer.rs | 4 ++-- src/fmt/writer/mod.rs | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/fmt/writer/buffer.rs b/src/fmt/writer/buffer.rs index aeffde05..fe983a00 100644 --- a/src/fmt/writer/buffer.rs +++ b/src/fmt/writer/buffer.rs @@ -94,11 +94,11 @@ impl BufferWriter { } #[cfg(feature = "color")] -fn adapt(buf: &[u8], _write_style: WriteStyle) -> std::io::Result> { +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::StripStream::new(adapted); + let mut stream = anstream::AutoStream::new(adapted, write_style.into()); stream.write_all(buf)?; let adapted = stream.into_inner(); Ok(adapted) diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 65f3445e..545d642d 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -139,11 +139,6 @@ impl Builder { Target::Stderr => color_choice, Target::Pipe(_) => WriteStyle::Never, }; - let color_choice = if self.is_test { - WriteStyle::Never - } else { - color_choice - }; let color_choice = if color_choice == WriteStyle::Auto { WriteStyle::Never } else { From 730b0a096b121e4732f9e69a0132f1ccd4dbd9ed Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jan 2024 11:15:32 -0600 Subject: [PATCH 36/53] fix(fmt): Allow styling on the `Pipe` Fixes #274 --- src/fmt/writer/buffer.rs | 4 ++++ src/fmt/writer/mod.rs | 7 +------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/fmt/writer/buffer.rs b/src/fmt/writer/buffer.rs index fe983a00..89ad9125 100644 --- a/src/fmt/writer/buffer.rs +++ b/src/fmt/writer/buffer.rs @@ -83,6 +83,10 @@ impl BufferWriter { 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()?; diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 545d642d..a3e66065 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -129,16 +129,11 @@ impl Builder { match &self.target { Target::Stdout => anstream::AutoStream::choice(&std::io::stdout()).into(), Target::Stderr => anstream::AutoStream::choice(&std::io::stderr()).into(), - Target::Pipe(_) => WriteStyle::Never, + Target::Pipe(_) => color_choice, } } else { color_choice }; - let color_choice = match &self.target { - Target::Stdout => color_choice, - Target::Stderr => color_choice, - Target::Pipe(_) => WriteStyle::Never, - }; let color_choice = if color_choice == WriteStyle::Auto { WriteStyle::Never } else { From ea464099b79fd5038d77e318bb46237868473b3d Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Jan 2024 12:21:39 -0600 Subject: [PATCH 37/53] chore(fmt): Add debug impls --- src/fmt/mod.rs | 6 +++++- src/fmt/writer/buffer.rs | 7 +++++++ src/fmt/writer/mod.rs | 9 ++------- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs index 771fd5cd..faee69bb 100644 --- a/src/fmt/mod.rs +++ b/src/fmt/mod.rs @@ -158,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() } } diff --git a/src/fmt/writer/buffer.rs b/src/fmt/writer/buffer.rs index 89ad9125..a7ea25bf 100644 --- a/src/fmt/writer/buffer.rs +++ b/src/fmt/writer/buffer.rs @@ -2,6 +2,7 @@ use std::{io, sync::Mutex}; use crate::fmt::writer::WriteStyle; +#[derive(Debug)] pub(in crate::fmt::writer) struct BufferWriter { target: WritableTarget, write_style: WriteStyle, @@ -129,6 +130,12 @@ impl Buffer { } } +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. diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index a3e66065..dbcf45b6 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -2,7 +2,7 @@ mod buffer; mod target; use self::buffer::BufferWriter; -use std::{fmt, io, mem, sync::Mutex}; +use std::{io, mem, sync::Mutex}; pub(super) use self::buffer::Buffer; @@ -44,6 +44,7 @@ impl From for anstream::ColorChoice { } /// A terminal target with color awareness. +#[derive(Debug)] pub(crate) struct Writer { inner: BufferWriter, } @@ -62,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. From b4a2c304c16d1db4a2998f24c00e00c0f776113b Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Jan 2024 10:56:23 -0600 Subject: [PATCH 38/53] chore: Add a workspace --- Cargo.toml | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a4ccec85..9c17f7c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,7 @@ -[package] -name = "env_logger" -version = "0.10.2" -description = """ -A logging implementation for `log` which is configured via an environment -variable. -""" -repository = "https://github.com/rust-cli/env_logger" -categories = ["development-tools::debugging"] -keywords = ["logging", "log", "logger"] +[workspace] +resolver = "2" + +[workspace.package] license = "MIT OR Apache-2.0" edition = "2021" rust-version = "1.71" # MSRV @@ -23,6 +17,21 @@ include = [ "tests/**/*", ] +[package] +name = "env_logger" +version = "0.10.2" +description = """ +A logging implementation for `log` which is configured via an environment +variable. +""" +repository = "https://github.com/rust-cli/env_logger" +categories = ["development-tools::debugging"] +keywords = ["logging", "log", "logger"] +license.workspace = true +edition.workspace = true +rust-version.workspace = true +include.workspace = true + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] From f1877824da51e8b79bcd8747e168b610b4a64cb3 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Jan 2024 11:12:17 -0600 Subject: [PATCH 39/53] refactor: Split out env_filter package Fixes #193 --- Cargo.lock | 10 +++++- Cargo.toml | 5 +-- crates/env_filter/CHANGELOG.md | 11 ++++++ crates/env_filter/Cargo.toml | 34 +++++++++++++++++++ crates/env_filter/LICENSE-APACHE | 1 + crates/env_filter/LICENSE-MIT | 1 + crates/env_filter/README.md | 6 ++++ .../mod.rs => crates/env_filter/src/lib.rs | 27 +++++---------- .../filter => crates/env_filter/src}/regex.rs | 4 +-- .../env_filter/src}/string.rs | 0 src/lib.rs | 3 +- 11 files changed, 76 insertions(+), 26 deletions(-) create mode 100644 crates/env_filter/CHANGELOG.md create mode 100644 crates/env_filter/Cargo.toml create mode 120000 crates/env_filter/LICENSE-APACHE create mode 120000 crates/env_filter/LICENSE-MIT create mode 100644 crates/env_filter/README.md rename src/filter/mod.rs => crates/env_filter/src/lib.rs (97%) rename {src/filter => crates/env_filter/src}/regex.rs (91%) rename {src/filter => crates/env_filter/src}/string.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 69452d28..776d3400 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,15 +71,23 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "env_filter" +version = "0.1.0" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" version = "0.10.2" dependencies = [ "anstream", "anstyle", + "env_filter", "humantime", "log", - "regex", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9c17f7c7..b669cef6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] resolver = "2" +members = ["crates/*"] [workspace.package] license = "MIT OR Apache-2.0" @@ -50,11 +51,11 @@ default = ["auto-color", "humantime", "regex"] color = ["dep:anstream", "dep:anstyle"] auto-color = ["color", "anstream/auto"] humantime = ["dep:humantime"] -regex = ["dep:regex"] +regex = ["env_filter/regex"] [dependencies] log = { version = "0.4.8", features = ["std"] } -regex = { version = "1.0.3", optional = true, default-features=false, features=["std", "perf"] } +env_filter = { version = "0.1.0", path = "crates/env_filter", default-features = false } humantime = { version = "2.0.0", optional = true } anstream = { version = "0.6.11", default-features = false, features = ["wincon"], optional = true } anstyle = { version = "1.0.4", optional = true } diff --git a/crates/env_filter/CHANGELOG.md b/crates/env_filter/CHANGELOG.md new file mode 100644 index 00000000..78e2e120 --- /dev/null +++ b/crates/env_filter/CHANGELOG.md @@ -0,0 +1,11 @@ +# Change Log +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + + +## [Unreleased] - ReleaseDate + + +[Unreleased]: https://github.com/rust-cli/env_logger/compare/b4a2c304c16d1db4a2998f24c00e00c0f776113b...HEAD diff --git a/crates/env_filter/Cargo.toml b/crates/env_filter/Cargo.toml new file mode 100644 index 00000000..9ac0fa47 --- /dev/null +++ b/crates/env_filter/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "env_filter" +version = "0.1.0" +description = """ +Filter log events using environment variables +""" +repository = "https://github.com/rust-cli/env_logger" +categories = ["development-tools::debugging"] +keywords = ["logging", "log", "logger"] +license.workspace = true +edition.workspace = true +rust-version.workspace = true +include.workspace = true + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.release] +pre-release-replacements = [ + {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, + {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, + {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, + {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, + {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/rust-cli/env_logger/compare/{{tag_name}}...HEAD", exactly=1}, +] + +[features] +default = ["regex"] +regex = ["dep:regex"] + +[dependencies] +log = { version = "0.4.8", features = ["std"] } +regex = { version = "1.0.3", optional = true, default-features=false, features=["std", "perf"] } diff --git a/crates/env_filter/LICENSE-APACHE b/crates/env_filter/LICENSE-APACHE new file mode 120000 index 00000000..1cd601d0 --- /dev/null +++ b/crates/env_filter/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE \ No newline at end of file diff --git a/crates/env_filter/LICENSE-MIT b/crates/env_filter/LICENSE-MIT new file mode 120000 index 00000000..b2cfbdc7 --- /dev/null +++ b/crates/env_filter/LICENSE-MIT @@ -0,0 +1 @@ +../../LICENSE-MIT \ No newline at end of file diff --git a/crates/env_filter/README.md b/crates/env_filter/README.md new file mode 100644 index 00000000..9e5164fb --- /dev/null +++ b/crates/env_filter/README.md @@ -0,0 +1,6 @@ +# env_filter + +[![crates.io](https://img.shields.io/crates/v/env_filter.svg)](https://crates.io/crates/env_filter) +[![Documentation](https://docs.rs/env_filter/badge.svg)](https://docs.rs/env_filter) + +> Filter log events using environment variables diff --git a/src/filter/mod.rs b/crates/env_filter/src/lib.rs similarity index 97% rename from src/filter/mod.rs rename to crates/env_filter/src/lib.rs index d4acf632..5d7d60b0 100644 --- a/src/filter/mod.rs +++ b/crates/env_filter/src/lib.rs @@ -1,21 +1,17 @@ //! Filtering for log records. //! -//! This module contains the log filtering used by `env_logger` to match records. -//! You can use the `Filter` type in your own logger implementation to use the same -//! filter parsing and matching as `env_logger`. For more details about the format -//! for directive strings see [Enabling Logging]. +//! You can use the [`Filter`] type in your own logger implementation to use the same +//! filter parsing and matching as `env_logger`. //! -//! ## Using `env_logger` in your own logger +//! ## Using `env_filter` in your own logger //! -//! You can use `env_logger`'s filtering functionality with your own logger. +//! You can use `env_filter`'s filtering functionality with your own logger. //! Call [`Builder::parse`] to parse directives from a string when constructing //! your logger. Call [`Filter::matches`] to check whether a record should be //! logged based on the parsed filters when log records are received. //! //! ``` -//! extern crate log; -//! extern crate env_logger; -//! use env_logger::filter::Filter; +//! use env_filter::Filter; //! use log::{Log, Metadata, Record}; //! //! struct MyLogger { @@ -24,7 +20,7 @@ //! //! impl MyLogger { //! fn new() -> MyLogger { -//! use env_logger::filter::Builder; +//! use env_filter::Builder; //! let mut builder = Builder::new(); //! //! // Parse a directives string from an environment variable @@ -53,10 +49,6 @@ //! fn flush(&self) {} //! } //! ``` -//! -//! [Enabling Logging]: ../index.html#enabling-logging -//! [`Builder::parse`]: struct.Builder.html#method.parse -//! [`Filter::matches`]: struct.Filter.html#method.matches use log::{Level, LevelFilter, Metadata, Record}; use std::env; @@ -79,9 +71,8 @@ mod inner; /// ## Example /// /// ``` -/// # #[macro_use] extern crate log; /// # use std::env; -/// use env_logger::filter::Builder; +/// use env_filter::Builder; /// /// let mut builder = Builder::new(); /// @@ -92,8 +83,6 @@ mod inner; /// /// let filter = builder.build(); /// ``` -/// -/// [`Filter`]: struct.Filter.html pub struct Builder { directives: Vec, filter: Option, @@ -248,7 +237,7 @@ impl Filter { /// /// ```rust /// use log::LevelFilter; - /// use env_logger::filter::Builder; + /// use env_filter::Builder; /// /// let mut builder = Builder::new(); /// builder.filter(Some("module1"), LevelFilter::Info); diff --git a/src/filter/regex.rs b/crates/env_filter/src/regex.rs similarity index 91% rename from src/filter/regex.rs rename to crates/env_filter/src/regex.rs index fb21528a..d56355af 100644 --- a/src/filter/regex.rs +++ b/crates/env_filter/src/regex.rs @@ -1,8 +1,6 @@ -extern crate regex; - use std::fmt; -use self::regex::Regex; +use regex::Regex; #[derive(Debug)] pub struct Filter { diff --git a/src/filter/string.rs b/crates/env_filter/src/string.rs similarity index 100% rename from src/filter/string.rs rename to crates/env_filter/src/string.rs diff --git a/src/lib.rs b/src/lib.rs index 93eba6ce..76cbf3a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -277,7 +277,8 @@ mod logger; -pub mod filter; +#[doc(inline)] +pub use ::env_filter as filter; pub mod fmt; pub use self::fmt::{Target, TimestampPrecision, WriteStyle}; From 57d938c7c75f07f927e8969a8013c65873f8fce0 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Jan 2024 11:28:41 -0600 Subject: [PATCH 40/53] refactor(filter): Clean up op mod --- crates/env_filter/src/lib.rs | 16 ++++--------- crates/env_filter/src/op.rs | 42 +++++++++++++++++++++++++++++++++ crates/env_filter/src/regex.rs | 27 --------------------- crates/env_filter/src/string.rs | 24 ------------------- 4 files changed, 47 insertions(+), 62 deletions(-) create mode 100644 crates/env_filter/src/op.rs delete mode 100644 crates/env_filter/src/regex.rs delete mode 100644 crates/env_filter/src/string.rs diff --git a/crates/env_filter/src/lib.rs b/crates/env_filter/src/lib.rs index 5d7d60b0..609654a4 100644 --- a/crates/env_filter/src/lib.rs +++ b/crates/env_filter/src/lib.rs @@ -55,13 +55,7 @@ use std::env; use std::fmt; use std::mem; -#[cfg(feature = "regex")] -#[path = "regex.rs"] -mod inner; - -#[cfg(not(feature = "regex"))] -#[path = "string.rs"] -mod inner; +mod op; /// A builder for a log filter. /// @@ -85,7 +79,7 @@ mod inner; /// ``` pub struct Builder { directives: Vec, - filter: Option, + filter: Option, built: bool, } @@ -226,7 +220,7 @@ struct Directive { /// [`Builder`]: struct.Builder.html pub struct Filter { directives: Vec, - filter: Option, + filter: Option, } impl Filter { @@ -289,7 +283,7 @@ impl fmt::Debug for Filter { /// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=error/foo") /// and return a vector with log directives. -fn parse_spec(spec: &str) -> (Vec, Option) { +fn parse_spec(spec: &str) -> (Vec, Option) { let mut dirs = Vec::new(); let mut parts = spec.split('/'); @@ -347,7 +341,7 @@ fn parse_spec(spec: &str) -> (Vec, Option) { } } - let filter = filter.and_then(|filter| match inner::Filter::new(filter) { + let filter = filter.and_then(|filter| match op::FilterOp::new(filter) { Ok(re) => Some(re), Err(e) => { eprintln!("warning: invalid regex filter - {}", e); diff --git a/crates/env_filter/src/op.rs b/crates/env_filter/src/op.rs new file mode 100644 index 00000000..e018e540 --- /dev/null +++ b/crates/env_filter/src/op.rs @@ -0,0 +1,42 @@ +use std::fmt; + +#[derive(Debug)] +pub struct FilterOp { + #[cfg(feature = "regex")] + inner: regex::Regex, + #[cfg(not(feature = "regex"))] + inner: String, +} + +#[cfg(feature = "regex")] +impl FilterOp { + pub fn new(spec: &str) -> Result { + match regex::Regex::new(spec) { + Ok(r) => Ok(Self { inner: r }), + Err(e) => Err(e.to_string()), + } + } + + pub fn is_match(&self, s: &str) -> bool { + self.inner.is_match(s) + } +} + +#[cfg(not(feature = "regex"))] +impl FilterOp { + pub fn new(spec: &str) -> Result { + Ok(Self { + inner: spec.to_string(), + }) + } + + pub fn is_match(&self, s: &str) -> bool { + s.contains(&self.inner) + } +} + +impl fmt::Display for FilterOp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} diff --git a/crates/env_filter/src/regex.rs b/crates/env_filter/src/regex.rs deleted file mode 100644 index d56355af..00000000 --- a/crates/env_filter/src/regex.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::fmt; - -use regex::Regex; - -#[derive(Debug)] -pub struct Filter { - inner: Regex, -} - -impl Filter { - pub fn new(spec: &str) -> Result { - match Regex::new(spec) { - Ok(r) => Ok(Filter { inner: r }), - Err(e) => Err(e.to_string()), - } - } - - pub fn is_match(&self, s: &str) -> bool { - self.inner.is_match(s) - } -} - -impl fmt::Display for Filter { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } -} diff --git a/crates/env_filter/src/string.rs b/crates/env_filter/src/string.rs deleted file mode 100644 index ea476e42..00000000 --- a/crates/env_filter/src/string.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::fmt; - -#[derive(Debug)] -pub struct Filter { - inner: String, -} - -impl Filter { - pub fn new(spec: &str) -> Result { - Ok(Filter { - inner: spec.to_string(), - }) - } - - pub fn is_match(&self, s: &str) -> bool { - s.contains(&self.inner) - } -} - -impl fmt::Display for Filter { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } -} From 3b45c6ffc11d0952dc67ebc9aadf8a990905d44b Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Jan 2024 11:32:01 -0600 Subject: [PATCH 41/53] refactor(filter): Pull out parser mod --- crates/env_filter/src/lib.rs | 255 +------------------------------ crates/env_filter/src/parser.rs | 261 ++++++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+), 252 deletions(-) create mode 100644 crates/env_filter/src/parser.rs diff --git a/crates/env_filter/src/lib.rs b/crates/env_filter/src/lib.rs index 609654a4..a90342e7 100644 --- a/crates/env_filter/src/lib.rs +++ b/crates/env_filter/src/lib.rs @@ -56,6 +56,7 @@ use std::fmt; use std::mem; mod op; +mod parser; /// A builder for a log filter. /// @@ -145,7 +146,7 @@ impl Builder { /// /// [Enabling Logging]: ../index.html#enabling-logging pub fn parse(&mut self, filters: &str) -> &mut Self { - let (directives, filter) = parse_spec(filters); + let (directives, filter) = parser::parse_spec(filters); self.filter = filter; @@ -281,77 +282,6 @@ impl fmt::Debug for Filter { } } -/// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=error/foo") -/// and return a vector with log directives. -fn parse_spec(spec: &str) -> (Vec, Option) { - let mut dirs = Vec::new(); - - let mut parts = spec.split('/'); - let mods = parts.next(); - let filter = parts.next(); - if parts.next().is_some() { - eprintln!( - "warning: invalid logging spec '{}', \ - ignoring it (too many '/'s)", - spec - ); - return (dirs, None); - } - if let Some(m) = mods { - for s in m.split(',').map(|ss| ss.trim()) { - if s.is_empty() { - continue; - } - let mut parts = s.split('='); - let (log_level, name) = - match (parts.next(), parts.next().map(|s| s.trim()), parts.next()) { - (Some(part0), None, None) => { - // if the single argument is a log-level string or number, - // treat that as a global fallback - match part0.parse() { - Ok(num) => (num, None), - Err(_) => (LevelFilter::max(), Some(part0)), - } - } - (Some(part0), Some(""), None) => (LevelFilter::max(), Some(part0)), - (Some(part0), Some(part1), None) => match part1.parse() { - Ok(num) => (num, Some(part0)), - _ => { - eprintln!( - "warning: invalid logging spec '{}', \ - ignoring it", - part1 - ); - continue; - } - }, - _ => { - eprintln!( - "warning: invalid logging spec '{}', \ - ignoring it", - s - ); - continue; - } - }; - dirs.push(Directive { - name: name.map(|s| s.to_string()), - level: log_level, - }); - } - } - - let filter = filter.and_then(|filter| match op::FilterOp::new(filter) { - Ok(re) => Some(re), - Err(e) => { - eprintln!("warning: invalid regex filter - {}", e); - None - } - }); - - (dirs, filter) -} - // Check whether a level and target are enabled by the set of directives. fn enabled(directives: &[Directive], level: Level, target: &str) -> bool { // Search for the longest match, the vector is assumed to be pre-sorted. @@ -368,7 +298,7 @@ fn enabled(directives: &[Directive], level: Level, target: &str) -> bool { mod tests { use log::{Level, LevelFilter}; - use super::{enabled, parse_spec, Builder, Directive, Filter}; + use super::{enabled, Builder, Directive, Filter}; fn make_logger_filter(dirs: Vec) -> Filter { let mut logger = Builder::new().build(); @@ -680,183 +610,4 @@ mod tests { assert!(!enabled(&logger.directives, Level::Error, "crate1::mod1")); assert!(enabled(&logger.directives, Level::Info, "crate2::mod2")); } - - #[test] - fn parse_spec_valid() { - let (dirs, filter) = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug"); - assert_eq!(dirs.len(), 3); - assert_eq!(dirs[0].name, Some("crate1::mod1".to_string())); - assert_eq!(dirs[0].level, LevelFilter::Error); - - assert_eq!(dirs[1].name, Some("crate1::mod2".to_string())); - assert_eq!(dirs[1].level, LevelFilter::max()); - - assert_eq!(dirs[2].name, Some("crate2".to_string())); - assert_eq!(dirs[2].level, LevelFilter::Debug); - assert!(filter.is_none()); - } - - #[test] - fn parse_spec_invalid_crate() { - // test parse_spec with multiple = in specification - let (dirs, filter) = parse_spec("crate1::mod1=warn=info,crate2=debug"); - assert_eq!(dirs.len(), 1); - assert_eq!(dirs[0].name, Some("crate2".to_string())); - assert_eq!(dirs[0].level, LevelFilter::Debug); - assert!(filter.is_none()); - } - - #[test] - fn parse_spec_invalid_level() { - // test parse_spec with 'noNumber' as log level - let (dirs, filter) = parse_spec("crate1::mod1=noNumber,crate2=debug"); - assert_eq!(dirs.len(), 1); - assert_eq!(dirs[0].name, Some("crate2".to_string())); - assert_eq!(dirs[0].level, LevelFilter::Debug); - assert!(filter.is_none()); - } - - #[test] - fn parse_spec_string_level() { - // test parse_spec with 'warn' as log level - let (dirs, filter) = parse_spec("crate1::mod1=wrong,crate2=warn"); - assert_eq!(dirs.len(), 1); - assert_eq!(dirs[0].name, Some("crate2".to_string())); - assert_eq!(dirs[0].level, LevelFilter::Warn); - assert!(filter.is_none()); - } - - #[test] - fn parse_spec_empty_level() { - // test parse_spec with '' as log level - let (dirs, filter) = parse_spec("crate1::mod1=wrong,crate2="); - assert_eq!(dirs.len(), 1); - assert_eq!(dirs[0].name, Some("crate2".to_string())); - assert_eq!(dirs[0].level, LevelFilter::max()); - assert!(filter.is_none()); - } - - #[test] - fn parse_spec_empty_level_isolated() { - // test parse_spec with "" as log level (and the entire spec str) - let (dirs, filter) = parse_spec(""); // should be ignored - assert_eq!(dirs.len(), 0); - assert!(filter.is_none()); - } - - #[test] - fn parse_spec_blank_level_isolated() { - // test parse_spec with a white-space-only string specified as the log - // level (and the entire spec str) - let (dirs, filter) = parse_spec(" "); // should be ignored - assert_eq!(dirs.len(), 0); - assert!(filter.is_none()); - } - - #[test] - fn parse_spec_blank_level_isolated_comma_only() { - // The spec should contain zero or more comma-separated string slices, - // so a comma-only string should be interpreted as two empty strings - // (which should both be treated as invalid, so ignored). - let (dirs, filter) = parse_spec(","); // should be ignored - assert_eq!(dirs.len(), 0); - assert!(filter.is_none()); - } - - #[test] - fn parse_spec_blank_level_isolated_comma_blank() { - // The spec should contain zero or more comma-separated string slices, - // so this bogus spec should be interpreted as containing one empty - // string and one blank string. Both should both be treated as - // invalid, so ignored. - let (dirs, filter) = parse_spec(", "); // should be ignored - assert_eq!(dirs.len(), 0); - assert!(filter.is_none()); - } - - #[test] - fn parse_spec_blank_level_isolated_blank_comma() { - // The spec should contain zero or more comma-separated string slices, - // so this bogus spec should be interpreted as containing one blank - // string and one empty string. Both should both be treated as - // invalid, so ignored. - let (dirs, filter) = parse_spec(" ,"); // should be ignored - assert_eq!(dirs.len(), 0); - assert!(filter.is_none()); - } - - #[test] - fn parse_spec_global() { - // test parse_spec with no crate - let (dirs, filter) = parse_spec("warn,crate2=debug"); - assert_eq!(dirs.len(), 2); - assert_eq!(dirs[0].name, None); - assert_eq!(dirs[0].level, LevelFilter::Warn); - assert_eq!(dirs[1].name, Some("crate2".to_string())); - assert_eq!(dirs[1].level, LevelFilter::Debug); - assert!(filter.is_none()); - } - - #[test] - fn parse_spec_global_bare_warn_lc() { - // test parse_spec with no crate, in isolation, all lowercase - let (dirs, filter) = parse_spec("warn"); - assert_eq!(dirs.len(), 1); - assert_eq!(dirs[0].name, None); - assert_eq!(dirs[0].level, LevelFilter::Warn); - assert!(filter.is_none()); - } - - #[test] - fn parse_spec_global_bare_warn_uc() { - // test parse_spec with no crate, in isolation, all uppercase - let (dirs, filter) = parse_spec("WARN"); - assert_eq!(dirs.len(), 1); - assert_eq!(dirs[0].name, None); - assert_eq!(dirs[0].level, LevelFilter::Warn); - assert!(filter.is_none()); - } - - #[test] - fn parse_spec_global_bare_warn_mixed() { - // test parse_spec with no crate, in isolation, mixed case - let (dirs, filter) = parse_spec("wArN"); - assert_eq!(dirs.len(), 1); - assert_eq!(dirs[0].name, None); - assert_eq!(dirs[0].level, LevelFilter::Warn); - assert!(filter.is_none()); - } - - #[test] - fn parse_spec_valid_filter() { - let (dirs, filter) = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug/abc"); - assert_eq!(dirs.len(), 3); - assert_eq!(dirs[0].name, Some("crate1::mod1".to_string())); - assert_eq!(dirs[0].level, LevelFilter::Error); - - assert_eq!(dirs[1].name, Some("crate1::mod2".to_string())); - assert_eq!(dirs[1].level, LevelFilter::max()); - - assert_eq!(dirs[2].name, Some("crate2".to_string())); - assert_eq!(dirs[2].level, LevelFilter::Debug); - assert!(filter.is_some() && filter.unwrap().to_string() == "abc"); - } - - #[test] - fn parse_spec_invalid_crate_filter() { - let (dirs, filter) = parse_spec("crate1::mod1=error=warn,crate2=debug/a.c"); - assert_eq!(dirs.len(), 1); - assert_eq!(dirs[0].name, Some("crate2".to_string())); - assert_eq!(dirs[0].level, LevelFilter::Debug); - assert!(filter.is_some() && filter.unwrap().to_string() == "a.c"); - } - - #[test] - fn parse_spec_empty_with_filter() { - let (dirs, filter) = parse_spec("crate1/a*c"); - assert_eq!(dirs.len(), 1); - assert_eq!(dirs[0].name, Some("crate1".to_string())); - assert_eq!(dirs[0].level, LevelFilter::max()); - assert!(filter.is_some() && filter.unwrap().to_string() == "a*c"); - } } diff --git a/crates/env_filter/src/parser.rs b/crates/env_filter/src/parser.rs new file mode 100644 index 00000000..d9158fab --- /dev/null +++ b/crates/env_filter/src/parser.rs @@ -0,0 +1,261 @@ +use log::LevelFilter; + +use crate::op; +use crate::Directive; + +/// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=error/foo") +/// and return a vector with log directives. +pub(crate) fn parse_spec(spec: &str) -> (Vec, Option) { + let mut dirs = Vec::new(); + + let mut parts = spec.split('/'); + let mods = parts.next(); + let filter = parts.next(); + if parts.next().is_some() { + eprintln!( + "warning: invalid logging spec '{}', \ + ignoring it (too many '/'s)", + spec + ); + return (dirs, None); + } + if let Some(m) = mods { + for s in m.split(',').map(|ss| ss.trim()) { + if s.is_empty() { + continue; + } + let mut parts = s.split('='); + let (log_level, name) = + match (parts.next(), parts.next().map(|s| s.trim()), parts.next()) { + (Some(part0), None, None) => { + // if the single argument is a log-level string or number, + // treat that as a global fallback + match part0.parse() { + Ok(num) => (num, None), + Err(_) => (LevelFilter::max(), Some(part0)), + } + } + (Some(part0), Some(""), None) => (LevelFilter::max(), Some(part0)), + (Some(part0), Some(part1), None) => match part1.parse() { + Ok(num) => (num, Some(part0)), + _ => { + eprintln!( + "warning: invalid logging spec '{}', \ + ignoring it", + part1 + ); + continue; + } + }, + _ => { + eprintln!( + "warning: invalid logging spec '{}', \ + ignoring it", + s + ); + continue; + } + }; + dirs.push(Directive { + name: name.map(|s| s.to_string()), + level: log_level, + }); + } + } + + let filter = filter.and_then(|filter| match op::FilterOp::new(filter) { + Ok(re) => Some(re), + Err(e) => { + eprintln!("warning: invalid regex filter - {}", e); + None + } + }); + + (dirs, filter) +} + +#[cfg(test)] +mod tests { + use log::LevelFilter; + + use super::parse_spec; + + #[test] + fn parse_spec_valid() { + let (dirs, filter) = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug"); + assert_eq!(dirs.len(), 3); + assert_eq!(dirs[0].name, Some("crate1::mod1".to_string())); + assert_eq!(dirs[0].level, LevelFilter::Error); + + assert_eq!(dirs[1].name, Some("crate1::mod2".to_string())); + assert_eq!(dirs[1].level, LevelFilter::max()); + + assert_eq!(dirs[2].name, Some("crate2".to_string())); + assert_eq!(dirs[2].level, LevelFilter::Debug); + assert!(filter.is_none()); + } + + #[test] + fn parse_spec_invalid_crate() { + // test parse_spec with multiple = in specification + let (dirs, filter) = parse_spec("crate1::mod1=warn=info,crate2=debug"); + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, Some("crate2".to_string())); + assert_eq!(dirs[0].level, LevelFilter::Debug); + assert!(filter.is_none()); + } + + #[test] + fn parse_spec_invalid_level() { + // test parse_spec with 'noNumber' as log level + let (dirs, filter) = parse_spec("crate1::mod1=noNumber,crate2=debug"); + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, Some("crate2".to_string())); + assert_eq!(dirs[0].level, LevelFilter::Debug); + assert!(filter.is_none()); + } + + #[test] + fn parse_spec_string_level() { + // test parse_spec with 'warn' as log level + let (dirs, filter) = parse_spec("crate1::mod1=wrong,crate2=warn"); + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, Some("crate2".to_string())); + assert_eq!(dirs[0].level, LevelFilter::Warn); + assert!(filter.is_none()); + } + + #[test] + fn parse_spec_empty_level() { + // test parse_spec with '' as log level + let (dirs, filter) = parse_spec("crate1::mod1=wrong,crate2="); + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, Some("crate2".to_string())); + assert_eq!(dirs[0].level, LevelFilter::max()); + assert!(filter.is_none()); + } + + #[test] + fn parse_spec_empty_level_isolated() { + // test parse_spec with "" as log level (and the entire spec str) + let (dirs, filter) = parse_spec(""); // should be ignored + assert_eq!(dirs.len(), 0); + assert!(filter.is_none()); + } + + #[test] + fn parse_spec_blank_level_isolated() { + // test parse_spec with a white-space-only string specified as the log + // level (and the entire spec str) + let (dirs, filter) = parse_spec(" "); // should be ignored + assert_eq!(dirs.len(), 0); + assert!(filter.is_none()); + } + + #[test] + fn parse_spec_blank_level_isolated_comma_only() { + // The spec should contain zero or more comma-separated string slices, + // so a comma-only string should be interpreted as two empty strings + // (which should both be treated as invalid, so ignored). + let (dirs, filter) = parse_spec(","); // should be ignored + assert_eq!(dirs.len(), 0); + assert!(filter.is_none()); + } + + #[test] + fn parse_spec_blank_level_isolated_comma_blank() { + // The spec should contain zero or more comma-separated string slices, + // so this bogus spec should be interpreted as containing one empty + // string and one blank string. Both should both be treated as + // invalid, so ignored. + let (dirs, filter) = parse_spec(", "); // should be ignored + assert_eq!(dirs.len(), 0); + assert!(filter.is_none()); + } + + #[test] + fn parse_spec_blank_level_isolated_blank_comma() { + // The spec should contain zero or more comma-separated string slices, + // so this bogus spec should be interpreted as containing one blank + // string and one empty string. Both should both be treated as + // invalid, so ignored. + let (dirs, filter) = parse_spec(" ,"); // should be ignored + assert_eq!(dirs.len(), 0); + assert!(filter.is_none()); + } + + #[test] + fn parse_spec_global() { + // test parse_spec with no crate + let (dirs, filter) = parse_spec("warn,crate2=debug"); + assert_eq!(dirs.len(), 2); + assert_eq!(dirs[0].name, None); + assert_eq!(dirs[0].level, LevelFilter::Warn); + assert_eq!(dirs[1].name, Some("crate2".to_string())); + assert_eq!(dirs[1].level, LevelFilter::Debug); + assert!(filter.is_none()); + } + + #[test] + fn parse_spec_global_bare_warn_lc() { + // test parse_spec with no crate, in isolation, all lowercase + let (dirs, filter) = parse_spec("warn"); + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, None); + assert_eq!(dirs[0].level, LevelFilter::Warn); + assert!(filter.is_none()); + } + + #[test] + fn parse_spec_global_bare_warn_uc() { + // test parse_spec with no crate, in isolation, all uppercase + let (dirs, filter) = parse_spec("WARN"); + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, None); + assert_eq!(dirs[0].level, LevelFilter::Warn); + assert!(filter.is_none()); + } + + #[test] + fn parse_spec_global_bare_warn_mixed() { + // test parse_spec with no crate, in isolation, mixed case + let (dirs, filter) = parse_spec("wArN"); + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, None); + assert_eq!(dirs[0].level, LevelFilter::Warn); + assert!(filter.is_none()); + } + + #[test] + fn parse_spec_valid_filter() { + let (dirs, filter) = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug/abc"); + assert_eq!(dirs.len(), 3); + assert_eq!(dirs[0].name, Some("crate1::mod1".to_string())); + assert_eq!(dirs[0].level, LevelFilter::Error); + + assert_eq!(dirs[1].name, Some("crate1::mod2".to_string())); + assert_eq!(dirs[1].level, LevelFilter::max()); + + assert_eq!(dirs[2].name, Some("crate2".to_string())); + assert_eq!(dirs[2].level, LevelFilter::Debug); + assert!(filter.is_some() && filter.unwrap().to_string() == "abc"); + } + + #[test] + fn parse_spec_invalid_crate_filter() { + let (dirs, filter) = parse_spec("crate1::mod1=error=warn,crate2=debug/a.c"); + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, Some("crate2".to_string())); + assert_eq!(dirs[0].level, LevelFilter::Debug); + assert!(filter.is_some() && filter.unwrap().to_string() == "a.c"); + } + + #[test] + fn parse_spec_empty_with_filter() { + let (dirs, filter) = parse_spec("crate1/a*c"); + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, Some("crate1".to_string())); + assert_eq!(dirs[0].level, LevelFilter::max()); + assert!(filter.is_some() && filter.unwrap().to_string() == "a*c"); + } +} From c769e03f40e03e83b972c334195014eddf8b2c9a Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Jan 2024 11:34:45 -0600 Subject: [PATCH 42/53] refactor(filter): Flatten the mod --- crates/env_filter/src/lib.rs | 16 ++++++++++------ crates/env_filter/src/parser.rs | 6 +++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/env_filter/src/lib.rs b/crates/env_filter/src/lib.rs index a90342e7..3d97d387 100644 --- a/crates/env_filter/src/lib.rs +++ b/crates/env_filter/src/lib.rs @@ -50,13 +50,17 @@ //! } //! ``` -use log::{Level, LevelFilter, Metadata, Record}; +mod op; +mod parser; + use std::env; use std::fmt; use std::mem; -mod op; -mod parser; +use log::{Level, LevelFilter, Metadata, Record}; + +use op::FilterOp; +use parser::parse_spec; /// A builder for a log filter. /// @@ -80,7 +84,7 @@ mod parser; /// ``` pub struct Builder { directives: Vec, - filter: Option, + filter: Option, built: bool, } @@ -146,7 +150,7 @@ impl Builder { /// /// [Enabling Logging]: ../index.html#enabling-logging pub fn parse(&mut self, filters: &str) -> &mut Self { - let (directives, filter) = parser::parse_spec(filters); + let (directives, filter) = parse_spec(filters); self.filter = filter; @@ -221,7 +225,7 @@ struct Directive { /// [`Builder`]: struct.Builder.html pub struct Filter { directives: Vec, - filter: Option, + filter: Option, } impl Filter { diff --git a/crates/env_filter/src/parser.rs b/crates/env_filter/src/parser.rs index d9158fab..2d0f90b0 100644 --- a/crates/env_filter/src/parser.rs +++ b/crates/env_filter/src/parser.rs @@ -1,11 +1,11 @@ use log::LevelFilter; -use crate::op; use crate::Directive; +use crate::FilterOp; /// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=error/foo") /// and return a vector with log directives. -pub(crate) fn parse_spec(spec: &str) -> (Vec, Option) { +pub(crate) fn parse_spec(spec: &str) -> (Vec, Option) { let mut dirs = Vec::new(); let mut parts = spec.split('/'); @@ -63,7 +63,7 @@ pub(crate) fn parse_spec(spec: &str) -> (Vec, Option) { } } - let filter = filter.and_then(|filter| match op::FilterOp::new(filter) { + let filter = filter.and_then(|filter| match FilterOp::new(filter) { Ok(re) => Some(re), Err(e) => { eprintln!("warning: invalid regex filter - {}", e); From 98c450f85b95779b60be37d847e176856305b6fd Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Jan 2024 11:36:37 -0600 Subject: [PATCH 43/53] refactor(filter): Pull out directive mod --- crates/env_filter/src/directive.rs | 20 ++++++++++++++++++++ crates/env_filter/src/lib.rs | 23 ++++------------------- 2 files changed, 24 insertions(+), 19 deletions(-) create mode 100644 crates/env_filter/src/directive.rs diff --git a/crates/env_filter/src/directive.rs b/crates/env_filter/src/directive.rs new file mode 100644 index 00000000..3721ef7f --- /dev/null +++ b/crates/env_filter/src/directive.rs @@ -0,0 +1,20 @@ +use log::Level; +use log::LevelFilter; + +#[derive(Debug)] +pub(crate) struct Directive { + pub(crate) name: Option, + pub(crate) level: LevelFilter, +} + +// Check whether a level and target are enabled by the set of directives. +pub(crate) fn enabled(directives: &[Directive], level: Level, target: &str) -> bool { + // Search for the longest match, the vector is assumed to be pre-sorted. + for directive in directives.iter().rev() { + match directive.name { + Some(ref name) if !target.starts_with(&**name) => {} + Some(..) | None => return level <= directive.level, + } + } + false +} diff --git a/crates/env_filter/src/lib.rs b/crates/env_filter/src/lib.rs index 3d97d387..41b71f8c 100644 --- a/crates/env_filter/src/lib.rs +++ b/crates/env_filter/src/lib.rs @@ -50,6 +50,7 @@ //! } //! ``` +mod directive; mod op; mod parser; @@ -57,8 +58,10 @@ use std::env; use std::fmt; use std::mem; -use log::{Level, LevelFilter, Metadata, Record}; +use log::{LevelFilter, Metadata, Record}; +use directive::enabled; +use directive::Directive; use op::FilterOp; use parser::parse_spec; @@ -210,12 +213,6 @@ impl fmt::Debug for Builder { } } -#[derive(Debug)] -struct Directive { - name: Option, - level: LevelFilter, -} - /// A log filter. /// /// This struct can be used to determine whether or not a log record @@ -286,18 +283,6 @@ impl fmt::Debug for Filter { } } -// Check whether a level and target are enabled by the set of directives. -fn enabled(directives: &[Directive], level: Level, target: &str) -> bool { - // Search for the longest match, the vector is assumed to be pre-sorted. - for directive in directives.iter().rev() { - match directive.name { - Some(ref name) if !target.starts_with(&**name) => {} - Some(..) | None => return level <= directive.level, - } - } - false -} - #[cfg(test)] mod tests { use log::{Level, LevelFilter}; From 841eba41feb44317facc586745b28707590d11fd Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Jan 2024 11:38:22 -0600 Subject: [PATCH 44/53] refactor(filter): Pull out filter mod --- crates/env_filter/src/filter.rs | 546 ++++++++++++++++++++++++++++++++ crates/env_filter/src/lib.rs | 544 +------------------------------ 2 files changed, 549 insertions(+), 541 deletions(-) create mode 100644 crates/env_filter/src/filter.rs diff --git a/crates/env_filter/src/filter.rs b/crates/env_filter/src/filter.rs new file mode 100644 index 00000000..7a052720 --- /dev/null +++ b/crates/env_filter/src/filter.rs @@ -0,0 +1,546 @@ +use std::env; +use std::fmt; +use std::mem; + +use log::{LevelFilter, Metadata, Record}; + +use crate::enabled; +use crate::Directive; +use crate::FilterOp; +use crate::parse_spec; + +/// A builder for a log filter. +/// +/// It can be used to parse a set of directives from a string before building +/// a [`Filter`] instance. +/// +/// ## Example +/// +/// ``` +/// # use std::env; +/// use env_filter::Builder; +/// +/// let mut builder = Builder::new(); +/// +/// // Parse a logging filter from an environment variable. +/// if let Ok(rust_log) = env::var("RUST_LOG") { +/// builder.parse(&rust_log); +/// } +/// +/// let filter = builder.build(); +/// ``` +pub struct Builder { + directives: Vec, + filter: Option, + built: bool, +} + +impl Builder { + /// Initializes the filter builder with defaults. + pub fn new() -> Builder { + Builder { + directives: Vec::new(), + filter: None, + built: false, + } + } + + /// Initializes the filter builder from an environment. + pub fn from_env(env: &str) -> Builder { + let mut builder = Builder::new(); + + if let Ok(s) = env::var(env) { + builder.parse(&s); + } + + builder + } + + /// Insert the directive replacing any directive with the same name. + fn insert_directive(&mut self, mut directive: Directive) { + if let Some(pos) = self + .directives + .iter() + .position(|d| d.name == directive.name) + { + mem::swap(&mut self.directives[pos], &mut directive); + } else { + self.directives.push(directive); + } + } + + /// Adds a directive to the filter for a specific module. + pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self { + self.filter(Some(module), level) + } + + /// Adds a directive to the filter for all modules. + pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self { + self.filter(None, level) + } + + /// Adds a directive to the filter. + /// + /// The given module (if any) will log at most the specified level provided. + /// If no module is provided then the filter will apply to all log messages. + pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self { + self.insert_directive(Directive { + name: module.map(|s| s.to_string()), + level, + }); + self + } + + /// Parses the directives string. + /// + /// See the [Enabling Logging] section for more details. + /// + /// [Enabling Logging]: ../index.html#enabling-logging + pub fn parse(&mut self, filters: &str) -> &mut Self { + let (directives, filter) = parse_spec(filters); + + self.filter = filter; + + for directive in directives { + self.insert_directive(directive); + } + self + } + + /// Build a log filter. + pub fn build(&mut self) -> Filter { + assert!(!self.built, "attempt to re-use consumed builder"); + self.built = true; + + let mut directives = Vec::new(); + if self.directives.is_empty() { + // Adds the default filter if none exist + directives.push(Directive { + name: None, + level: LevelFilter::Error, + }); + } else { + // Consume directives. + directives = mem::take(&mut self.directives); + // Sort the directives by length of their name, this allows a + // little more efficient lookup at runtime. + directives.sort_by(|a, b| { + let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0); + let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0); + alen.cmp(&blen) + }); + } + + Filter { + directives: mem::take(&mut directives), + filter: mem::take(&mut self.filter), + } + } +} + +impl Default for Builder { + fn default() -> Self { + Builder::new() + } +} + +impl fmt::Debug for Builder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.built { + f.debug_struct("Filter").field("built", &true).finish() + } else { + f.debug_struct("Filter") + .field("filter", &self.filter) + .field("directives", &self.directives) + .finish() + } + } +} + +/// A log filter. +/// +/// This struct can be used to determine whether or not a log record +/// should be written to the output. +/// Use the [`Builder`] type to parse and construct a `Filter`. +/// +/// [`Builder`]: struct.Builder.html +pub struct Filter { + directives: Vec, + filter: Option, +} + +impl Filter { + /// Returns the maximum `LevelFilter` that this filter instance is + /// configured to output. + /// + /// # Example + /// + /// ```rust + /// use log::LevelFilter; + /// use env_filter::Builder; + /// + /// let mut builder = Builder::new(); + /// builder.filter(Some("module1"), LevelFilter::Info); + /// builder.filter(Some("module2"), LevelFilter::Error); + /// + /// let filter = builder.build(); + /// assert_eq!(filter.filter(), LevelFilter::Info); + /// ``` + pub fn filter(&self) -> LevelFilter { + self.directives + .iter() + .map(|d| d.level) + .max() + .unwrap_or(LevelFilter::Off) + } + + /// Checks if this record matches the configured filter. + pub fn matches(&self, record: &Record) -> bool { + if !self.enabled(record.metadata()) { + return false; + } + + if let Some(filter) = self.filter.as_ref() { + if !filter.is_match(&record.args().to_string()) { + return false; + } + } + + true + } + + /// Determines if a log message with the specified metadata would be logged. + pub fn enabled(&self, metadata: &Metadata) -> bool { + let level = metadata.level(); + let target = metadata.target(); + + enabled(&self.directives, level, target) + } +} + +impl fmt::Debug for Filter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Filter") + .field("filter", &self.filter) + .field("directives", &self.directives) + .finish() + } +} + +#[cfg(test)] +mod tests { + use log::{Level, LevelFilter}; + + use super::{enabled, Builder, Directive, Filter}; + + fn make_logger_filter(dirs: Vec) -> Filter { + let mut logger = Builder::new().build(); + logger.directives = dirs; + logger + } + + #[test] + fn filter_info() { + let logger = Builder::new().filter(None, LevelFilter::Info).build(); + assert!(enabled(&logger.directives, Level::Info, "crate1")); + assert!(!enabled(&logger.directives, Level::Debug, "crate1")); + } + + #[test] + fn filter_beginning_longest_match() { + let logger = Builder::new() + .filter(Some("crate2"), LevelFilter::Info) + .filter(Some("crate2::mod"), LevelFilter::Debug) + .filter(Some("crate1::mod1"), LevelFilter::Warn) + .build(); + assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1")); + assert!(!enabled(&logger.directives, Level::Debug, "crate2")); + } + + // Some of our tests are only correct or complete when they cover the full + // universe of variants for log::Level. In the unlikely event that a new + // variant is added in the future, this test will detect the scenario and + // alert us to the need to review and update the tests. In such a + // situation, this test will fail to compile, and the error message will + // look something like this: + // + // error[E0004]: non-exhaustive patterns: `NewVariant` not covered + // --> src/filter/mod.rs:413:15 + // | + // 413 | match level_universe { + // | ^^^^^^^^^^^^^^ pattern `NewVariant` not covered + #[test] + fn ensure_tests_cover_level_universe() { + let level_universe: Level = Level::Trace; // use of trace variant is arbitrary + match level_universe { + Level::Error | Level::Warn | Level::Info | Level::Debug | Level::Trace => (), + } + } + + #[test] + fn parse_default() { + let logger = Builder::new().parse("info,crate1::mod1=warn").build(); + assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1")); + assert!(enabled(&logger.directives, Level::Info, "crate2::mod2")); + } + + #[test] + fn parse_default_bare_level_off_lc() { + let logger = Builder::new().parse("off").build(); + assert!(!enabled(&logger.directives, Level::Error, "")); + assert!(!enabled(&logger.directives, Level::Warn, "")); + assert!(!enabled(&logger.directives, Level::Info, "")); + assert!(!enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_off_uc() { + let logger = Builder::new().parse("OFF").build(); + assert!(!enabled(&logger.directives, Level::Error, "")); + assert!(!enabled(&logger.directives, Level::Warn, "")); + assert!(!enabled(&logger.directives, Level::Info, "")); + assert!(!enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_error_lc() { + let logger = Builder::new().parse("error").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(!enabled(&logger.directives, Level::Warn, "")); + assert!(!enabled(&logger.directives, Level::Info, "")); + assert!(!enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_error_uc() { + let logger = Builder::new().parse("ERROR").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(!enabled(&logger.directives, Level::Warn, "")); + assert!(!enabled(&logger.directives, Level::Info, "")); + assert!(!enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_warn_lc() { + let logger = Builder::new().parse("warn").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(!enabled(&logger.directives, Level::Info, "")); + assert!(!enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_warn_uc() { + let logger = Builder::new().parse("WARN").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(!enabled(&logger.directives, Level::Info, "")); + assert!(!enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_info_lc() { + let logger = Builder::new().parse("info").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(!enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_info_uc() { + let logger = Builder::new().parse("INFO").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(!enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_debug_lc() { + let logger = Builder::new().parse("debug").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_debug_uc() { + let logger = Builder::new().parse("DEBUG").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_trace_lc() { + let logger = Builder::new().parse("trace").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(enabled(&logger.directives, Level::Debug, "")); + assert!(enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_trace_uc() { + let logger = Builder::new().parse("TRACE").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(enabled(&logger.directives, Level::Debug, "")); + assert!(enabled(&logger.directives, Level::Trace, "")); + } + + // In practice, the desired log level is typically specified by a token + // that is either all lowercase (e.g., 'trace') or all uppercase (.e.g, + // 'TRACE'), but this tests serves as a reminder that + // log::Level::from_str() ignores all case variants. + #[test] + fn parse_default_bare_level_debug_mixed() { + { + let logger = Builder::new().parse("Debug").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + { + let logger = Builder::new().parse("debuG").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + { + let logger = Builder::new().parse("deBug").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + { + let logger = Builder::new().parse("DeBuG").build(); // LaTeX flavor! + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + } + + #[test] + fn match_full_path() { + let logger = make_logger_filter(vec![ + Directive { + name: Some("crate2".to_string()), + level: LevelFilter::Info, + }, + Directive { + name: Some("crate1::mod1".to_string()), + level: LevelFilter::Warn, + }, + ]); + assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1")); + assert!(!enabled(&logger.directives, Level::Info, "crate1::mod1")); + assert!(enabled(&logger.directives, Level::Info, "crate2")); + assert!(!enabled(&logger.directives, Level::Debug, "crate2")); + } + + #[test] + fn no_match() { + let logger = make_logger_filter(vec![ + Directive { + name: Some("crate2".to_string()), + level: LevelFilter::Info, + }, + Directive { + name: Some("crate1::mod1".to_string()), + level: LevelFilter::Warn, + }, + ]); + assert!(!enabled(&logger.directives, Level::Warn, "crate3")); + } + + #[test] + fn match_beginning() { + let logger = make_logger_filter(vec![ + Directive { + name: Some("crate2".to_string()), + level: LevelFilter::Info, + }, + Directive { + name: Some("crate1::mod1".to_string()), + level: LevelFilter::Warn, + }, + ]); + assert!(enabled(&logger.directives, Level::Info, "crate2::mod1")); + } + + #[test] + fn match_beginning_longest_match() { + let logger = make_logger_filter(vec![ + Directive { + name: Some("crate2".to_string()), + level: LevelFilter::Info, + }, + Directive { + name: Some("crate2::mod".to_string()), + level: LevelFilter::Debug, + }, + Directive { + name: Some("crate1::mod1".to_string()), + level: LevelFilter::Warn, + }, + ]); + assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1")); + assert!(!enabled(&logger.directives, Level::Debug, "crate2")); + } + + #[test] + fn match_default() { + let logger = make_logger_filter(vec![ + Directive { + name: None, + level: LevelFilter::Info, + }, + Directive { + name: Some("crate1::mod1".to_string()), + level: LevelFilter::Warn, + }, + ]); + assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1")); + assert!(enabled(&logger.directives, Level::Info, "crate2::mod2")); + } + + #[test] + fn zero_level() { + let logger = make_logger_filter(vec![ + Directive { + name: None, + level: LevelFilter::Info, + }, + Directive { + name: Some("crate1::mod1".to_string()), + level: LevelFilter::Off, + }, + ]); + assert!(!enabled(&logger.directives, Level::Error, "crate1::mod1")); + assert!(enabled(&logger.directives, Level::Info, "crate2::mod2")); + } +} diff --git a/crates/env_filter/src/lib.rs b/crates/env_filter/src/lib.rs index 41b71f8c..494b82da 100644 --- a/crates/env_filter/src/lib.rs +++ b/crates/env_filter/src/lib.rs @@ -51,552 +51,14 @@ //! ``` mod directive; +mod filter; mod op; mod parser; -use std::env; -use std::fmt; -use std::mem; - -use log::{LevelFilter, Metadata, Record}; - use directive::enabled; use directive::Directive; use op::FilterOp; use parser::parse_spec; -/// A builder for a log filter. -/// -/// It can be used to parse a set of directives from a string before building -/// a [`Filter`] instance. -/// -/// ## Example -/// -/// ``` -/// # use std::env; -/// use env_filter::Builder; -/// -/// let mut builder = Builder::new(); -/// -/// // Parse a logging filter from an environment variable. -/// if let Ok(rust_log) = env::var("RUST_LOG") { -/// builder.parse(&rust_log); -/// } -/// -/// let filter = builder.build(); -/// ``` -pub struct Builder { - directives: Vec, - filter: Option, - built: bool, -} - -impl Builder { - /// Initializes the filter builder with defaults. - pub fn new() -> Builder { - Builder { - directives: Vec::new(), - filter: None, - built: false, - } - } - - /// Initializes the filter builder from an environment. - pub fn from_env(env: &str) -> Builder { - let mut builder = Builder::new(); - - if let Ok(s) = env::var(env) { - builder.parse(&s); - } - - builder - } - - /// Insert the directive replacing any directive with the same name. - fn insert_directive(&mut self, mut directive: Directive) { - if let Some(pos) = self - .directives - .iter() - .position(|d| d.name == directive.name) - { - mem::swap(&mut self.directives[pos], &mut directive); - } else { - self.directives.push(directive); - } - } - - /// Adds a directive to the filter for a specific module. - pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self { - self.filter(Some(module), level) - } - - /// Adds a directive to the filter for all modules. - pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self { - self.filter(None, level) - } - - /// Adds a directive to the filter. - /// - /// The given module (if any) will log at most the specified level provided. - /// If no module is provided then the filter will apply to all log messages. - pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self { - self.insert_directive(Directive { - name: module.map(|s| s.to_string()), - level, - }); - self - } - - /// Parses the directives string. - /// - /// See the [Enabling Logging] section for more details. - /// - /// [Enabling Logging]: ../index.html#enabling-logging - pub fn parse(&mut self, filters: &str) -> &mut Self { - let (directives, filter) = parse_spec(filters); - - self.filter = filter; - - for directive in directives { - self.insert_directive(directive); - } - self - } - - /// Build a log filter. - pub fn build(&mut self) -> Filter { - assert!(!self.built, "attempt to re-use consumed builder"); - self.built = true; - - let mut directives = Vec::new(); - if self.directives.is_empty() { - // Adds the default filter if none exist - directives.push(Directive { - name: None, - level: LevelFilter::Error, - }); - } else { - // Consume directives. - directives = mem::take(&mut self.directives); - // Sort the directives by length of their name, this allows a - // little more efficient lookup at runtime. - directives.sort_by(|a, b| { - let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0); - let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0); - alen.cmp(&blen) - }); - } - - Filter { - directives: mem::take(&mut directives), - filter: mem::take(&mut self.filter), - } - } -} - -impl Default for Builder { - fn default() -> Self { - Builder::new() - } -} - -impl fmt::Debug for Builder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.built { - f.debug_struct("Filter").field("built", &true).finish() - } else { - f.debug_struct("Filter") - .field("filter", &self.filter) - .field("directives", &self.directives) - .finish() - } - } -} - -/// A log filter. -/// -/// This struct can be used to determine whether or not a log record -/// should be written to the output. -/// Use the [`Builder`] type to parse and construct a `Filter`. -/// -/// [`Builder`]: struct.Builder.html -pub struct Filter { - directives: Vec, - filter: Option, -} - -impl Filter { - /// Returns the maximum `LevelFilter` that this filter instance is - /// configured to output. - /// - /// # Example - /// - /// ```rust - /// use log::LevelFilter; - /// use env_filter::Builder; - /// - /// let mut builder = Builder::new(); - /// builder.filter(Some("module1"), LevelFilter::Info); - /// builder.filter(Some("module2"), LevelFilter::Error); - /// - /// let filter = builder.build(); - /// assert_eq!(filter.filter(), LevelFilter::Info); - /// ``` - pub fn filter(&self) -> LevelFilter { - self.directives - .iter() - .map(|d| d.level) - .max() - .unwrap_or(LevelFilter::Off) - } - - /// Checks if this record matches the configured filter. - pub fn matches(&self, record: &Record) -> bool { - if !self.enabled(record.metadata()) { - return false; - } - - if let Some(filter) = self.filter.as_ref() { - if !filter.is_match(&record.args().to_string()) { - return false; - } - } - - true - } - - /// Determines if a log message with the specified metadata would be logged. - pub fn enabled(&self, metadata: &Metadata) -> bool { - let level = metadata.level(); - let target = metadata.target(); - - enabled(&self.directives, level, target) - } -} - -impl fmt::Debug for Filter { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Filter") - .field("filter", &self.filter) - .field("directives", &self.directives) - .finish() - } -} - -#[cfg(test)] -mod tests { - use log::{Level, LevelFilter}; - - use super::{enabled, Builder, Directive, Filter}; - - fn make_logger_filter(dirs: Vec) -> Filter { - let mut logger = Builder::new().build(); - logger.directives = dirs; - logger - } - - #[test] - fn filter_info() { - let logger = Builder::new().filter(None, LevelFilter::Info).build(); - assert!(enabled(&logger.directives, Level::Info, "crate1")); - assert!(!enabled(&logger.directives, Level::Debug, "crate1")); - } - - #[test] - fn filter_beginning_longest_match() { - let logger = Builder::new() - .filter(Some("crate2"), LevelFilter::Info) - .filter(Some("crate2::mod"), LevelFilter::Debug) - .filter(Some("crate1::mod1"), LevelFilter::Warn) - .build(); - assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1")); - assert!(!enabled(&logger.directives, Level::Debug, "crate2")); - } - - // Some of our tests are only correct or complete when they cover the full - // universe of variants for log::Level. In the unlikely event that a new - // variant is added in the future, this test will detect the scenario and - // alert us to the need to review and update the tests. In such a - // situation, this test will fail to compile, and the error message will - // look something like this: - // - // error[E0004]: non-exhaustive patterns: `NewVariant` not covered - // --> src/filter/mod.rs:413:15 - // | - // 413 | match level_universe { - // | ^^^^^^^^^^^^^^ pattern `NewVariant` not covered - #[test] - fn ensure_tests_cover_level_universe() { - let level_universe: Level = Level::Trace; // use of trace variant is arbitrary - match level_universe { - Level::Error | Level::Warn | Level::Info | Level::Debug | Level::Trace => (), - } - } - - #[test] - fn parse_default() { - let logger = Builder::new().parse("info,crate1::mod1=warn").build(); - assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1")); - assert!(enabled(&logger.directives, Level::Info, "crate2::mod2")); - } - - #[test] - fn parse_default_bare_level_off_lc() { - let logger = Builder::new().parse("off").build(); - assert!(!enabled(&logger.directives, Level::Error, "")); - assert!(!enabled(&logger.directives, Level::Warn, "")); - assert!(!enabled(&logger.directives, Level::Info, "")); - assert!(!enabled(&logger.directives, Level::Debug, "")); - assert!(!enabled(&logger.directives, Level::Trace, "")); - } - - #[test] - fn parse_default_bare_level_off_uc() { - let logger = Builder::new().parse("OFF").build(); - assert!(!enabled(&logger.directives, Level::Error, "")); - assert!(!enabled(&logger.directives, Level::Warn, "")); - assert!(!enabled(&logger.directives, Level::Info, "")); - assert!(!enabled(&logger.directives, Level::Debug, "")); - assert!(!enabled(&logger.directives, Level::Trace, "")); - } - - #[test] - fn parse_default_bare_level_error_lc() { - let logger = Builder::new().parse("error").build(); - assert!(enabled(&logger.directives, Level::Error, "")); - assert!(!enabled(&logger.directives, Level::Warn, "")); - assert!(!enabled(&logger.directives, Level::Info, "")); - assert!(!enabled(&logger.directives, Level::Debug, "")); - assert!(!enabled(&logger.directives, Level::Trace, "")); - } - - #[test] - fn parse_default_bare_level_error_uc() { - let logger = Builder::new().parse("ERROR").build(); - assert!(enabled(&logger.directives, Level::Error, "")); - assert!(!enabled(&logger.directives, Level::Warn, "")); - assert!(!enabled(&logger.directives, Level::Info, "")); - assert!(!enabled(&logger.directives, Level::Debug, "")); - assert!(!enabled(&logger.directives, Level::Trace, "")); - } - - #[test] - fn parse_default_bare_level_warn_lc() { - let logger = Builder::new().parse("warn").build(); - assert!(enabled(&logger.directives, Level::Error, "")); - assert!(enabled(&logger.directives, Level::Warn, "")); - assert!(!enabled(&logger.directives, Level::Info, "")); - assert!(!enabled(&logger.directives, Level::Debug, "")); - assert!(!enabled(&logger.directives, Level::Trace, "")); - } - - #[test] - fn parse_default_bare_level_warn_uc() { - let logger = Builder::new().parse("WARN").build(); - assert!(enabled(&logger.directives, Level::Error, "")); - assert!(enabled(&logger.directives, Level::Warn, "")); - assert!(!enabled(&logger.directives, Level::Info, "")); - assert!(!enabled(&logger.directives, Level::Debug, "")); - assert!(!enabled(&logger.directives, Level::Trace, "")); - } - - #[test] - fn parse_default_bare_level_info_lc() { - let logger = Builder::new().parse("info").build(); - assert!(enabled(&logger.directives, Level::Error, "")); - assert!(enabled(&logger.directives, Level::Warn, "")); - assert!(enabled(&logger.directives, Level::Info, "")); - assert!(!enabled(&logger.directives, Level::Debug, "")); - assert!(!enabled(&logger.directives, Level::Trace, "")); - } - - #[test] - fn parse_default_bare_level_info_uc() { - let logger = Builder::new().parse("INFO").build(); - assert!(enabled(&logger.directives, Level::Error, "")); - assert!(enabled(&logger.directives, Level::Warn, "")); - assert!(enabled(&logger.directives, Level::Info, "")); - assert!(!enabled(&logger.directives, Level::Debug, "")); - assert!(!enabled(&logger.directives, Level::Trace, "")); - } - - #[test] - fn parse_default_bare_level_debug_lc() { - let logger = Builder::new().parse("debug").build(); - assert!(enabled(&logger.directives, Level::Error, "")); - assert!(enabled(&logger.directives, Level::Warn, "")); - assert!(enabled(&logger.directives, Level::Info, "")); - assert!(enabled(&logger.directives, Level::Debug, "")); - assert!(!enabled(&logger.directives, Level::Trace, "")); - } - - #[test] - fn parse_default_bare_level_debug_uc() { - let logger = Builder::new().parse("DEBUG").build(); - assert!(enabled(&logger.directives, Level::Error, "")); - assert!(enabled(&logger.directives, Level::Warn, "")); - assert!(enabled(&logger.directives, Level::Info, "")); - assert!(enabled(&logger.directives, Level::Debug, "")); - assert!(!enabled(&logger.directives, Level::Trace, "")); - } - - #[test] - fn parse_default_bare_level_trace_lc() { - let logger = Builder::new().parse("trace").build(); - assert!(enabled(&logger.directives, Level::Error, "")); - assert!(enabled(&logger.directives, Level::Warn, "")); - assert!(enabled(&logger.directives, Level::Info, "")); - assert!(enabled(&logger.directives, Level::Debug, "")); - assert!(enabled(&logger.directives, Level::Trace, "")); - } - - #[test] - fn parse_default_bare_level_trace_uc() { - let logger = Builder::new().parse("TRACE").build(); - assert!(enabled(&logger.directives, Level::Error, "")); - assert!(enabled(&logger.directives, Level::Warn, "")); - assert!(enabled(&logger.directives, Level::Info, "")); - assert!(enabled(&logger.directives, Level::Debug, "")); - assert!(enabled(&logger.directives, Level::Trace, "")); - } - - // In practice, the desired log level is typically specified by a token - // that is either all lowercase (e.g., 'trace') or all uppercase (.e.g, - // 'TRACE'), but this tests serves as a reminder that - // log::Level::from_str() ignores all case variants. - #[test] - fn parse_default_bare_level_debug_mixed() { - { - let logger = Builder::new().parse("Debug").build(); - assert!(enabled(&logger.directives, Level::Error, "")); - assert!(enabled(&logger.directives, Level::Warn, "")); - assert!(enabled(&logger.directives, Level::Info, "")); - assert!(enabled(&logger.directives, Level::Debug, "")); - assert!(!enabled(&logger.directives, Level::Trace, "")); - } - { - let logger = Builder::new().parse("debuG").build(); - assert!(enabled(&logger.directives, Level::Error, "")); - assert!(enabled(&logger.directives, Level::Warn, "")); - assert!(enabled(&logger.directives, Level::Info, "")); - assert!(enabled(&logger.directives, Level::Debug, "")); - assert!(!enabled(&logger.directives, Level::Trace, "")); - } - { - let logger = Builder::new().parse("deBug").build(); - assert!(enabled(&logger.directives, Level::Error, "")); - assert!(enabled(&logger.directives, Level::Warn, "")); - assert!(enabled(&logger.directives, Level::Info, "")); - assert!(enabled(&logger.directives, Level::Debug, "")); - assert!(!enabled(&logger.directives, Level::Trace, "")); - } - { - let logger = Builder::new().parse("DeBuG").build(); // LaTeX flavor! - assert!(enabled(&logger.directives, Level::Error, "")); - assert!(enabled(&logger.directives, Level::Warn, "")); - assert!(enabled(&logger.directives, Level::Info, "")); - assert!(enabled(&logger.directives, Level::Debug, "")); - assert!(!enabled(&logger.directives, Level::Trace, "")); - } - } - - #[test] - fn match_full_path() { - let logger = make_logger_filter(vec![ - Directive { - name: Some("crate2".to_string()), - level: LevelFilter::Info, - }, - Directive { - name: Some("crate1::mod1".to_string()), - level: LevelFilter::Warn, - }, - ]); - assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1")); - assert!(!enabled(&logger.directives, Level::Info, "crate1::mod1")); - assert!(enabled(&logger.directives, Level::Info, "crate2")); - assert!(!enabled(&logger.directives, Level::Debug, "crate2")); - } - - #[test] - fn no_match() { - let logger = make_logger_filter(vec![ - Directive { - name: Some("crate2".to_string()), - level: LevelFilter::Info, - }, - Directive { - name: Some("crate1::mod1".to_string()), - level: LevelFilter::Warn, - }, - ]); - assert!(!enabled(&logger.directives, Level::Warn, "crate3")); - } - - #[test] - fn match_beginning() { - let logger = make_logger_filter(vec![ - Directive { - name: Some("crate2".to_string()), - level: LevelFilter::Info, - }, - Directive { - name: Some("crate1::mod1".to_string()), - level: LevelFilter::Warn, - }, - ]); - assert!(enabled(&logger.directives, Level::Info, "crate2::mod1")); - } - - #[test] - fn match_beginning_longest_match() { - let logger = make_logger_filter(vec![ - Directive { - name: Some("crate2".to_string()), - level: LevelFilter::Info, - }, - Directive { - name: Some("crate2::mod".to_string()), - level: LevelFilter::Debug, - }, - Directive { - name: Some("crate1::mod1".to_string()), - level: LevelFilter::Warn, - }, - ]); - assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1")); - assert!(!enabled(&logger.directives, Level::Debug, "crate2")); - } - - #[test] - fn match_default() { - let logger = make_logger_filter(vec![ - Directive { - name: None, - level: LevelFilter::Info, - }, - Directive { - name: Some("crate1::mod1".to_string()), - level: LevelFilter::Warn, - }, - ]); - assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1")); - assert!(enabled(&logger.directives, Level::Info, "crate2::mod2")); - } - - #[test] - fn zero_level() { - let logger = make_logger_filter(vec![ - Directive { - name: None, - level: LevelFilter::Info, - }, - Directive { - name: Some("crate1::mod1".to_string()), - level: LevelFilter::Off, - }, - ]); - assert!(!enabled(&logger.directives, Level::Error, "crate1::mod1")); - assert!(enabled(&logger.directives, Level::Info, "crate2::mod2")); - } -} +pub use filter::Builder; +pub use filter::Filter; From e6e2b633688a56a53ad718b3b498243cb3893d52 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Jan 2024 11:44:39 -0600 Subject: [PATCH 45/53] fix(log)!: Dont re-export env_filter This lets us shrink our public API --- examples/custom_logger.rs | 59 --------------------------------------- src/lib.rs | 2 -- src/logger.rs | 6 ++-- 3 files changed, 2 insertions(+), 65 deletions(-) delete mode 100644 examples/custom_logger.rs diff --git a/examples/custom_logger.rs b/examples/custom_logger.rs deleted file mode 100644 index e924faee..00000000 --- a/examples/custom_logger.rs +++ /dev/null @@ -1,59 +0,0 @@ -/*! -Using `env_logger` to drive a custom logger. - -Before running this example, try setting the `MY_LOG_LEVEL` environment variable to `info`: - -```no_run,shell -$ export MY_LOG_LEVEL='info' -``` - -If you only want to change the way logs are formatted, look at the `custom_format` example. -*/ - -use env_logger::filter::{Builder, Filter}; - -use log::{info, Log, Metadata, Record, SetLoggerError}; - -const FILTER_ENV: &str = "MY_LOG_LEVEL"; - -struct MyLogger { - inner: Filter, -} - -impl MyLogger { - fn new() -> MyLogger { - let mut builder = Builder::from_env(FILTER_ENV); - - MyLogger { - inner: builder.build(), - } - } - - fn init() -> Result<(), SetLoggerError> { - let logger = Self::new(); - - log::set_max_level(logger.inner.filter()); - log::set_boxed_logger(Box::new(logger)) - } -} - -impl Log for MyLogger { - fn enabled(&self, metadata: &Metadata) -> bool { - self.inner.enabled(metadata) - } - - fn log(&self, record: &Record) { - // Check if the record is matched by the logger before logging - if self.inner.matches(record) { - println!("{} - {}", record.level(), record.args()); - } - } - - fn flush(&self) {} -} - -fn main() { - MyLogger::init().unwrap(); - - info!("a log from `MyLogger`"); -} diff --git a/src/lib.rs b/src/lib.rs index 76cbf3a5..26f25b85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -277,8 +277,6 @@ mod logger; -#[doc(inline)] -pub use ::env_filter as filter; pub mod fmt; pub use self::fmt::{Target, TimestampPrecision, WriteStyle}; diff --git a/src/logger.rs b/src/logger.rs index 1e7d5894..18bfe9fa 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -2,8 +2,6 @@ use std::{borrow::Cow, cell::RefCell, env, io}; use log::{LevelFilter, Log, Metadata, Record, SetLoggerError}; -use crate::filter; -use crate::filter::Filter; use crate::fmt; use crate::fmt::writer::{self, Writer}; use crate::fmt::{FormatFn, Formatter}; @@ -38,7 +36,7 @@ pub const DEFAULT_WRITE_STYLE_ENV: &str = "RUST_LOG_STYLE"; /// ``` #[derive(Default)] pub struct Builder { - filter: filter::Builder, + filter: env_filter::Builder, writer: writer::Builder, format: fmt::Builder, built: bool, @@ -532,7 +530,7 @@ impl std::fmt::Debug for Builder { /// [`Builder`]: struct.Builder.html pub struct Logger { writer: Writer, - filter: Filter, + filter: env_filter::Filter, format: FormatFn, } From 2d3526001061bacbf4a4c47767a318986c2c61b0 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Jan 2024 11:48:20 -0600 Subject: [PATCH 46/53] feat(filter): Add a Logger decorator --- crates/env_filter/src/filtered_log.rs | 45 +++++++++++++++++++++++++++ crates/env_filter/src/lib.rs | 39 +++++++++-------------- 2 files changed, 59 insertions(+), 25 deletions(-) create mode 100644 crates/env_filter/src/filtered_log.rs diff --git a/crates/env_filter/src/filtered_log.rs b/crates/env_filter/src/filtered_log.rs new file mode 100644 index 00000000..f707bcc3 --- /dev/null +++ b/crates/env_filter/src/filtered_log.rs @@ -0,0 +1,45 @@ +use log::Log; + +use crate::Filter; + +/// Decorate a [`log::Log`] with record [`Filter`]ing. +/// +/// Records that match the filter will be forwarded to the wrapped log. +/// Other records will be ignored. +#[derive(Debug)] +pub struct FilteredLog { + log: T, + filter: Filter, +} + +impl FilteredLog { + /// Create a new filtered log. + pub fn new(log: T, filter: Filter) -> Self { + Self { log, filter } + } +} + +impl Log for FilteredLog { + /// Determines if a log message with the specified metadata would be logged. + /// + /// For the wrapped log, this returns `true` only if both the filter and the wrapped log return `true`. + fn enabled(&self, metadata: &log::Metadata) -> bool { + self.filter.enabled(metadata) && self.log.enabled(metadata) + } + + /// Logs the record. + /// + /// Forwards the record to the wrapped log, but only if the record matches the filter. + fn log(&self, record: &log::Record) { + if self.filter.matches(record) { + self.log.log(record) + } + } + + /// Flushes any buffered records. + /// + /// Forwards directly to the wrapped log. + fn flush(&self) { + self.log.flush() + } +} diff --git a/crates/env_filter/src/lib.rs b/crates/env_filter/src/lib.rs index 494b82da..dad06817 100644 --- a/crates/env_filter/src/lib.rs +++ b/crates/env_filter/src/lib.rs @@ -14,44 +14,32 @@ //! use env_filter::Filter; //! use log::{Log, Metadata, Record}; //! -//! struct MyLogger { -//! filter: Filter -//! } -//! -//! impl MyLogger { -//! fn new() -> MyLogger { -//! use env_filter::Builder; -//! let mut builder = Builder::new(); -//! -//! // Parse a directives string from an environment variable -//! if let Ok(ref filter) = std::env::var("MY_LOG_LEVEL") { -//! builder.parse(filter); -//! } +//! struct PrintLogger; //! -//! MyLogger { -//! filter: builder.build() -//! } -//! } -//! } -//! -//! impl Log for MyLogger { +//! impl Log for PrintLogger { //! fn enabled(&self, metadata: &Metadata) -> bool { -//! self.filter.enabled(metadata) +//! true //! } //! //! fn log(&self, record: &Record) { -//! // Check if the record is matched by the filter -//! if self.filter.matches(record) { -//! println!("{:?}", record); -//! } +//! println!("{:?}", record); //! } //! //! fn flush(&self) {} //! } +//! +//! let mut builder = env_filter::Builder::new(); +//! // Parse a directives string from an environment variable +//! if let Ok(ref filter) = std::env::var("MY_LOG_LEVEL") { +//! builder.parse(filter); +//! } +//! +//! let logger = env_filter::FilteredLog::new(PrintLogger, builder.build()); //! ``` mod directive; mod filter; +mod filtered_log; mod op; mod parser; @@ -62,3 +50,4 @@ use parser::parse_spec; pub use filter::Builder; pub use filter::Filter; +pub use filtered_log::FilteredLog; From 6c2ea8028236fe80c1da0a354b19808bf440858d Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Jan 2024 11:50:52 -0600 Subject: [PATCH 47/53] style(filter): Clean up --- crates/env_filter/src/filter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/env_filter/src/filter.rs b/crates/env_filter/src/filter.rs index 7a052720..c5d107da 100644 --- a/crates/env_filter/src/filter.rs +++ b/crates/env_filter/src/filter.rs @@ -5,9 +5,9 @@ use std::mem; use log::{LevelFilter, Metadata, Record}; use crate::enabled; +use crate::parse_spec; use crate::Directive; use crate::FilterOp; -use crate::parse_spec; /// A builder for a log filter. /// From 5e226cb2b73d6c9f1b21886a4b504afdea1ebfcf Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Jan 2024 11:59:56 -0600 Subject: [PATCH 48/53] chore: Release --- crates/env_filter/CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/env_filter/CHANGELOG.md b/crates/env_filter/CHANGELOG.md index 78e2e120..b9a276ca 100644 --- a/crates/env_filter/CHANGELOG.md +++ b/crates/env_filter/CHANGELOG.md @@ -7,5 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate +## [0.1.0] - 2024-01-19 + -[Unreleased]: https://github.com/rust-cli/env_logger/compare/b4a2c304c16d1db4a2998f24c00e00c0f776113b...HEAD +[Unreleased]: https://github.com/rust-cli/env_logger/compare/env_filter-v0.1.0...HEAD +[0.1.0]: https://github.com/rust-cli/env_logger/compare/b4a2c304c16d1db4a2998f24c00e00c0f776113b...env_filter-v0.1.0 From ba41ebb6d2d726403560cd987b1c5b3c6797f817 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Jan 2024 12:07:14 -0600 Subject: [PATCH 49/53] docs: Update changelog --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b911e5e..93b9369e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate +### Breaking Change + +- Removed bespoke styling API + - `env_logger::fmt::Formatter::style` + - `env_logger::fmt::Formatter::default_styled_level` + - `env_logger::fmt::Style` + - `env_logger::fmt::Color` + - `env_logger::fmt::StyledValue` +- Removed `env_logger::filter` in favor of `env_filter` + +### Compatibility + +MSRV changed to 1.71 + +### Features + +- Automatically adapt ANSI escape codes in logged messages to the current terminal's capabilities +- Add support for `NO_COLOR` and `CLICOLOR_FORCE`, see https://bixense.com/clicolors/ + +### Fixes + +- Print colors when `is_test(true)` +- Allow styling with `Target::Pipe` + ## [0.10.2] - 2024-01-18 ### Performance From 8f4361ba4439acb69068be0e181d2d1300b7218d Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Jan 2024 12:08:36 -0600 Subject: [PATCH 50/53] chore: Release --- CHANGELOG.md | 5 ++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93b9369e..e3f20a27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate +## [0.11.0] - 2024-01-19 + ### Breaking Change - Removed bespoke styling API @@ -85,7 +87,8 @@ To open room for changing dependencies: - Added a method to print the module instead of the target -[Unreleased]: https://github.com/rust-cli/env_logger/compare/v0.10.2...HEAD +[Unreleased]: https://github.com/rust-cli/env_logger/compare/v0.11.0...HEAD +[0.11.0]: https://github.com/rust-cli/env_logger/compare/v0.10.2...v0.11.0 [0.10.2]: https://github.com/rust-cli/env_logger/compare/v0.10.1...v0.10.2 [0.10.1]: https://github.com/rust-cli/env_logger/compare/v0.10.0...v0.10.1 [0.10.0]: https://github.com/rust-cli/env_logger/compare/v0.9.3...v0.10.0 diff --git a/Cargo.lock b/Cargo.lock index 776d3400..204d2c4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,7 +81,7 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.2" +version = "0.11.0" dependencies = [ "anstream", "anstyle", diff --git a/Cargo.toml b/Cargo.toml index b669cef6..224c6659 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ include = [ [package] name = "env_logger" -version = "0.10.2" +version = "0.11.0" description = """ A logging implementation for `log` which is configured via an environment variable. From 6f31706a184ebe6332c1a4d083702a06b56cfb87 Mon Sep 17 00:00:00 2001 From: Boris-Chengbiao Zhou Date: Sat, 27 Jan 2024 03:37:56 +0100 Subject: [PATCH 51/53] fix(fmt): Fix passing of WriteStyle when using Target::Pipe --- src/fmt/writer/buffer.rs | 7 +++++-- src/fmt/writer/mod.rs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/fmt/writer/buffer.rs b/src/fmt/writer/buffer.rs index a7ea25bf..b720a988 100644 --- a/src/fmt/writer/buffer.rs +++ b/src/fmt/writer/buffer.rs @@ -31,10 +31,13 @@ impl BufferWriter { } } - pub(in crate::fmt::writer) fn pipe(pipe: Box>) -> Self { + pub(in crate::fmt::writer) fn pipe( + pipe: Box>, + write_style: WriteStyle, + ) -> Self { BufferWriter { target: WritableTarget::Pipe(pipe), - write_style: WriteStyle::Never, + write_style, } } diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index dbcf45b6..688fda16 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -138,7 +138,7 @@ impl Builder { let writer = match mem::take(&mut self.target) { 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))), + Target::Pipe(pipe) => BufferWriter::pipe(Box::new(Mutex::new(pipe)), color_choice), }; Writer { inner: writer } From 9f73bde4ba4d14085e0248af8eef87b506290443 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 26 Jan 2024 21:47:10 -0600 Subject: [PATCH 52/53] docs: Update changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3f20a27..ecc28da1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate +### Fixes + +- Allow styling with `Target::Pipe` + ## [0.11.0] - 2024-01-19 ### Breaking Change @@ -31,7 +35,6 @@ MSRV changed to 1.71 ### Fixes - Print colors when `is_test(true)` -- Allow styling with `Target::Pipe` ## [0.10.2] - 2024-01-18 From 7113ad4bd937a56b260a799f8952f3e61d0eaea7 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 26 Jan 2024 21:47:15 -0600 Subject: [PATCH 53/53] chore: Release --- CHANGELOG.md | 5 ++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecc28da1..c6b4ef76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate +## [0.11.1] - 2024-01-27 + ### Fixes - Allow styling with `Target::Pipe` @@ -90,7 +92,8 @@ To open room for changing dependencies: - Added a method to print the module instead of the target -[Unreleased]: https://github.com/rust-cli/env_logger/compare/v0.11.0...HEAD +[Unreleased]: https://github.com/rust-cli/env_logger/compare/v0.11.1...HEAD +[0.11.1]: https://github.com/rust-cli/env_logger/compare/v0.11.0...v0.11.1 [0.11.0]: https://github.com/rust-cli/env_logger/compare/v0.10.2...v0.11.0 [0.10.2]: https://github.com/rust-cli/env_logger/compare/v0.10.1...v0.10.2 [0.10.1]: https://github.com/rust-cli/env_logger/compare/v0.10.0...v0.10.1 diff --git a/Cargo.lock b/Cargo.lock index 204d2c4c..bb0de810 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,7 +81,7 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.0" +version = "0.11.1" dependencies = [ "anstream", "anstyle", diff --git a/Cargo.toml b/Cargo.toml index 224c6659..97ffdd69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ include = [ [package] name = "env_logger" -version = "0.11.0" +version = "0.11.1" description = """ A logging implementation for `log` which is configured via an environment variable. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy