From c522638ae413fb41efb0a1ea7aa9c5ca4be54711 Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Mon, 18 Dec 2017 18:03:24 +1000 Subject: [PATCH 01/11] refactor color api for better ergonomics --- examples/custom_format.rs | 11 ++- src/fmt.rs | 148 +++++++++++++++++++++++++++++--------- src/lib.rs | 35 ++++----- 3 files changed, 137 insertions(+), 57 deletions(-) diff --git a/examples/custom_format.rs b/examples/custom_format.rs index b66a3c29..af656399 100644 --- a/examples/custom_format.rs +++ b/examples/custom_format.rs @@ -17,12 +17,19 @@ extern crate env_logger; use std::env; use std::io::Write; +use env_logger::{Builder, fmt}; + fn init_logger() { - let mut builder = env_logger::Builder::new(); + let mut builder = Builder::new(); // Use a different format for writing log records builder.format(|buf, record| { - writeln!(buf, "My formatted log: {}", record.args()) + let mut style = buf.style(); + style.set_bg(fmt::Color::Yellow).set_bold(true); + + let timestamp = buf.timestamp(); + + writeln!(buf, "My formatted log ({}): {}", timestamp, style.value(record.args())) }); if let Ok(s) = env::var("MY_LOG_LEVEL") { diff --git a/src/fmt.rs b/src/fmt.rs index eccb08ce..0efbb530 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -11,97 +11,175 @@ use std::io::prelude::*; use std::io; use std::fmt; +use std::rc::Rc; +use std::cell::RefCell; -use termcolor::{Color, ColorSpec, Buffer, WriteColor}; +use termcolor::{ColorSpec, Buffer, BufferWriter, WriteColor}; use chrono::{DateTime, Utc}; use chrono::format::Item; +pub use termcolor::Color; + /// A formatter to write logs into. /// /// `Formatter` implements the standard [`Write`] trait for writing log records. -/// It also supports terminal colors, but this is currently private. +/// It also supports terminal colors, through the [`style`] method. /// /// [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html +/// [`style`]: #method.style pub struct Formatter { - buf: Buffer, + buf: Rc>, } -/// A formatter with a particular style. -/// -/// Each call to `write` will apply the style before writing the output. -pub(crate) struct StyledFormatter { - buf: W, +/// A set of styles to apply to the terminal output. +#[derive(Clone)] +pub struct Style { + buf: Rc>, spec: ColorSpec, } +/// A value that can be printed using the given styles. +pub struct StyledValue<'a, T> { + style: &'a Style, + value: T, +} + +impl Style { + /// Set the foreground color. + pub fn set_color(&mut self, color: Color) -> &mut Style { + self.spec.set_fg(Some(color)); + self + } + + /// Make the text bold. + pub fn set_bold(&mut self, yes: bool) -> &mut Style { + self.spec.set_bold(yes); + self + } + + /// Set the background color. + pub fn set_bg(&mut self, color: Color) -> &mut Style { + self.spec.set_bg(Some(color)); + self + } + + /// Wrap a value in the style. + /// + /// The same `Style` can be used to print multiple different values. + pub fn value(&self, value: T) -> StyledValue { + StyledValue { + style: &self, + value + } + } +} + /// An RFC3339 formatted timestamp. -pub(crate) struct Timestamp(DateTime); +/// +/// The timestamp implements [`Display`] and can be written to a [`Formatter`]. +/// +/// [`Display`]: https://doc.rust-lang.org/stable/std/fmt/trait.Display.html +/// [`Formatter`]: struct.Formatter.html +pub struct Timestamp(DateTime); impl Formatter { pub(crate) fn new(buf: Buffer) -> Self { Formatter { - buf: buf, + buf: Rc::new(RefCell::new(buf)), } } - pub(crate) fn color(&mut self, color: Color) -> StyledFormatter<&mut Buffer> { - let mut spec = ColorSpec::new(); - spec.set_fg(Some(color)); - - StyledFormatter { - buf: &mut self.buf, - spec: spec + /// Begin a new style. + pub fn style(&self) -> Style { + Style { + buf: self.buf.clone(), + spec: ColorSpec::new(), } } - pub(crate) fn timestamp(&self) -> Timestamp { + /// Get a timestamp. + pub fn timestamp(&self) -> Timestamp { Timestamp(Utc::now()) } - pub(crate) fn as_ref(&self) -> &Buffer { - &self.buf + pub(crate) fn print(&self, writer: &BufferWriter) -> io::Result<()> { + writer.print(&self.buf.borrow()) } pub(crate) fn clear(&mut self) { - self.buf.clear() + self.buf.borrow_mut().clear() } } impl Write for Formatter { fn write(&mut self, buf: &[u8]) -> io::Result { - self.buf.write(buf) + self.buf.borrow_mut().write(buf) } fn flush(&mut self) -> io::Result<()> { - self.buf.flush() + self.buf.borrow_mut().flush() } } -impl Write for StyledFormatter - where W: WriteColor -{ - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buf.set_color(&self.spec)?; +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 = self.buf.write(buf); - let reset = self.buf.reset(); + let write = f(); + let reset = self.style.buf.borrow_mut().reset().map_err(|_| fmt::Error); - write.and_then(|w| reset.map(|_| w)) + write.and(reset) } +} - fn flush(&mut self) -> io::Result<()> { - self.buf.flush() +impl fmt::Debug for Timestamp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + /// A `Debug` wrapper for `Timestamp` that uses the `Display` implementation. + struct TimestampValue<'a>(&'a Timestamp); + + impl<'a> fmt::Debug for TimestampValue<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } + } + + f.debug_tuple("Timestamp") + .field(&TimestampValue(&self)) + .finish() } } -impl fmt::Debug for Formatter{ +impl fmt::Debug for Formatter { fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { f.debug_struct("Formatter").finish() } } -impl fmt::Display for Timestamp{ +impl fmt::Debug for Style { + fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { + f.debug_struct("Style").field("spec", &self.spec).finish() + } +} + +impl<'a, T: fmt::Debug> fmt::Debug for StyledValue<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { + self.write_fmt(|| T::fmt(&self.value, f)) + } +} + +impl<'a, T: fmt::Display> fmt::Display for StyledValue<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { + self.write_fmt(|| T::fmt(&self.value, f)) + } +} + +// TODO: Other `fmt` traits + +impl fmt::Display for Timestamp { fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { const ITEMS: &'static [Item<'static>] = { use chrono::format::Item::*; diff --git a/src/lib.rs b/src/lib.rs index 5c1500ce..3eea5e30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -152,12 +152,12 @@ use std::mem; use std::cell::RefCell; use log::{Log, LevelFilter, Level, Record, SetLoggerError, Metadata}; -use termcolor::{ColorChoice, Color, BufferWriter}; +use termcolor::{ColorChoice, BufferWriter}; pub mod filter; pub mod fmt; -use self::fmt::Formatter; +use self::fmt::{Formatter, Color}; /// Log target, either stdout or stderr. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -241,23 +241,22 @@ impl Builder { format: Box::new(|buf, record| { let ts = buf.timestamp(); let level = record.level(); - let level_color = match level { - Level::Trace => Color::White, - Level::Debug => Color::Blue, - Level::Info => Color::Green, - Level::Warn => Color::Yellow, - Level::Error => Color::Red, + let mut level_style = buf.style(); + + match level { + Level::Trace => level_style.set_color(Color::White), + 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), }; - let write_level = write!(buf.color(level_color), "{:>5}:", level); - let write_args = if let Some(module_path) = record.module_path() { - writeln!(buf, " {}: {}: {}", ts, module_path, record.args()) + if let Some(module_path) = record.module_path() { + writeln!(buf, "{:>5} {}: {}: {}", level_style.value(level), ts, module_path, record.args()) } else { - writeln!(buf, " {}: {}", ts, record.args()) - }; - - write_level.and(write_args) + writeln!(buf, "{:>5} {}: {}", level_style.value(level), ts, record.args()) + } }), target: Target::Stderr, } @@ -456,10 +455,6 @@ impl Logger { pub fn matches(&self, record: &Record) -> bool { self.filter.matches(record) } - - fn print(&self, formatter: &Formatter) -> io::Result<()> { - self.writer.print(formatter.as_ref()) - } } impl Log for Logger { @@ -488,7 +483,7 @@ impl Log for Logger { // The format is guaranteed to be `Some` by this point let mut formatter = tl_buf.as_mut().unwrap(); - let _ = (self.format)(&mut formatter, record).and_then(|_| self.print(formatter)); + let _ = (self.format)(&mut formatter, record).and_then(|_| formatter.print(&self.writer)); // Always clear the buffer afterwards formatter.clear(); From 09135c8ba9c733df55157a2ee95dacb1214ba55a Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Fri, 22 Dec 2017 14:42:39 +1000 Subject: [PATCH 02/11] add examples and other fmt trait impls --- src/fmt.rs | 231 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 215 insertions(+), 16 deletions(-) diff --git a/src/fmt.rs b/src/fmt.rs index 0efbb530..b348af8d 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -25,13 +25,77 @@ pub use termcolor::Color; /// `Formatter` implements the standard [`Write`] trait for writing log records. /// It also supports terminal colors, through the [`style`] method. /// +/// # Examples +/// +/// Use the [`writeln`] macro to easily format a log record: +/// +/// ``` +/// use std::io::Write; +/// +/// let mut builder = env_logger::Builder::new(); +/// +/// builder.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args())); +/// ``` +/// /// [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html +/// [`writeln`]: https://doc.rust-lang.org/stable/std/macro.writeln.html /// [`style`]: #method.style pub struct Formatter { buf: Rc>, } /// 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.color +/// [weight]: #method.weight +/// [`value`]: #method.value #[derive(Clone)] pub struct Style { buf: Rc>, @@ -39,25 +103,88 @@ pub struct Style { } /// 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: &'a Style, value: T, } impl Style { - /// Set the foreground color. + /// 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)); self } - /// Make the text bold. + /// 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 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)); self @@ -66,6 +193,27 @@ impl Style { /// 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: &self, @@ -74,10 +222,11 @@ impl Style { } } -/// An RFC3339 formatted timestamp. +/// An [RFC3339] formatted timestamp. /// /// The timestamp implements [`Display`] and can be written to a [`Formatter`]. /// +/// [RFC3339]: https://www.ietf.org/rfc/rfc3339.txt /// [`Display`]: https://doc.rust-lang.org/stable/std/fmt/trait.Display.html /// [`Formatter`]: struct.Formatter.html pub struct Timestamp(DateTime); @@ -89,7 +238,30 @@ impl Formatter { } } - /// Begin a new style. + /// 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(), @@ -97,7 +269,25 @@ impl Formatter { } } - /// Get a timestamp. + /// Get a [`Timestamp`] for the current date and time in UTC. + /// + /// # Examples + /// + /// Include the current timestamp with the log record: + /// + /// ``` + /// use std::io::Write; + /// + /// let mut builder = env_logger::Builder::new(); + /// + /// builder.format(|buf, record| { + /// let ts = buf.timestamp(); + /// + /// writeln!(buf, "{}: {}: {}", ts, record.level(), record.args()) + /// }); + /// ``` + /// + /// [`Timestamp`]: struct.Timestamp.html pub fn timestamp(&self) -> Timestamp { Timestamp(Utc::now()) } @@ -165,19 +355,28 @@ impl fmt::Debug for Style { } } -impl<'a, T: fmt::Debug> fmt::Debug for StyledValue<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { - self.write_fmt(|| T::fmt(&self.value, f)) - } -} - -impl<'a, T: fmt::Display> fmt::Display for StyledValue<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { - self.write_fmt(|| T::fmt(&self.value, f)) - } +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)) + } + } + )* + }; } -// TODO: Other `fmt` traits +impl_styled_value_fmt!( + fmt::Debug, + fmt::Display, + fmt::Pointer, + fmt::Octal, + fmt::Binary, + fmt::UpperHex, + fmt::LowerHex, + fmt::UpperExp, + fmt::LowerExp); impl fmt::Display for Timestamp { fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { From 1243aec34cc5237fc8bcceb934091735616f8327 Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Fri, 22 Dec 2017 15:30:26 +1000 Subject: [PATCH 03/11] support ignoring styles in output --- src/fmt.rs | 11 ++- src/lib.rs | 192 ++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 184 insertions(+), 19 deletions(-) diff --git a/src/fmt.rs b/src/fmt.rs index b348af8d..8585a775 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -42,6 +42,7 @@ pub use termcolor::Color; /// [`style`]: #method.style pub struct Formatter { buf: Rc>, + write_style: bool } /// A set of styles to apply to the terminal output. @@ -99,6 +100,7 @@ pub struct Formatter { #[derive(Clone)] pub struct Style { buf: Rc>, + write_style: bool, spec: ColorSpec, } @@ -232,9 +234,10 @@ impl Style { pub struct Timestamp(DateTime); impl Formatter { - pub(crate) fn new(buf: Buffer) -> Self { + pub(crate) fn new(buf: Buffer, write_style: bool) -> Self { Formatter { buf: Rc::new(RefCell::new(buf)), + write_style } } @@ -265,6 +268,7 @@ impl Formatter { pub fn style(&self) -> Style { Style { buf: self.buf.clone(), + write_style: self.write_style, spec: ColorSpec::new(), } } @@ -316,6 +320,11 @@ impl<'a, T> StyledValue<'a, T> { where F: FnOnce() -> fmt::Result, { + if !self.style.write_style { + // Ignore styles and just run the format function + return f() + } + 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 diff --git a/src/lib.rs b/src/lib.rs index 3eea5e30..ae326290 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,6 +114,11 @@ //! logged if it matches. Note that the matching is done after formatting the //! log string but before adding any logging meta-data. There is a single filter //! for all modules. +//! +//! ## Disabling colors +//! +//! Colors and other styles can be disabled by setting the `RUST_LOG_STYLE` +//! environment variable to `0`. //! //! Some examples: //! @@ -146,6 +151,7 @@ extern crate termcolor; extern crate chrono; use std::env; +use std::borrow::Cow; use std::io::prelude::*; use std::io; use std::mem; @@ -159,6 +165,16 @@ pub mod fmt; use self::fmt::{Formatter, Color}; +const DEFAULT_FILTER_ENV: &'static str = "RUST_LOG"; +const DEFAULT_WRITE_STYLE_ENV: &'static str = "RUST_LOG_STYLE"; + +/// Set of environment variables to configure from. +#[derive(Debug)] +pub struct Env<'a> { + filter: Cow<'a, str>, + write_style: Cow<'a, str>, +} + /// Log target, either stdout or stderr. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum Target { @@ -190,6 +206,7 @@ pub enum Target { /// [`Builder`]: struct.Builder.html pub struct Logger { writer: BufferWriter, + write_style: bool, filter: filter::Filter, format: Box io::Result<()> + Sync + Send>, } @@ -229,6 +246,7 @@ pub struct Logger { /// ``` pub struct Builder { filter: filter::Builder, + write_style: bool, format: Box io::Result<()> + Sync + Send>, target: Target, } @@ -238,6 +256,7 @@ impl Builder { pub fn new() -> Builder { Builder { filter: filter::Builder::new(), + write_style: true, format: Box::new(|buf, record| { let ts = buf.timestamp(); let level = record.level(); @@ -262,6 +281,25 @@ impl Builder { } } + /// Initializes the log builder from the environment. + pub fn from_env<'a, E>(env: E) -> Self + where + E: Into> + { + let mut builder = Builder::new(); + let Env { filter, write_style } = env.into(); + + if let Ok(s) = env::var(&*filter) { + builder.parse(&s); + } + + if let Ok(s) = env::var(&*write_style) { + builder.parse_write_style(&s); + } + + builder + } + /// Adds filters to the logger. /// /// The given module (if any) will log at most the specified level provided. @@ -301,6 +339,15 @@ impl Builder { self } + /// Sets whether or not styles will be written. + /// + /// This can be useful in environments that don't support control characters + /// for setting colors. + pub fn write_style(&mut self, yes: bool) -> &mut Self { + self.write_style = yes; + self + } + /// Parses the directives string in the same form as the `RUST_LOG` /// environment variable. /// @@ -310,6 +357,14 @@ impl Builder { self } + /// Parses whether or not to write styles. + /// + /// A value of `0` will ignore styles. + /// Any other value will include styles. + pub fn parse_write_style(&mut self, write_style: &str) -> &mut Self { + self.write_style(parse_write_style_spec(write_style)) + } + /// Initializes the global logger with the built env logger. /// /// This should be called early in the execution of a Rust program. Any log @@ -347,7 +402,8 @@ impl Builder { }; Logger { - writer: writer, + writer, + write_style: self.write_style, filter: self.filter.build(), format: mem::replace(&mut self.format, Box::new(|_, _| Ok(()))), } @@ -384,7 +440,7 @@ impl Logger { /// [`init()`]: fn.init.html /// [`try_init()`]: fn.try_init.html pub fn new() -> Logger { - Self::from_env("RUST_LOG") + Self::from_env(Env::default()) } /// Creates a new env logger by parsing the environment variable with the @@ -398,8 +454,11 @@ impl Logger { /// for initialization as a global logger. /// /// # Example + /// + /// Initialise the logger using the given environment variable for filtering + /// and the default environment variables for other properties. /// - /// ```rust + /// ``` /// extern crate log; /// extern crate env_logger; /// @@ -413,15 +472,34 @@ impl Logger { /// log::set_boxed_logger(Box::new(logger)); /// } /// ``` + /// + /// Specify the environment variable for filtering and whether or not to + /// write styles: + /// + /// ``` + /// extern crate log; + /// extern crate env_logger; + /// + /// use std::env; + /// use env_logger::{Env, Logger}; + /// + /// fn main() { + /// let logger = Logger::from_env(Env::default() + /// .filter("MY_LOG") + /// .write_style("MY_LOG_STYLE")); + /// + /// log::set_max_level(logger.filter()); + /// log::set_boxed_logger(Box::new(logger)); + /// } + /// ``` /// /// [`new()`]: #method.new /// [`Builder`]: struct.Builder.html - pub fn from_env(env: &str) -> Logger { - let mut builder = Builder::new(); - - if let Ok(s) = env::var(env) { - builder.parse(&s); - } + pub fn from_env<'a, E>(env: E) -> Logger + where + E: Into> + { + let mut builder = Builder::from_env(env); builder.build() } @@ -477,7 +555,7 @@ impl Log for Logger { let mut tl_buf = tl_buf.borrow_mut(); if tl_buf.is_none() { - *tl_buf = Some(Formatter::new(self.writer.buffer())); + *tl_buf = Some(Formatter::new(self.writer.buffer(), self.write_style)); } // The format is guaranteed to be `Some` by this point @@ -494,6 +572,49 @@ impl Log for Logger { 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 = filter_env.into(); + self + } + + /// 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 = write_style_env.into(); + self + } +} + +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: DEFAULT_FILTER_ENV.into(), + write_style: DEFAULT_WRITE_STYLE_ENV.into() + } + } +} + mod std_fmt_impls { use std::fmt; use super::*; @@ -526,7 +647,7 @@ mod std_fmt_impls { /// 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("RUST_LOG") + try_init_from_env(Env::default()) } /// Initializes the global logger with an env logger. @@ -552,12 +673,11 @@ pub fn init() { /// /// 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(env: &str) -> Result<(), SetLoggerError> { - let mut builder = Builder::new(); - - if let Ok(s) = env::var(env) { - builder.parse(&s); - } +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() } @@ -572,6 +692,42 @@ pub fn try_init_from_env(env: &str) -> Result<(), SetLoggerError> { /// /// 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(env: &str) { +pub fn init_from_env<'a, E>(env: E) +where + E: Into> +{ try_init_from_env(env).unwrap(); } + +// Parse a `write_style` environment value. +// The output will be `true` unless a value of `0` is given +fn parse_write_style_spec(spec: &str) -> bool { + match spec { + "0" => false, + _ => true + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_write_style_valid_false() { + assert_eq!(false, parse_write_style_spec("0")); + } + + #[test] + fn parse_write_style_invalid() { + let inputs = vec![ + "1", + "false", + "true", + "please write me some styles" + ]; + + for input in inputs { + assert_eq!(true, parse_write_style_spec(input)); + } + } +} \ No newline at end of file From 8dc42f79c939b6ea21fde49089e2915124dfead3 Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Fri, 22 Dec 2017 15:32:13 +1000 Subject: [PATCH 04/11] fix position of disabling colors section --- src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ae326290..419a2e4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,11 +114,6 @@ //! logged if it matches. Note that the matching is done after formatting the //! log string but before adding any logging meta-data. There is a single filter //! for all modules. -//! -//! ## Disabling colors -//! -//! Colors and other styles can be disabled by setting the `RUST_LOG_STYLE` -//! environment variable to `0`. //! //! Some examples: //! @@ -131,6 +126,11 @@ //! * `error,hello=warn/[0-9]scopes` turn on global error logging and also //! warn for hello. In both cases the log message must include a single digit //! number followed by 'scopes'. +//! +//! ## Disabling colors +//! +//! Colors and other styles can be disabled by setting the `RUST_LOG_STYLE` +//! environment variable to `0`. //! //! [log-crate-url]: https://docs.rs/log/ From beabd78a704c6cc66487834f7863ffb374eaa2a2 Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Fri, 22 Dec 2017 15:37:42 +1000 Subject: [PATCH 05/11] add notes about RUST_LOG_STYLE to examples --- examples/custom_format.rs | 18 ++++++++++++------ examples/custom_logger.rs | 14 +++++++------- examples/default.rs | 6 ++++++ 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/examples/custom_format.rs b/examples/custom_format.rs index af656399..16fa7184 100644 --- a/examples/custom_format.rs +++ b/examples/custom_format.rs @@ -7,6 +7,12 @@ Before running this example, try setting the `MY_LOG_LEVEL` environment variable $ export MY_LOG_LEVEL = 'info' ``` +Also try setting the `MY_LOG_STYLE` environment variable to `0` to disable colors: + +```no_run,shell +$ export MY_LOG_STYLE = 0 +``` + If you want to control the logging output completely, see the `custom_logger` example. */ @@ -17,10 +23,14 @@ extern crate env_logger; use std::env; use std::io::Write; -use env_logger::{Builder, fmt}; +use env_logger::{Env, Builder, fmt}; fn init_logger() { - let mut builder = Builder::new(); + let env = Env::default() + .filter("MY_LOG_LEVEL") + .write_style("MY_LOG_STYLE"); + + let mut builder = Builder::from_env(env); // Use a different format for writing log records builder.format(|buf, record| { @@ -32,10 +42,6 @@ fn init_logger() { writeln!(buf, "My formatted log ({}): {}", timestamp, style.value(record.args())) }); - if let Ok(s) = env::var("MY_LOG_LEVEL") { - builder.parse(&s); - } - builder.init(); } diff --git a/examples/custom_logger.rs b/examples/custom_logger.rs index 31b46575..557c3193 100644 --- a/examples/custom_logger.rs +++ b/examples/custom_logger.rs @@ -7,6 +7,12 @@ Before running this example, try setting the `MY_LOG_LEVEL` environment variable $ export MY_LOG_LEVEL = 'info' ``` +Also try setting the `RUST_LOG_STYLE` environment variable to `0` to disable colors: + +```no_run,shell +$ export RUST_LOG_STYLE = 0 +``` + If you only want to change the way logs are formatted, look at the `custom_format` example. */ @@ -23,13 +29,7 @@ struct MyLogger { impl MyLogger { fn new() -> MyLogger { use env_logger::filter::Builder; - let mut builder = Builder::new(); - - // Parse a directives string from an environment variable - // This uses the same format as `env_logger::Logger` - if let Ok(ref filter) = std::env::var("MY_LOG_LEVEL") { - builder.parse(filter); - } + let mut builder = Builder::from_env("MY_LOG_LEVEL"); MyLogger { inner: builder.build() diff --git a/examples/default.rs b/examples/default.rs index ea2ea0b8..b6e7d703 100644 --- a/examples/default.rs +++ b/examples/default.rs @@ -6,6 +6,12 @@ Before running this example, try setting the `MY_LOG_LEVEL` environment variable ```no_run,shell $ export MY_LOG_LEVEL = 'info' ``` + +Also try setting the `RUST_LOG_STYLE` environment variable to `0` to disable colors: + +```no_run,shell +$ export RUST_LOG_STYLE = 0 +``` */ #[macro_use] From 70c969960aa6158b359728d699cd10f468083f6e Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Fri, 22 Dec 2017 15:55:36 +1000 Subject: [PATCH 06/11] add from_env method to filter::Builder for consistency --- examples/custom_format.rs | 1 - src/filter/mod.rs | 12 ++++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/custom_format.rs b/examples/custom_format.rs index 16fa7184..5bd46c54 100644 --- a/examples/custom_format.rs +++ b/examples/custom_format.rs @@ -20,7 +20,6 @@ If you want to control the logging output completely, see the `custom_logger` ex extern crate log; extern crate env_logger; -use std::env; use std::io::Write; use env_logger::{Env, Builder, fmt}; diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 26d7f208..0ce623c7 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -59,6 +59,7 @@ //! [`Builder::parse`]: struct.Builder.html#method.parse //! [`Filter::matches`]: struct.Filter.html#method.matches +use std::env; use std::mem; use std::fmt; use log::{Level, LevelFilter, Record, Metadata}; @@ -185,6 +186,17 @@ impl Builder { } } + /// 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 + } + /// Adds a directive to the filter. /// /// The given module (if any) will log at most the specified level provided. From c43b8f44141bdbe59cfde64f3f5e776593b5d63f Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Fri, 22 Dec 2017 15:57:36 +1000 Subject: [PATCH 07/11] use properly formed exports in example prose --- examples/custom_format.rs | 4 ++-- examples/custom_logger.rs | 4 ++-- examples/default.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/custom_format.rs b/examples/custom_format.rs index 5bd46c54..221d8f31 100644 --- a/examples/custom_format.rs +++ b/examples/custom_format.rs @@ -4,13 +4,13 @@ Changing the default logging format. Before running this example, try setting the `MY_LOG_LEVEL` environment variable to `info`: ```no_run,shell -$ export MY_LOG_LEVEL = 'info' +$ export MY_LOG_LEVEL='info' ``` Also try setting the `MY_LOG_STYLE` environment variable to `0` to disable colors: ```no_run,shell -$ export MY_LOG_STYLE = 0 +$ export MY_LOG_STYLE=0 ``` If you want to control the logging output completely, see the `custom_logger` example. diff --git a/examples/custom_logger.rs b/examples/custom_logger.rs index 557c3193..a2cce7ce 100644 --- a/examples/custom_logger.rs +++ b/examples/custom_logger.rs @@ -4,13 +4,13 @@ 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' +$ export MY_LOG_LEVEL='info' ``` Also try setting the `RUST_LOG_STYLE` environment variable to `0` to disable colors: ```no_run,shell -$ export RUST_LOG_STYLE = 0 +$ export RUST_LOG_STYLE=0 ``` If you only want to change the way logs are formatted, look at the `custom_format` example. diff --git a/examples/default.rs b/examples/default.rs index b6e7d703..1108d30e 100644 --- a/examples/default.rs +++ b/examples/default.rs @@ -4,13 +4,13 @@ Using `env_logger`. Before running this example, try setting the `MY_LOG_LEVEL` environment variable to `info`: ```no_run,shell -$ export MY_LOG_LEVEL = 'info' +$ export MY_LOG_LEVEL='info' ``` Also try setting the `RUST_LOG_STYLE` environment variable to `0` to disable colors: ```no_run,shell -$ export RUST_LOG_STYLE = 0 +$ export RUST_LOG_STYLE=0 ``` */ From 7e44865bd4aa26402f5046dabe904b30fbc36655 Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Sat, 23 Dec 2017 10:24:52 +1000 Subject: [PATCH 08/11] disallow building Logger instances directly --- src/lib.rs | 131 ++++++++--------------------------------------------- 1 file changed, 18 insertions(+), 113 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 419a2e4a..24151bdc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,6 +169,13 @@ const DEFAULT_FILTER_ENV: &'static str = "RUST_LOG"; const DEFAULT_WRITE_STYLE_ENV: &'static str = "RUST_LOG_STYLE"; /// Set of environment variables to configure from. +/// +/// 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: Cow<'a, str>, @@ -395,7 +402,11 @@ impl Builder { } /// Build an env logger. - pub fn build(&mut self) -> Logger { + /// + /// This method is kept private because the only way we support building + /// loggers is by installing them as the single global logger for the + /// `log` crate. + fn build(&mut self) -> Logger { let writer = match self.target { Target::Stderr => BufferWriter::stderr(ColorChoice::Always), Target::Stdout => BufferWriter::stdout(ColorChoice::Always), @@ -411,120 +422,8 @@ impl Builder { } impl Logger { - /// Creates a new env logger by parsing the `RUST_LOG` environment variable. - /// - /// The returned logger can be passed to the [`log` crate](https://docs.rs/log/) - /// for initialization as a global logger. - /// - /// If you do not need to interact directly with the `Logger`, you should - /// prefer the [`init()`] or [`try_init()`] methods, which - /// construct a `Logger` and configure it as the default logger. - /// - /// # Example - /// - /// ```rust - /// extern crate log; - /// extern crate env_logger; - /// - /// use std::env; - /// use env_logger::Logger; - /// - /// fn main() { - /// let logger = Logger::new(); - /// - /// log::set_max_level(logger.filter()); - /// log::set_boxed_logger(Box::new(logger)); - /// } - /// ``` - /// - /// [`init()`]: fn.init.html - /// [`try_init()`]: fn.try_init.html - pub fn new() -> Logger { - Self::from_env(Env::default()) - } - - /// Creates a new env logger by parsing the environment variable with the - /// given name. - /// - /// This is identical to the [`new()`] constructor, except it allows the - /// name of the environment variable to be customized. For additional - /// customization, use the [`Builder`] type instead. - /// - /// The returned logger can be passed to the [`log` crate](https://docs.rs/log/) - /// for initialization as a global logger. - /// - /// # Example - /// - /// Initialise the logger using the given environment variable for filtering - /// and the default environment variables for other properties. - /// - /// ``` - /// extern crate log; - /// extern crate env_logger; - /// - /// use std::env; - /// use env_logger::Logger; - /// - /// fn main() { - /// let logger = Logger::from_env("MY_LOG"); - /// - /// log::set_max_level(logger.filter()); - /// log::set_boxed_logger(Box::new(logger)); - /// } - /// ``` - /// - /// Specify the environment variable for filtering and whether or not to - /// write styles: - /// - /// ``` - /// extern crate log; - /// extern crate env_logger; - /// - /// use std::env; - /// use env_logger::{Env, Logger}; - /// - /// fn main() { - /// let logger = Logger::from_env(Env::default() - /// .filter("MY_LOG") - /// .write_style("MY_LOG_STYLE")); - /// - /// log::set_max_level(logger.filter()); - /// log::set_boxed_logger(Box::new(logger)); - /// } - /// ``` - /// - /// [`new()`]: #method.new - /// [`Builder`]: struct.Builder.html - pub fn from_env<'a, E>(env: E) -> Logger - where - E: Into> - { - let mut builder = Builder::from_env(env); - - builder.build() - } - /// Returns the maximum `LevelFilter` that this env logger instance is /// configured to output. - /// - /// # Example - /// - /// ```rust - /// extern crate log; - /// extern crate env_logger; - /// - /// use log::LevelFilter; - /// use env_logger::Builder; - /// - /// fn main() { - /// let mut builder = Builder::new(); - /// builder.filter(Some("module1"), LevelFilter::Info); - /// builder.filter(Some("module2"), LevelFilter::Error); - /// - /// let logger = builder.build(); - /// assert_eq!(logger.filter(), LevelFilter::Info); - /// } - /// ``` pub fn filter(&self) -> LevelFilter { self.filter.filter() } @@ -546,6 +445,12 @@ impl Log for Logger { // to the terminal. We clear these buffers afterwards, but they aren't shrinked // so will always at least have capacity for the largest log record formatted // on that thread. + // + // Because these buffers are tied to a particular logger, we don't let callers + // create instances of `Logger` themselves, or they'll race to configure the + // thread local buffer with their own configuration. This is still potentially + // an issue if a caller attempts to set and use the global logger multiple times, + // but in that case it's clearer that there's shared state at play. thread_local! { static FORMATTER: RefCell> = RefCell::new(None); From 476596b8d3dd50fe5ad7edd1081e849a96190065 Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Tue, 2 Jan 2018 17:42:56 +1000 Subject: [PATCH 09/11] refactor terminal into fmt module --- src/filter/mod.rs | 6 ++ src/fmt.rs | 147 +++++++++++++++++++++++++++++++++++++++++----- src/lib.rs | 108 +++++++++++----------------------- 3 files changed, 172 insertions(+), 89 deletions(-) diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 0ce623c7..c3b67e8f 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -252,6 +252,12 @@ impl Builder { } } +impl Default for Builder { + fn default() -> Self { + Builder::new() + } +} + impl fmt::Debug for Filter { fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { f.debug_struct("Filter") diff --git a/src/fmt.rs b/src/fmt.rs index 8585a775..8dc80297 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -9,12 +9,11 @@ //! [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html use std::io::prelude::*; -use std::io; -use std::fmt; +use std::{io, fmt}; use std::rc::Rc; use std::cell::RefCell; -use termcolor::{ColorSpec, Buffer, BufferWriter, WriteColor}; +use termcolor::{ColorSpec, ColorChoice, Buffer, BufferWriter, WriteColor}; use chrono::{DateTime, Utc}; use chrono::format::Item; @@ -42,7 +41,6 @@ pub use termcolor::Color; /// [`style`]: #method.style pub struct Formatter { buf: Rc>, - write_style: bool } /// A set of styles to apply to the terminal output. @@ -100,7 +98,6 @@ pub struct Formatter { #[derive(Clone)] pub struct Style { buf: Rc>, - write_style: bool, spec: ColorSpec, } @@ -114,6 +111,89 @@ pub struct StyledValue<'a, T> { value: T, } +/// Log target, either stdout or stderr. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum Target { + /// Logs will be sent to standard output. + Stdout, + /// Logs will be sent to standard error. + Stderr, +} + +impl Default for Target { + fn default() -> Self { + Target::Stderr + } +} + +/// Whether or not to print styles to the target. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum WriteStyle { + /// Try to print styles, but don't force the issue. + Auto, + /// Always print styles. + Always, + /// Never print styles. + Never, +} + +impl Default for WriteStyle { + fn default() -> Self { + WriteStyle::Auto + } +} + +pub(crate) struct Writer(BufferWriter); + +pub(crate) struct Builder { + target: Target, + write_style: WriteStyle, +} + +impl Builder { + pub fn new() -> Self { + Builder { + target: Default::default(), + write_style: Default::default(), + } + } + + pub fn target(&mut self, target: Target) -> &mut Self { + self.target = target; + self + } + + pub fn parse(&mut self, write_style: &str) -> &mut Self { + self.write_style(parse_write_style(write_style)) + } + + pub fn write_style(&mut self, write_style: WriteStyle) -> &mut Self { + self.write_style = write_style; + self + } + + pub fn build(&mut self) -> Writer { + let color_choice = match self.write_style { + WriteStyle::Auto => ColorChoice::Auto, + WriteStyle::Always => ColorChoice::Always, + WriteStyle::Never => ColorChoice::Never, + }; + + let writer = match self.target { + Target::Stderr => BufferWriter::stderr(color_choice), + Target::Stdout => BufferWriter::stdout(color_choice), + }; + + Writer(writer) + } +} + +impl Default for Builder { + fn default() -> Self { + Builder::new() + } +} + impl Style { /// Set the text color. /// @@ -234,10 +314,9 @@ impl Style { pub struct Timestamp(DateTime); impl Formatter { - pub(crate) fn new(buf: Buffer, write_style: bool) -> Self { + pub(crate) fn new(writer: &Writer) -> Self { Formatter { - buf: Rc::new(RefCell::new(buf)), - write_style + buf: Rc::new(RefCell::new(writer.0.buffer())), } } @@ -268,7 +347,6 @@ impl Formatter { pub fn style(&self) -> Style { Style { buf: self.buf.clone(), - write_style: self.write_style, spec: ColorSpec::new(), } } @@ -296,8 +374,8 @@ impl Formatter { Timestamp(Utc::now()) } - pub(crate) fn print(&self, writer: &BufferWriter) -> io::Result<()> { - writer.print(&self.buf.borrow()) + pub(crate) fn print(&self, writer: &Writer) -> io::Result<()> { + writer.0.print(&self.buf.borrow()) } pub(crate) fn clear(&mut self) { @@ -320,11 +398,6 @@ impl<'a, T> StyledValue<'a, T> { where F: FnOnce() -> fmt::Result, { - if !self.style.write_style { - // Ignore styles and just run the format function - return f() - } - 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 @@ -352,12 +425,27 @@ impl fmt::Debug for Timestamp { } } +impl fmt::Debug for Writer { + fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { + f.debug_struct("Writer").finish() + } +} + impl fmt::Debug for Formatter { fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { f.debug_struct("Formatter").finish() } } +impl fmt::Debug for Builder { + fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { + f.debug_struct("Logger") + .field("target", &self.target) + .field("write_style", &self.write_style) + .finish() + } +} + impl fmt::Debug for Style { fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { f.debug_struct("Style").field("spec", &self.spec).finish() @@ -414,3 +502,30 @@ impl fmt::Display for Timestamp { self.0.format_with_items(ITEMS.iter().cloned()).fmt(f) } } + +fn parse_write_style(spec: &str) -> WriteStyle { + match spec { + "auto" => WriteStyle::Auto, + "always" => WriteStyle::Always, + "never" => WriteStyle::Never, + _ => Default::default(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_write_style_valid() { + let inputs = vec![ + ("auto", Some(WriteStyle::Auto)), + ("always", Some(WriteStyle::Always)), + ("never", Some(WriteStyle::Never)), + ]; + + for (input, expected) in inputs { + assert_eq!(expected, WriteStyle::parse(input)); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 24151bdc..28854791 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -129,8 +129,13 @@ //! //! ## Disabling colors //! -//! Colors and other styles can be disabled by setting the `RUST_LOG_STYLE` -//! environment variable to `0`. +//! Colors and other styles can be configured with the `RUST_LOG_STYLE` +//! environment variable. +//! It accepts the following values: +//! +//! * `auto` (default) will attempt to print style characters, but don't force the issue. +//! * `always` will always print style characters even if they aren't supported by the terminal. +//! * `never` will never print style characters. //! //! [log-crate-url]: https://docs.rs/log/ @@ -158,12 +163,11 @@ use std::mem; use std::cell::RefCell; use log::{Log, LevelFilter, Level, Record, SetLoggerError, Metadata}; -use termcolor::{ColorChoice, BufferWriter}; pub mod filter; pub mod fmt; -use self::fmt::{Formatter, Color}; +pub use self::fmt::{Target, WriteStyle, Color, Formatter}; const DEFAULT_FILTER_ENV: &'static str = "RUST_LOG"; const DEFAULT_WRITE_STYLE_ENV: &'static str = "RUST_LOG_STYLE"; @@ -182,15 +186,6 @@ pub struct Env<'a> { write_style: Cow<'a, str>, } -/// Log target, either stdout or stderr. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum Target { - /// Logs will be sent to standard output. - Stdout, - /// Logs will be sent to standard error. - Stderr, -} - /// The env logger. /// /// This struct implements the `Log` trait from the [`log` crate][log-crate-url], @@ -212,8 +207,7 @@ pub enum Target { /// [`Logger::new()`]: #method.new /// [`Builder`]: struct.Builder.html pub struct Logger { - writer: BufferWriter, - write_style: bool, + writer: fmt::Writer, filter: filter::Filter, format: Box io::Result<()> + Sync + Send>, } @@ -253,17 +247,16 @@ pub struct Logger { /// ``` pub struct Builder { filter: filter::Builder, - write_style: bool, + writer: fmt::Builder, format: Box io::Result<()> + Sync + Send>, - target: Target, } impl Builder { /// Initializes the log builder with defaults. pub fn new() -> Builder { Builder { - filter: filter::Builder::new(), - write_style: true, + filter: Default::default(), + writer: Default::default(), format: Box::new(|buf, record| { let ts = buf.timestamp(); let level = record.level(); @@ -284,7 +277,6 @@ impl Builder { writeln!(buf, "{:>5} {}: {}", level_style.value(level), ts, record.args()) } }), - target: Target::Stderr, } } @@ -294,13 +286,13 @@ impl Builder { E: Into> { let mut builder = Builder::new(); - let Env { filter, write_style } = env.into(); + let env = env.into(); - if let Ok(s) = env::var(&*filter) { + if let Some(s) = env.get_filter() { builder.parse(&s); } - if let Ok(s) = env::var(&*write_style) { + if let Some(s) = env.get_write_style() { builder.parse_write_style(&s); } @@ -341,8 +333,8 @@ impl Builder { /// Sets the target for the log output. /// /// Env logger can log to either stdout or stderr. The default is stderr. - pub fn target(&mut self, target: Target) -> &mut Self { - self.target = target; + pub fn target(&mut self, target: fmt::Target) -> &mut Self { + self.writer.target(target); self } @@ -350,8 +342,8 @@ impl Builder { /// /// This can be useful in environments that don't support control characters /// for setting colors. - pub fn write_style(&mut self, yes: bool) -> &mut Self { - self.write_style = yes; + pub fn write_style(&mut self, write_style: fmt::WriteStyle) -> &mut Self { + self.writer.write_style(write_style); self } @@ -364,12 +356,13 @@ impl Builder { self } - /// Parses whether or not to write styles. + /// Parses whether or not to write styles in the same form as the `RUST_LOG_STYLE` + /// environment variable. /// - /// A value of `0` will ignore styles. - /// Any other value will include styles. + /// See the module documentation for more details. pub fn parse_write_style(&mut self, write_style: &str) -> &mut Self { - self.write_style(parse_write_style_spec(write_style)) + self.writer.parse(write_style); + self } /// Initializes the global logger with the built env logger. @@ -407,14 +400,8 @@ impl Builder { /// loggers is by installing them as the single global logger for the /// `log` crate. fn build(&mut self) -> Logger { - let writer = match self.target { - Target::Stderr => BufferWriter::stderr(ColorChoice::Always), - Target::Stdout => BufferWriter::stdout(ColorChoice::Always), - }; - Logger { - writer, - write_style: self.write_style, + writer: self.writer.build(), filter: self.filter.build(), format: mem::replace(&mut self.format, Box::new(|_, _| Ok(()))), } @@ -460,7 +447,7 @@ impl Log for Logger { let mut tl_buf = tl_buf.borrow_mut(); if tl_buf.is_none() { - *tl_buf = Some(Formatter::new(self.writer.buffer(), self.write_style)); + *tl_buf = Some(Formatter::new(&self.writer)); } // The format is guaranteed to be `Some` by this point @@ -492,6 +479,10 @@ impl<'a> Env<'a> { self } + fn get_filter(&self) -> Option { + env::var(&*self.filter).ok() + } + /// Specify an environment variable to read the style from. pub fn write_style(mut self, write_style_env: E) -> Self where @@ -500,6 +491,10 @@ impl<'a> Env<'a> { self.write_style = write_style_env.into(); self } + + fn get_write_style(&self) -> Option { + env::var(&*self.write_style).ok() + } } impl<'a, T> From for Env<'a> @@ -536,7 +531,7 @@ mod std_fmt_impls { fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { f.debug_struct("Logger") .field("filter", &self.filter) - .field("target", &self.target) + .field("writer", &self.writer) .finish() } } @@ -603,36 +598,3 @@ where { try_init_from_env(env).unwrap(); } - -// Parse a `write_style` environment value. -// The output will be `true` unless a value of `0` is given -fn parse_write_style_spec(spec: &str) -> bool { - match spec { - "0" => false, - _ => true - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_write_style_valid_false() { - assert_eq!(false, parse_write_style_spec("0")); - } - - #[test] - fn parse_write_style_invalid() { - let inputs = vec![ - "1", - "false", - "true", - "please write me some styles" - ]; - - for input in inputs { - assert_eq!(true, parse_write_style_spec(input)); - } - } -} \ No newline at end of file From 88b88fe1ca3a7765465c0aeae7bb36dcca9bbb8a Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Tue, 2 Jan 2018 17:47:06 +1000 Subject: [PATCH 10/11] fix up tests --- src/fmt.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/fmt.rs b/src/fmt.rs index 8dc80297..74740f52 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -519,13 +519,27 @@ mod tests { #[test] fn parse_write_style_valid() { let inputs = vec![ - ("auto", Some(WriteStyle::Auto)), - ("always", Some(WriteStyle::Always)), - ("never", Some(WriteStyle::Never)), + ("auto", WriteStyle::Auto), + ("always", WriteStyle::Always), + ("never", WriteStyle::Never), ]; for (input, expected) in inputs { - assert_eq!(expected, WriteStyle::parse(input)); + assert_eq!(expected, parse_write_style(input)); + } + } + + #[test] + fn parse_write_style_invalid() { + let inputs = vec![ + "", + "true", + "false", + "NEVER!!" + ]; + + for input in inputs { + assert_eq!(WriteStyle::Auto, parse_write_style(input)); } } } From 48944ca8265ecc1d2fc83acb1bf53514f2237e1f Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Wed, 3 Jan 2018 07:30:54 +1000 Subject: [PATCH 11/11] fix a few doc errors --- examples/custom_format.rs | 5 +-- examples/custom_logger.rs | 5 +-- examples/default.rs | 5 +-- src/fmt.rs | 61 +++++++++++++++++++++++++++------- src/lib.rs | 69 ++++++++++++++++++++++++++++++++++++--- 5 files changed, 123 insertions(+), 22 deletions(-) diff --git a/examples/custom_format.rs b/examples/custom_format.rs index 221d8f31..68a064d4 100644 --- a/examples/custom_format.rs +++ b/examples/custom_format.rs @@ -7,10 +7,11 @@ Before running this example, try setting the `MY_LOG_LEVEL` environment variable $ export MY_LOG_LEVEL='info' ``` -Also try setting the `MY_LOG_STYLE` environment variable to `0` to disable colors: +Also try setting the `MY_LOG_STYLE` environment variable to `never` to disable colors +or `auto` to enable them: ```no_run,shell -$ export MY_LOG_STYLE=0 +$ export MY_LOG_STYLE=never ``` If you want to control the logging output completely, see the `custom_logger` example. diff --git a/examples/custom_logger.rs b/examples/custom_logger.rs index a2cce7ce..5536985d 100644 --- a/examples/custom_logger.rs +++ b/examples/custom_logger.rs @@ -7,10 +7,11 @@ Before running this example, try setting the `MY_LOG_LEVEL` environment variable $ export MY_LOG_LEVEL='info' ``` -Also try setting the `RUST_LOG_STYLE` environment variable to `0` to disable colors: +Also try setting the `MY_LOG_STYLE` environment variable to `never` to disable colors +or `auto` to enable them: ```no_run,shell -$ export RUST_LOG_STYLE=0 +$ export MY_LOG_STYLE=never ``` If you only want to change the way logs are formatted, look at the `custom_format` example. diff --git a/examples/default.rs b/examples/default.rs index 1108d30e..89444539 100644 --- a/examples/default.rs +++ b/examples/default.rs @@ -7,10 +7,11 @@ Before running this example, try setting the `MY_LOG_LEVEL` environment variable $ export MY_LOG_LEVEL='info' ``` -Also try setting the `RUST_LOG_STYLE` environment variable to `0` to disable colors: +Also try setting the `MY_LOG_STYLE` environment variable to `never` to disable colors +or `auto` to enable them: ```no_run,shell -$ export RUST_LOG_STYLE=0 +$ export MY_LOG_STYLE=never ``` */ diff --git a/src/fmt.rs b/src/fmt.rs index 74740f52..24e096dc 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -5,7 +5,33 @@ //! about the contents of this module and can use the `Formatter` like an ordinary //! [`Write`]. //! +//! # Formatting log records +//! +//! The format used to print log records can be customised using the [`Builder.format`] +//! method. +//! Custom formats can apply different color and weight to printed values using +//! [`Style`] builders. +//! +//! ``` +//! 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()) +//! }); +//! ``` +//! //! [`Formatter`]: struct.Formatter.html +//! [`Style`]: struct.Style.html +//! [`Builder.format`]: ../struct.Builder.html#method.format //! [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html use std::io::prelude::*; @@ -111,7 +137,16 @@ pub struct StyledValue<'a, T> { value: T, } -/// Log target, either stdout or stderr. +/// An [RFC3339] formatted timestamp. +/// +/// The timestamp implements [`Display`] and can be written to a [`Formatter`]. +/// +/// [RFC3339]: https://www.ietf.org/rfc/rfc3339.txt +/// [`Display`]: https://doc.rust-lang.org/stable/std/fmt/trait.Display.html +/// [`Formatter`]: struct.Formatter.html +pub struct Timestamp(DateTime); + +/// Log target, either `stdout` or `stderr`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum Target { /// Logs will be sent to standard output. @@ -131,7 +166,7 @@ impl Default for Target { pub enum WriteStyle { /// Try to print styles, but don't force the issue. Auto, - /// Always print styles. + /// Try very hard to print styles. Always, /// Never print styles. Never, @@ -143,14 +178,19 @@ impl Default for WriteStyle { } } +/// A terminal target with color awareness. pub(crate) struct Writer(BufferWriter); +/// A builder for a terminal writer. +/// +/// The target and style choice can be configured before building. pub(crate) struct Builder { target: Target, write_style: WriteStyle, } impl Builder { + /// Initialize the writer builder with defaults. pub fn new() -> Self { Builder { target: Default::default(), @@ -158,20 +198,28 @@ impl Builder { } } + /// Set the target to write to. pub fn target(&mut self, target: Target) -> &mut Self { self.target = target; self } + /// Parses a style choice string. + /// + /// See the [Disabling colors] section for more details. + /// + /// [Disabling colors]: ../index.html#disabling-colors pub fn parse(&mut self, write_style: &str) -> &mut Self { self.write_style(parse_write_style(write_style)) } + /// Whether or not to print style characters when writing. pub fn write_style(&mut self, write_style: WriteStyle) -> &mut Self { self.write_style = write_style; self } + /// Build a terminal writer. pub fn build(&mut self) -> Writer { let color_choice = match self.write_style { WriteStyle::Auto => ColorChoice::Auto, @@ -304,15 +352,6 @@ impl Style { } } -/// An [RFC3339] formatted timestamp. -/// -/// The timestamp implements [`Display`] and can be written to a [`Formatter`]. -/// -/// [RFC3339]: https://www.ietf.org/rfc/rfc3339.txt -/// [`Display`]: https://doc.rust-lang.org/stable/std/fmt/trait.Display.html -/// [`Formatter`]: struct.Formatter.html -pub struct Timestamp(DateTime); - impl Formatter { pub(crate) fn new(writer: &Writer) -> Self { Formatter { diff --git a/src/lib.rs b/src/lib.rs index 28854791..011079cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,13 +130,14 @@ //! ## Disabling colors //! //! Colors and other styles can be configured with the `RUST_LOG_STYLE` -//! environment variable. -//! It accepts the following values: +//! environment variable. It accepts the following values: //! //! * `auto` (default) will attempt to print style characters, but don't force the issue. +//! If the console isn't available on Windows, or if TERM=dumb, for example, then don't print colors. //! * `always` will always print style characters even if they aren't supported by the terminal. +//! This includes emitting ANSI colors on Windows if the console API is unavailable. //! * `never` will never print style characters. -//! +//! //! [log-crate-url]: https://docs.rs/log/ #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", @@ -281,6 +282,32 @@ impl Builder { } /// Initializes the log builder from the environment. + /// + /// The variables used to read configuration from can be tweaked before + /// passing in. + /// + /// # Examples + /// + /// Initialise a logger using the default environment variables: + /// + /// ``` + /// use env_logger::{Builder, Env}; + /// + /// let mut builder = Builder::from_env(Env::default()); + /// 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> @@ -564,10 +591,29 @@ pub fn init() { } /// Attempts to initialize the global logger with an env logger from the given -/// environment variable. +/// 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: +/// +/// ``` +/// # extern crate env_logger; +/// use env_logger::{Builder, Env}; +/// +/// # fn run() -> Result<(), Box<::std::error::Error>> { +/// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); +/// +/// env_logger::try_init_from_env(env)?; +/// +/// Ok(()) +/// # } +/// # fn main() { run().unwrap(); } +/// ``` /// /// # Errors /// @@ -583,10 +629,23 @@ where } /// Initializes the global logger with an env logger from the given environment -/// variable. +/// 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 ///