diff --git a/Cargo.toml b/Cargo.toml index 43d1b996..ab73f6b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ publish = false # this branch contains breaking changes [dependencies] log = { git = "https://github.com/rust-lang-nursery/log.git", features = ["std"] } regex = { version = "0.2", optional = true } +termcolor = "0.3" [[test]] name = "regexp_filter" diff --git a/examples/custom_format.rs b/examples/custom_format.rs index 8ea99c82..b66a3c29 100644 --- a/examples/custom_format.rs +++ b/examples/custom_format.rs @@ -15,6 +15,7 @@ extern crate log; extern crate env_logger; use std::env; +use std::io::Write; fn init_logger() { let mut builder = env_logger::Builder::new(); diff --git a/examples/default.rs b/examples/default.rs new file mode 100644 index 00000000..ea2ea0b8 --- /dev/null +++ b/examples/default.rs @@ -0,0 +1,23 @@ +/*! +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' +``` +*/ + +#[macro_use] +extern crate log; +extern crate env_logger; + +fn main() { + env_logger::init_from_env("MY_LOG_LEVEL"); + + trace!("some trace log"); + debug!("some debug log"); + info!("some information log"); + warn!("some warning log"); + error!("some error log"); +} diff --git a/src/fmt.rs b/src/fmt.rs new file mode 100644 index 00000000..1b3c71c1 --- /dev/null +++ b/src/fmt.rs @@ -0,0 +1,93 @@ +//! 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`]. +//! +//! [`Formatter`]: struct.Formatter.html +//! [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html + +use std::io::prelude::*; +use std::io; +use std::fmt; + +use termcolor::{Color, ColorSpec, Buffer, WriteColor}; + +/// 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. +/// +/// [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html +pub struct Formatter { + buf: Buffer, +} + +/// A formatter with a particular style. +/// +/// Each call to `write` will apply the style before writing the output. +pub(crate) struct StyledFormatter { + buf: W, + spec: ColorSpec, +} + +impl Formatter { + pub(crate) fn new(buf: Buffer) -> Self { + Formatter { + buf: 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 + } + } + + pub(crate) fn as_ref(&self) -> &Buffer { + &self.buf + } + + pub(crate) fn clear(&mut self) { + self.buf.clear() + } +} + +impl Write for Formatter { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.buf.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.buf.flush() + } +} + +impl Write for StyledFormatter + where W: WriteColor +{ + fn write(&mut self, buf: &[u8]) -> io::Result { + self.buf.set_color(&self.spec)?; + + // Always try to reset the terminal style, even if writing failed + let write = self.buf.write(buf); + let reset = self.buf.reset(); + + write.and_then(|w| reset.map(|_| w)) + } + + fn flush(&mut self) -> io::Result<()> { + self.buf.flush() + } +} + +impl fmt::Debug for Formatter{ + fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { + f.debug_struct("Formatter").finish() + } +} diff --git a/src/lib.rs b/src/lib.rs index ab5540c2..a3021708 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,16 +142,21 @@ #![deny(missing_debug_implementations, missing_docs, warnings)] extern crate log; +extern crate termcolor; use std::env; use std::io::prelude::*; use std::io; use std::mem; -use std::fmt; +use std::cell::RefCell; -use log::{Log, LevelFilter, Record, SetLoggerError, Metadata}; +use log::{Log, LevelFilter, Level, Record, SetLoggerError, Metadata}; +use termcolor::{ColorChoice, Color, BufferWriter}; pub mod filter; +pub mod fmt; + +use self::fmt::Formatter; /// Log target, either stdout or stderr. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -183,9 +188,9 @@ pub enum Target { /// [`Logger::new()`]: #method.new /// [`Builder`]: struct.Builder.html pub struct Logger { + writer: BufferWriter, filter: filter::Filter, - format: Box io::Result<()> + Sync + Send>, - target: Target, + format: Box io::Result<()> + Sync + Send>, } /// `Builder` acts as builder for initializing a `Logger`. @@ -201,17 +206,15 @@ pub struct Logger { /// extern crate env_logger; /// /// use std::env; -/// use std::io; -/// use log::{Record, LevelFilter}; +/// use std::io::Write; +/// use log::LevelFilter; /// use env_logger::Builder; /// /// fn main() { -/// let format = |buf: &mut io::Write, record: &Record| { -/// writeln!(buf, "{} - {}", record.level(), record.args()) -/// }; -/// /// let mut builder = Builder::new(); -/// builder.format(format).filter(None, LevelFilter::Info); +/// +/// builder.format(|buf, record| writeln!(buf, "{} - {}", record.level(), record.args())) +/// .filter(None, LevelFilter::Info); /// /// if let Ok(rust_log) = env::var("RUST_LOG") { /// builder.parse(&rust_log); @@ -225,7 +228,7 @@ pub struct Logger { /// ``` pub struct Builder { filter: filter::Builder, - format: Box io::Result<()> + Sync + Send>, + format: Box io::Result<()> + Sync + Send>, target: Target, } @@ -234,9 +237,20 @@ impl Builder { pub fn new() -> Builder { Builder { filter: filter::Builder::new(), - format: Box::new(|buf: &mut Write, record: &Record| { - writeln!(buf, "{}:{}: {}", record.level(), - record.module_path(), record.args()) + format: Box::new(|buf, record| { + 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 write_level = write!(buf.color(level_color), "{}:", level); + let write_args = writeln!(buf, "{}: {}", record.module_path(), record.args()); + + write_level.and(write_args) }), target: Target::Stderr, } @@ -256,19 +270,18 @@ impl Builder { /// Sets the format function for formatting the log output. /// /// This function is called on each record logged and should format the - /// log record and output it to the given [`Write`] trait object. + /// log record and output it to the given [`Formatter`]. /// /// The format function is expected to output the string directly to the - /// [`Write`] trait object (rather than returning a [`String`]), so that - /// implementations can use the [`std::fmt`] macros to format and output - /// without intermediate heap allocations. The default `env_logger` - /// formatter takes advantage of this. + /// `Formatter` so that implementations can use the [`std::fmt`] macros + /// to format and output without intermediate heap allocations. The default + /// `env_logger` formatter takes advantage of this. /// - /// [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html + /// [`Formatter`]: struct.Formatter.html /// [`String`]: https://doc.rust-lang.org/stable/std/string/struct.String.html /// [`std::fmt`]: https://doc.rust-lang.org/std/fmt/index.html pub fn format(&mut self, format: F) -> &mut Self - where F: Fn(&mut Write, &Record) -> io::Result<()> + Sync + Send + where F: Fn(&mut Formatter, &Record) -> io::Result<()> + Sync + Send { self.format = Box::new(format); self @@ -323,10 +336,15 @@ impl Builder { /// Build an env logger. pub fn build(&mut self) -> Logger { + let writer = match self.target { + Target::Stderr => BufferWriter::stderr(ColorChoice::Always), + Target::Stdout => BufferWriter::stdout(ColorChoice::Always), + }; + Logger { + writer: writer, filter: self.filter.build(), format: mem::replace(&mut self.format, Box::new(|_, _| Ok(()))), - target: mem::replace(&mut self.target, Target::Stderr), } } } @@ -434,6 +452,10 @@ 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 { @@ -442,31 +464,56 @@ impl Log for Logger { } fn log(&self, record: &Record) { - let _ = match self.target { - Target::Stdout if self.matches(record) => (self.format)(&mut io::stdout(), record), - Target::Stderr if self.matches(record) => (self.format)(&mut io::stderr(), record), - _ => Ok(()), - }; + if self.matches(record) { + // Log records are written to a thread-local buffer before being printed + // to the terminal. We clear these buffers afterwards, but they aren't shrinked + // so will always at least have capacity for the largest log record formatted + // on that thread. + + thread_local! { + static FORMATTER: RefCell> = RefCell::new(None); + } + + FORMATTER.with(|tl_buf| { + let mut tl_buf = tl_buf.borrow_mut(); + + if tl_buf.is_none() { + *tl_buf = Some(Formatter::new(self.writer.buffer())); + } + + // 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)); + + // Always clear the buffer afterwards + formatter.clear(); + }); + } } fn flush(&self) {} } -impl fmt::Debug for Logger{ - fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { - f.debug_struct("Logger") +mod std_fmt_impls { + use std::fmt; + use super::*; + + impl fmt::Debug for Logger{ + fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { + f.debug_struct("Logger") + .field("filter", &self.filter) + .finish() + } + } + + impl fmt::Debug for Builder{ + fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { + f.debug_struct("Logger") .field("filter", &self.filter) .field("target", &self.target) .finish() - } -} - -impl fmt::Debug for Builder{ - fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { - f.debug_struct("Logger") - .field("filter", &self.filter) - .field("target", &self.target) - .finish() + } } }