Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support capturing for cargo test #127

Merged
merged 2 commits into from
Mar 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Tests can use the `env_logger` crate to see log messages generated during that t
log = "0.4.0"

[dev-dependencies]
env_logger = { version = "0.6.0", default-features = false }
env_logger = "0.6.0"
```

```rust
Expand All @@ -69,16 +69,22 @@ mod tests {
use super::*;
extern crate env_logger;

fn init() {
let _ = env_logger::builder().is_test(true).try_init();
}

#[test]
fn it_adds_one() {
let _ = env_logger::try_init();
init();

info!("can log from the test too");
assert_eq!(3, add_one(2));
}

#[test]
fn it_handles_negative_numbers() {
let _ = env_logger::try_init();
init();

info!("logging from another test");
assert_eq!(-7, add_one(-8));
}
Expand Down
22 changes: 15 additions & 7 deletions src/fmt/writer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,21 +70,23 @@ impl Writer {
pub(crate) struct Builder {
target: Target,
write_style: WriteStyle,
is_test: bool,
built: bool,
}

impl Builder {
/// Initialize the writer builder with defaults.
pub fn new() -> Self {
pub(crate) fn new() -> Self {
Builder {
target: Default::default(),
write_style: Default::default(),
is_test: false,
built: false,
}
}

/// Set the target to write to.
pub fn target(&mut self, target: Target) -> &mut Self {
pub(crate) fn target(&mut self, target: Target) -> &mut Self {
self.target = target;
self
}
Expand All @@ -94,18 +96,24 @@ impl Builder {
/// See the [Disabling colors] section for more details.
///
/// [Disabling colors]: ../index.html#disabling-colors
pub fn parse(&mut self, write_style: &str) -> &mut Self {
pub(crate) fn parse_write_style(&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 {
pub(crate) fn write_style(&mut self, write_style: WriteStyle) -> &mut Self {
self.write_style = write_style;
self
}

/// Whether or not to capture logs for `cargo test`.
pub(crate) fn is_test(&mut self, is_test: bool) -> &mut Self {
self.is_test = is_test;
self
}

/// Build a terminal writer.
pub fn build(&mut self) -> Writer {
pub(crate) fn build(&mut self) -> Writer {
assert!(!self.built, "attempt to re-use consumed builder");
self.built = true;

Expand All @@ -124,8 +132,8 @@ impl Builder {
};

let writer = match self.target {
Target::Stderr => BufferWriter::stderr(color_choice),
Target::Stdout => BufferWriter::stdout(color_choice),
Target::Stderr => BufferWriter::stderr(self.is_test, color_choice),
Target::Stdout => BufferWriter::stdout(self.is_test, color_choice),
};

Writer {
Expand Down
79 changes: 63 additions & 16 deletions src/fmt/writer/termcolor/extern_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use log::Level;
use termcolor::{self, ColorChoice, ColorSpec, WriteColor};

use ::WriteStyle;
use ::fmt::Formatter;
use ::fmt::{Formatter, Target};

pub(in ::fmt::writer) mod glob {
pub use super::*;
Expand Down Expand Up @@ -69,51 +69,98 @@ impl Formatter {
}
}

pub(in ::fmt::writer) struct BufferWriter(termcolor::BufferWriter);
pub(in ::fmt) struct Buffer(termcolor::Buffer);
pub(in ::fmt::writer) struct BufferWriter {
inner: termcolor::BufferWriter,
test_target: Option<Target>,
}

pub(in ::fmt) struct Buffer {
inner: termcolor::Buffer,
test_target: Option<Target>,
}

impl BufferWriter {
pub(in ::fmt::writer) fn stderr(write_style: WriteStyle) -> Self {
BufferWriter(termcolor::BufferWriter::stderr(write_style.into_color_choice()))
pub(in ::fmt::writer) fn stderr(is_test: bool, write_style: WriteStyle) -> Self {
BufferWriter {
inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()),
test_target: if is_test {
Some(Target::Stderr)
} else {
None
},
}
}

pub(in ::fmt::writer) fn stdout(write_style: WriteStyle) -> Self {
BufferWriter(termcolor::BufferWriter::stdout(write_style.into_color_choice()))
pub(in ::fmt::writer) fn stdout(is_test: bool, write_style: WriteStyle) -> Self {
BufferWriter {
inner: termcolor::BufferWriter::stdout(write_style.into_color_choice()),
test_target: if is_test {
Some(Target::Stdout)
} else {
None
},
}
}

pub(in ::fmt::writer) fn buffer(&self) -> Buffer {
Buffer(self.0.buffer())
Buffer {
inner: self.inner.buffer(),
test_target: self.test_target,
}
}

pub(in ::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> {
self.0.print(&buf.0)
if let Some(target) = self.test_target {
// This impl uses the `eprint` and `print` macros
// instead of `termcolor`'s buffer.
// This is so their output can be captured by `cargo test`
let log = String::from_utf8_lossy(buf.bytes());

match target {
Target::Stderr => eprint!("{}", log),
Target::Stdout => print!("{}", log),
}

Ok(())
} else {
self.inner.print(&buf.inner)
}
}
}

impl Buffer {
pub(in ::fmt) fn clear(&mut self) {
self.0.clear()
self.inner.clear()
}

pub(in ::fmt) fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
self.inner.write(buf)
}

pub(in ::fmt) fn flush(&mut self) -> io::Result<()> {
self.0.flush()
self.inner.flush()
}

#[cfg(test)]
pub(in ::fmt) fn bytes(&self) -> &[u8] {
self.0.as_slice()
self.inner.as_slice()
}

fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
self.0.set_color(spec)
// Ignore styles for test captured logs because they can't be printed
if self.test_target.is_none() {
self.inner.set_color(spec)
} else {
Ok(())
}
}

fn reset(&mut self) -> io::Result<()> {
self.0.reset()
// Ignore styles for test captured logs because they can't be printed
if self.test_target.is_none() {
self.inner.reset()
} else {
Ok(())
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/fmt/writer/termcolor/shim_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ pub(in ::fmt::writer) struct BufferWriter {
pub(in ::fmt) struct Buffer(Vec<u8>);

impl BufferWriter {
pub(in ::fmt::writer) fn stderr(_: WriteStyle) -> Self {
pub(in ::fmt::writer) fn stderr(_is_test: bool, _write_style: WriteStyle) -> Self {
BufferWriter {
target: Target::Stderr,
}
}

pub(in ::fmt::writer) fn stdout(_: WriteStyle) -> Self {
pub(in ::fmt::writer) fn stdout(_is_test: bool, _write_style: WriteStyle) -> Self {
BufferWriter {
target: Target::Stdout,
}
Expand Down
78 changes: 69 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,33 @@
//! * `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'.
//!
//! ## Capturing logs in tests
//!
//! Records logged during `cargo test` will not be captured by the test harness by default.
//! The [`Builder::is_test`] method can be used in unit tests to ensure logs will be captured:
//!
//! ```
//! # #[macro_use] extern crate log;
//! # extern crate env_logger;
//! # fn main() {}
//! #[cfg(test)]
//! mod tests {
//! fn init() {
//! let _ = env_logger::builder().is_test(true).try_init();
//! }
//!
//! #[test]
//! fn it_works() {
//! info!("This record will be captured by `cargo test`");
//!
//! assert_eq!(2, 1 + 1);
//! }
//! }
//! ```
//!
//! Enabling test capturing comes at the expense of color and other style support
//! and may have performance implications.
//!
//! ## Disabling colors
//!
Expand All @@ -157,9 +184,7 @@
//! The following example excludes the timestamp from the log output:
//!
//! ```
//! use env_logger::Builder;
//!
//! Builder::from_default_env()
//! env_logger::builder()
//! .default_format_timestamp(false)
//! .init();
//! ```
Expand All @@ -180,9 +205,8 @@
//!
//! ```
//! use std::io::Write;
//! use env_logger::Builder;
//!
//! Builder::from_default_env()
//! env_logger::builder()
//! .format(|buf, record| {
//! writeln!(buf, "{}: {}", record.level(), record.args())
//! })
Expand All @@ -199,13 +223,14 @@
//! isn't set:
//!
//! ```
//! use env_logger::{Builder, Env};
//! use env_logger::Env;
//!
//! Builder::from_env(Env::default().default_filter_or("warn")).init();
//! env_logger::from_env(Env::default().default_filter_or("warn")).init();
//! ```
//!
//! [log-crate-url]: https://docs.rs/log/
//! [`Builder`]: struct.Builder.html
//! [`Builder::is_test`]: struct.Builder.html#method.is_test
//! [`Env`]: struct.Env.html
//! [`fmt`]: fmt/index.html

Expand Down Expand Up @@ -404,7 +429,7 @@ impl Builder {
let env = env.into();

if let Some(s) = env.get_filter() {
builder.parse(&s);
builder.parse_filters(&s);
}

if let Some(s) = env.get_write_style() {
Expand Down Expand Up @@ -579,7 +604,16 @@ impl Builder {
/// environment variable.
///
/// See the module documentation for more details.
#[deprecated(since = "0.6.0", note = "use `parse_filters` instead.")]
pub fn parse(&mut self, filters: &str) -> &mut Self {
self.parse_filters(filters)
}

/// Parses the directives string in the same form as the `RUST_LOG`
/// environment variable.
///
/// See the module documentation for more details.
pub fn parse_filters(&mut self, filters: &str) -> &mut Self {
self.filter.parse(filters);
self
}
Expand Down Expand Up @@ -630,7 +664,16 @@ impl Builder {
///
/// See the module documentation for more details.
pub fn parse_write_style(&mut self, write_style: &str) -> &mut Self {
self.writer.parse(write_style);
self.writer.parse_write_style(write_style);
self
}

/// Sets whether or not the logger will be used in unit tests.
///
/// If `is_test` is `true` then the logger will allow the testing framework to
/// capture log records rather than printing them to the terminal directly.
pub fn is_test(&mut self, is_test: bool) -> &mut Self {
self.writer.is_test(is_test);
self
}

Expand Down Expand Up @@ -1068,6 +1111,23 @@ where
try_init_from_env(env).expect("env_logger::init_from_env should not be called after logger initialized");
}

/// Create a new builder with the default environment variables.
///
/// The builder can be configured before being initialized.
pub fn builder() -> Builder {
Builder::from_default_env()
}

/// Create a builder from the given environment variables.
///
/// The builder can be configured before being initialized.
pub fn from_env<'a, E>(env: E) -> Builder
where
E: Into<Env<'a>>
{
Builder::from_env(env)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion tests/init-twice-retains-filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn main() {
// Init again using a different max level
// This shouldn't clobber the level that was previously set
env_logger::Builder::new()
.parse("info")
.parse_filters("info")
.try_init()
.unwrap_err();

Expand Down