diff --git a/.travis.yml b/.travis.yml index f42848d0..29e9feb1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,10 +8,7 @@ rust: before_script: - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH script: - - cargo build --verbose - - cargo build --verbose --no-default-features - - cargo test --verbose - - cargo test --verbose --no-default-features + - cargo run -p ci after_success: - travis-cargo --only nightly doc-upload diff --git a/Cargo.toml b/Cargo.toml index b5b09273..d7e959ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,12 +16,17 @@ keywords = ["logging", "log", "logger"] [maintenance] status = "actively-developed" +[workspace] +members = [ + "ci" +] + [dependencies] log = { version = "0.4", features = ["std"] } regex = { version = "1.0.3", optional = true } -termcolor = "1" -humantime = "1.1" -atty = "0.2.5" +termcolor = { version = "1.0.2", optional = true } +humantime = { version = "1.1", optional = true } +atty = { version = "0.2.5", optional = true } [[test]] name = "regexp_filter" @@ -32,4 +37,4 @@ name = "log-in-log" harness = false [features] -default = ["regex"] +default = ["termcolor", "atty", "humantime", "regex"] diff --git a/ci/Cargo.toml b/ci/Cargo.toml new file mode 100644 index 00000000..01cd779a --- /dev/null +++ b/ci/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "ci" +version = "0.0.0" +authors = ["The Rust Project Developers"] +publish = false + +[dependencies] diff --git a/ci/src/main.rs b/ci/src/main.rs new file mode 100644 index 00000000..e49a63a8 --- /dev/null +++ b/ci/src/main.rs @@ -0,0 +1,37 @@ +mod task; +mod permute; + +fn main() { + let features = [ + "termcolor", + "humantime", + "atty", + "regex", + ]; + + // Run a default build + if !task::test(Default::default()) { + panic!("default test execution failed"); + } + + // Run a set of permutations + let failed = permute::all(&features) + .into_iter() + .filter(|features| + !task::test(task::TestArgs { + features: features.clone(), + default_features: false, + lib_only: true, + })) + .collect::>(); + + if failed.len() > 0 { + for failed in failed { + eprintln!("FAIL: {:?}", failed); + } + + panic!("test execution failed"); + } else { + println!("test execution succeeded"); + } +} diff --git a/ci/src/permute.rs b/ci/src/permute.rs new file mode 100644 index 00000000..bc3a7063 --- /dev/null +++ b/ci/src/permute.rs @@ -0,0 +1,27 @@ +use std::collections::BTreeSet; + +pub fn all(input: &[T]) -> BTreeSet> where T: Ord + Eq + Clone { + let mut permutations = BTreeSet::new(); + + if input.len() == 0 { + return permutations; + } + + permutations.insert(input.iter().cloned().collect()); + + if input.len() > 1 { + for t in input { + let mut p = input + .iter() + .filter(|pt| *pt != t) + .cloned() + .collect::>(); + + for pt in all(&p) { + permutations.insert(pt); + } + } + } + + permutations +} diff --git a/ci/src/task.rs b/ci/src/task.rs new file mode 100644 index 00000000..aebc77f3 --- /dev/null +++ b/ci/src/task.rs @@ -0,0 +1,79 @@ +use std::collections::BTreeSet; +use std::process::{ + Command, + Stdio, +}; + +pub type Feature = &'static str; + +pub struct TestArgs { + pub features: BTreeSet, + pub default_features: bool, + pub lib_only: bool, +} + +impl Default for TestArgs { + fn default() -> Self { + TestArgs { + features: BTreeSet::new(), + default_features: true, + lib_only: false, + } + } +} + +impl TestArgs { + fn features_string(&self) -> Option { + if self.features.len() == 0 { + return None; + } + + let s = self.features.iter().fold(String::new(), |mut s, f| { + if s.len() > 0 { + s.push_str(" "); + } + s.push_str(f); + + s + }); + + Some(s) + } +} + +pub fn test(args: TestArgs) -> bool { + let features = args.features_string(); + + let mut command = Command::new("cargo"); + + command + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .arg("test") + .arg("--verbose"); + + if !args.default_features { + command.arg("--no-default-features"); + } + + if args.lib_only { + command.arg("--lib"); + } + + if let Some(ref features) = features { + command.args(&["--features", features]); + } + + println!("running {:?}", command); + + let status = command + .status() + .expect("Failed to execute command"); + + if !status.success() { + eprintln!("test execution failed for features: {}", features.as_ref().map(AsRef::as_ref).unwrap_or("")); + false + } else { + true + } +} diff --git a/examples/custom_format.rs b/examples/custom_format.rs index 68a064d4..c4563f50 100644 --- a/examples/custom_format.rs +++ b/examples/custom_format.rs @@ -33,6 +33,8 @@ fn init_logger() { let mut builder = Builder::from_env(env); // Use a different format for writing log records + // The colors are only available when the `termcolor` dependency is (which it is by default) + #[cfg(feature = "termcolor")] builder.format(|buf, record| { let mut style = buf.style(); style.set_bg(fmt::Color::Yellow).set_bold(true); diff --git a/src/fmt/atty.rs b/src/fmt/atty.rs new file mode 100644 index 00000000..c441cf08 --- /dev/null +++ b/src/fmt/atty.rs @@ -0,0 +1,34 @@ +/* +This internal module contains the terminal detection implementation. + +If the `atty` crate is available then we use it to detect whether we're +attached to a particular TTY. If the `atty` crate is not available we +assume we're not attached to anything. This effectively prevents styles +from being printed. +*/ + +#[cfg(feature = "atty")] +mod imp { + use atty; + + pub(in ::fmt) fn is_stdout() -> bool { + atty::is(atty::Stream::Stdout) + } + + pub(in ::fmt) fn is_stderr() -> bool { + atty::is(atty::Stream::Stderr) + } +} + +#[cfg(not(feature = "atty"))] +mod imp { + pub(in ::fmt) fn is_stdout() -> bool { + false + } + + pub(in ::fmt) fn is_stderr() -> bool { + false + } +} + +pub(in ::fmt) use self::imp::*; diff --git a/src/fmt/humantime/extern_impl.rs b/src/fmt/humantime/extern_impl.rs new file mode 100644 index 00000000..53c47f63 --- /dev/null +++ b/src/fmt/humantime/extern_impl.rs @@ -0,0 +1,84 @@ +use std::fmt; +use std::time::SystemTime; + +use humantime::{format_rfc3339_nanos, format_rfc3339_seconds}; + +use ::fmt::Formatter; + +pub(in ::fmt) mod pub_use_in_super { + pub use super::*; +} + +impl Formatter { + /// 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(SystemTime::now()) + } + + /// Get a [`PreciseTimestamp`] for the current date and time in UTC with nanos. + pub fn precise_timestamp(&self) -> PreciseTimestamp { + PreciseTimestamp(SystemTime::now()) + } +} + +/// 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(SystemTime); + +/// An [RFC3339] formatted timestamp with nanos. +/// +/// [RFC3339]: https://www.ietf.org/rfc/rfc3339.txt +#[derive(Debug)] +pub struct PreciseTimestamp(SystemTime); + +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::Display for Timestamp { + fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { + format_rfc3339_seconds(self.0).fmt(f) + } +} + +impl fmt::Display for PreciseTimestamp { + fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { + format_rfc3339_nanos(self.0).fmt(f) + } +} \ No newline at end of file diff --git a/src/fmt/humantime/mod.rs b/src/fmt/humantime/mod.rs new file mode 100644 index 00000000..c4f7599c --- /dev/null +++ b/src/fmt/humantime/mod.rs @@ -0,0 +1,11 @@ +/* +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 ::fmt) use self::imp::*; diff --git a/src/fmt/humantime/shim_impl.rs b/src/fmt/humantime/shim_impl.rs new file mode 100644 index 00000000..3f5d4e39 --- /dev/null +++ b/src/fmt/humantime/shim_impl.rs @@ -0,0 +1,7 @@ +/* +Timestamps aren't available when we don't have a `humantime` dependency. +*/ + +pub(in ::fmt) mod pub_use_in_super { + +} diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs new file mode 100644 index 00000000..58687dc2 --- /dev/null +++ b/src/fmt/mod.rs @@ -0,0 +1,286 @@ +//! Formatting for log records. +//! +//! This module contains a [`Formatter`] that can be used to format log records +//! into without needing temporary allocations. Usually you won't need to worry +//! 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; +//! +//! let mut builder = env_logger::Builder::new(); +//! +//! builder.format(|buf, record| { +//! writeln!(buf, "{}: {}", +//! 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::*; +use std::{io, fmt}; +use std::rc::Rc; +use std::cell::RefCell; + +mod termcolor; +mod atty; +mod humantime; + +use self::termcolor::{Buffer, BufferWriter}; +use self::atty::{is_stdout, is_stderr}; + +pub use self::humantime::pub_use_in_super::*; +pub use self::termcolor::pub_use_in_super::*; + +pub(super) mod pub_use_in_super { + pub use super::{Target, WriteStyle, Formatter}; + + #[cfg(feature = "termcolor")] + pub use super::Color; +} + +/// A formatter to write logs into. +/// +/// `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>, + write_style: WriteStyle, +} + +/// 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, + /// Try very hard to print styles. + Always, + /// Never print styles. + Never, +} + +impl Default for WriteStyle { + fn default() -> Self { + WriteStyle::Auto + } +} + +/// A terminal target with color awareness. +pub(crate) struct Writer { + inner: BufferWriter, + write_style: WriteStyle, +} + +impl Writer { + pub(crate) fn write_style(&self) -> WriteStyle { + self.write_style + } +} + +/// 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(), + write_style: Default::default(), + } + } + + /// 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 => { + if match self.target { + Target::Stderr => is_stderr(), + Target::Stdout => is_stdout(), + } { + WriteStyle::Auto + } else { + WriteStyle::Never + } + }, + color_choice => color_choice, + }; + + let writer = match self.target { + Target::Stderr => BufferWriter::stderr(color_choice), + Target::Stdout => BufferWriter::stdout(color_choice), + }; + + Writer { + inner: writer, + write_style: self.write_style, + } + } +} + +impl Default for Builder { + fn default() -> Self { + Builder::new() + } +} + +impl Formatter { + pub(crate) fn new(writer: &Writer) -> Self { + Formatter { + buf: Rc::new(RefCell::new(writer.inner.buffer())), + write_style: writer.write_style(), + } + } + + pub(crate) fn write_style(&self) -> WriteStyle { + self.write_style + } + + pub(crate) fn print(&self, writer: &Writer) -> io::Result<()> { + writer.inner.print(&self.buf.borrow()) + } + + pub(crate) fn clear(&mut self) { + self.buf.borrow_mut().clear() + } +} + +impl Write for Formatter { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.buf.borrow_mut().write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.buf.borrow_mut().flush() + } +} + +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() + } +} + +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", WriteStyle::Auto), + ("always", WriteStyle::Always), + ("never", WriteStyle::Never), + ]; + + for (input, expected) in inputs { + 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)); + } + } +} \ No newline at end of file diff --git a/src/fmt.rs b/src/fmt/termcolor/extern_impl.rs similarity index 60% rename from src/fmt.rs rename to src/fmt/termcolor/extern_impl.rs index 72198430..be0c1816 100644 --- a/src/fmt.rs +++ b/src/fmt/termcolor/extern_impl.rs @@ -1,75 +1,127 @@ -//! Formatting for log records. -//! -//! This module contains a [`Formatter`] that can be used to format log records -//! into without needing temporary allocations. Usually you won't need to worry -//! 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::*; -use std::{io, fmt}; -use std::rc::Rc; -use std::str::FromStr; use std::error::Error; +use std::borrow::Cow; +use std::fmt; +use std::io::{self, Write}; +use std::str::FromStr; use std::cell::RefCell; -use std::time::SystemTime; +use std::rc::Rc; use log::Level; -use termcolor::{self, ColorSpec, ColorChoice, Buffer, BufferWriter, WriteColor}; -use atty; -use humantime::{format_rfc3339_seconds, format_rfc3339_nanos}; +use termcolor::{self, ColorChoice, ColorSpec, WriteColor}; -/// A formatter to write logs into. -/// -/// `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>, - write_style: WriteStyle, +use ::WriteStyle; +use ::fmt::Formatter; + +pub(in ::fmt) mod pub_use_in_super { + 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::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), + }; + 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 ::fmt) struct BufferWriter(termcolor::BufferWriter); +pub(in ::fmt) struct Buffer(termcolor::Buffer); + +impl BufferWriter { + pub(in ::fmt) fn stderr(write_style: WriteStyle) -> Self { + BufferWriter(termcolor::BufferWriter::stderr(write_style.into_color_choice())) + } + + pub(in ::fmt) fn stdout(write_style: WriteStyle) -> Self { + BufferWriter(termcolor::BufferWriter::stdout(write_style.into_color_choice())) + } + + pub(in ::fmt) fn buffer(&self) -> Buffer { + Buffer(self.0.buffer()) + } + + pub(in ::fmt) fn print(&self, buf: &Buffer) -> io::Result<()> { + self.0.print(&buf.0) + } +} + +impl Buffer { + pub(in ::fmt) fn clear(&mut self) { + self.0.clear() + } + + pub(in ::fmt) fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + + pub(in ::fmt) fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } + + fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + self.0.set_color(spec) + } + + fn reset(&mut self) -> io::Result<()> { + self.0.reset() + } +} + +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. @@ -136,142 +188,10 @@ pub struct Style { /// /// [`Style::value`]: struct.Style.html#method.value pub struct StyledValue<'a, T> { - style: &'a Style, + style: Cow<'a, Style>, value: T, } -/// 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(SystemTime); - -/// An [RFC3339] formatted timestamp with nanos -/// -/// [RFC3339]: https://www.ietf.org/rfc/rfc3339.txt -#[derive(Debug)] -pub struct PreciseTimestamp(SystemTime); - -/// 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, - /// Try very hard to print styles. - Always, - /// Never print styles. - Never, -} - -impl Default for WriteStyle { - fn default() -> Self { - WriteStyle::Auto - } -} - -/// A terminal target with color awareness. -pub(crate) struct Writer { - inner: BufferWriter, - write_style: WriteStyle, -} - -impl Writer { - pub(crate) fn write_style(&self) -> WriteStyle { - self.write_style - } -} - -/// 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(), - write_style: Default::default(), - } - } - - /// 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 => { - if atty::is(match self.target { - Target::Stderr => atty::Stream::Stderr, - Target::Stdout => atty::Stream::Stdout, - }) { - ColorChoice::Auto - } else { - ColorChoice::Never - } - }, - 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 { - inner: writer, - write_style: self.write_style, - } - } -} - -impl Default for Builder { - fn default() -> Self { - Builder::new() - } -} - impl Style { /// Set the text color. /// @@ -403,113 +323,17 @@ impl Style { /// ``` pub fn value(&self, value: T) -> StyledValue { StyledValue { - style: &self, + style: Cow::Borrowed(&self), value } } -} - -impl Formatter { - pub(crate) fn new(writer: &Writer) -> Self { - Formatter { - buf: Rc::new(RefCell::new(writer.inner.buffer())), - write_style: writer.write_style(), - } - } - - pub(crate) fn write_style(&self) -> WriteStyle { - self.write_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(), - spec: ColorSpec::new(), + fn into_value(self, value: T) -> StyledValue<'static, T> { + StyledValue { + style: Cow::Owned(self), + value } } - - /// Get the default [`Style`] for the given 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::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), - }; - level_style - } - - /// 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(SystemTime::now()) - } - - /// Get a [`PreciseTimestamp`] for the current date and time in UTC with nanos. - pub fn precise_timestamp(&self) -> PreciseTimestamp { - PreciseTimestamp(SystemTime::now()) - } - - pub(crate) fn print(&self, writer: &Writer) -> io::Result<()> { - writer.inner.print(&self.buf.borrow()) - } - - pub(crate) fn clear(&mut self) { - self.buf.borrow_mut().clear() - } -} - -impl Write for Formatter { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buf.borrow_mut().write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.buf.borrow_mut().flush() - } } impl<'a, T> StyledValue<'a, T> { @@ -527,44 +351,6 @@ impl<'a, T> StyledValue<'a, T> { } } -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 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() @@ -594,18 +380,6 @@ impl_styled_value_fmt!( fmt::UpperExp, fmt::LowerExp); -impl fmt::Display for Timestamp { - fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { - format_rfc3339_seconds(self.0).fmt(f) - } -} - -impl fmt::Display for PreciseTimestamp { - fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { - format_rfc3339_nanos(self.0).fmt(f) - } -} - // The `Color` type is copied from https://github.com/BurntSushi/ripgrep/tree/master/termcolor /// The set of available colors for the terminal foreground/background. @@ -744,46 +518,10 @@ impl FromStr for Color { } } -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", WriteStyle::Auto), - ("always", WriteStyle::Always), - ("never", WriteStyle::Never), - ]; - - for (input, expected) in inputs { - 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)); - } - } - #[test] fn parse_color_name_valid() { let inputs = vec![ @@ -843,4 +581,4 @@ mod tests { assert_eq!(input, err.invalid()); } } -} +} \ No newline at end of file diff --git a/src/fmt/termcolor/mod.rs b/src/fmt/termcolor/mod.rs new file mode 100644 index 00000000..df0f7859 --- /dev/null +++ b/src/fmt/termcolor/mod.rs @@ -0,0 +1,12 @@ +/* +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_attr(feature = "termcolor", path = "extern_impl.rs")] +#[cfg_attr(not(feature = "termcolor"), path = "shim_impl.rs")] +mod imp; + +pub(in ::fmt) use self::imp::*; diff --git a/src/fmt/termcolor/shim_impl.rs b/src/fmt/termcolor/shim_impl.rs new file mode 100644 index 00000000..2ab3f7ca --- /dev/null +++ b/src/fmt/termcolor/shim_impl.rs @@ -0,0 +1,61 @@ +use std::io::{self, Write}; + +use fmt::{WriteStyle, Target}; + +pub(in ::fmt) mod pub_use_in_super { + +} + +pub(in ::fmt) struct BufferWriter { + target: Target, +} + +pub(in ::fmt) struct Buffer(Vec); + +impl BufferWriter { + pub(in ::fmt) fn stderr(_: WriteStyle) -> Self { + BufferWriter { + target: Target::Stderr, + } + } + + pub(in ::fmt) fn stdout(_: WriteStyle) -> Self { + BufferWriter { + target: Target::Stdout, + } + } + + pub(in ::fmt) fn buffer(&self) -> Buffer { + Buffer(Vec::new()) + } + + pub(in ::fmt) fn print(&self, buf: &Buffer) -> io::Result<()> { + match self.target { + Target::Stderr => { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + stderr.write_all(&buf.0) + }, + Target::Stdout => { + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + stdout.write_all(&buf.0) + }, + } + } +} + +impl Buffer { + pub(in ::fmt) fn clear(&mut self) { + self.0.clear(); + } + + pub(in ::fmt) fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.extend(buf); + Ok(buf.len()) + } + + pub(in ::fmt) fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 90058ee7..90d27df6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -215,8 +215,12 @@ #![deny(missing_debug_implementations, missing_docs, warnings)] extern crate log; + +#[cfg(feature = "termcolor")] extern crate termcolor; +#[cfg(feature = "humantime")] extern crate humantime; +#[cfg(feature = "atty")] extern crate atty; use std::env; @@ -231,7 +235,7 @@ use log::{Log, LevelFilter, Record, SetLoggerError, Metadata}; pub mod filter; pub mod fmt; -pub use self::fmt::{Target, WriteStyle, Color, Formatter}; +pub use self::fmt::pub_use_in_super::*; /// The default name for the environment variable to read filters from. pub const DEFAULT_FILTER_ENV: &'static str = "RUST_LOG"; @@ -319,23 +323,42 @@ impl Format { else { Box::new(move |buf, record| { let write_level = if self.default_format_level { - let level = record.level(); - let level_style = buf.default_level_style(level); - write!(buf, "{:>5} ", level_style.value(level)) + let level = { + #[cfg(feature = "termcolor")] + { + buf.default_styled_level(record.level()) + } + #[cfg(not(feature = "termcolor"))] + { + record.level() + } + }; + + write!(buf, "{:>5} ", level) } else { Ok(()) }; - let write_ts = if self.default_format_timestamp { - if self.default_format_timestamp_nanos { - let ts_nanos = buf.precise_timestamp(); - write!(buf, "{}: ", ts_nanos) - } else { - let ts = buf.timestamp(); - write!(buf, "{}: ", ts) + let write_ts = { + #[cfg(feature = "humantime")] + { + if self.default_format_timestamp { + if self.default_format_timestamp_nanos { + let ts_nanos = buf.precise_timestamp(); + write!(buf, "{}: ", ts_nanos) + } else { + let ts = buf.timestamp(); + write!(buf, "{}: ", ts) + } + } else { + Ok(()) + } + } + + #[cfg(not(feature = "humantime"))] + { + Ok(()) } - } else { - Ok(()) }; let default_format_module_path = (self.default_format_module_path, record.module_path());