Skip to content

Commit 8fa6240

Browse files
authored
Support capturing for cargo test (#127)
* allow logs to be captured for cargo test
1 parent 811db32 commit 8fa6240

File tree

6 files changed

+159
-38
lines changed

6 files changed

+159
-38
lines changed

README.md

+9-3
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Tests can use the `env_logger` crate to see log messages generated during that t
5252
log = "0.4.0"
5353

5454
[dev-dependencies]
55-
env_logger = { version = "0.6.0", default-features = false }
55+
env_logger = "0.6.0"
5656
```
5757

5858
```rust
@@ -69,16 +69,22 @@ mod tests {
6969
use super::*;
7070
extern crate env_logger;
7171

72+
fn init() {
73+
let _ = env_logger::builder().is_test(true).try_init();
74+
}
75+
7276
#[test]
7377
fn it_adds_one() {
74-
let _ = env_logger::try_init();
78+
init();
79+
7580
info!("can log from the test too");
7681
assert_eq!(3, add_one(2));
7782
}
7883

7984
#[test]
8085
fn it_handles_negative_numbers() {
81-
let _ = env_logger::try_init();
86+
init();
87+
8288
info!("logging from another test");
8389
assert_eq!(-7, add_one(-8));
8490
}

src/fmt/writer/mod.rs

+15-7
Original file line numberDiff line numberDiff line change
@@ -70,21 +70,23 @@ impl Writer {
7070
pub(crate) struct Builder {
7171
target: Target,
7272
write_style: WriteStyle,
73+
is_test: bool,
7374
built: bool,
7475
}
7576

7677
impl Builder {
7778
/// Initialize the writer builder with defaults.
78-
pub fn new() -> Self {
79+
pub(crate) fn new() -> Self {
7980
Builder {
8081
target: Default::default(),
8182
write_style: Default::default(),
83+
is_test: false,
8284
built: false,
8385
}
8486
}
8587

8688
/// Set the target to write to.
87-
pub fn target(&mut self, target: Target) -> &mut Self {
89+
pub(crate) fn target(&mut self, target: Target) -> &mut Self {
8890
self.target = target;
8991
self
9092
}
@@ -94,18 +96,24 @@ impl Builder {
9496
/// See the [Disabling colors] section for more details.
9597
///
9698
/// [Disabling colors]: ../index.html#disabling-colors
97-
pub fn parse(&mut self, write_style: &str) -> &mut Self {
99+
pub(crate) fn parse_write_style(&mut self, write_style: &str) -> &mut Self {
98100
self.write_style(parse_write_style(write_style))
99101
}
100102

101103
/// Whether or not to print style characters when writing.
102-
pub fn write_style(&mut self, write_style: WriteStyle) -> &mut Self {
104+
pub(crate) fn write_style(&mut self, write_style: WriteStyle) -> &mut Self {
103105
self.write_style = write_style;
104106
self
105107
}
106108

109+
/// Whether or not to capture logs for `cargo test`.
110+
pub(crate) fn is_test(&mut self, is_test: bool) -> &mut Self {
111+
self.is_test = is_test;
112+
self
113+
}
114+
107115
/// Build a terminal writer.
108-
pub fn build(&mut self) -> Writer {
116+
pub(crate) fn build(&mut self) -> Writer {
109117
assert!(!self.built, "attempt to re-use consumed builder");
110118
self.built = true;
111119

@@ -124,8 +132,8 @@ impl Builder {
124132
};
125133

126134
let writer = match self.target {
127-
Target::Stderr => BufferWriter::stderr(color_choice),
128-
Target::Stdout => BufferWriter::stdout(color_choice),
135+
Target::Stderr => BufferWriter::stderr(self.is_test, color_choice),
136+
Target::Stdout => BufferWriter::stdout(self.is_test, color_choice),
129137
};
130138

131139
Writer {

src/fmt/writer/termcolor/extern_impl.rs

+63-16
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use log::Level;
88
use termcolor::{self, ColorChoice, ColorSpec, WriteColor};
99

1010
use ::WriteStyle;
11-
use ::fmt::Formatter;
11+
use ::fmt::{Formatter, Target};
1212

1313
pub(in ::fmt::writer) mod glob {
1414
pub use super::*;
@@ -69,51 +69,98 @@ impl Formatter {
6969
}
7070
}
7171

72-
pub(in ::fmt::writer) struct BufferWriter(termcolor::BufferWriter);
73-
pub(in ::fmt) struct Buffer(termcolor::Buffer);
72+
pub(in ::fmt::writer) struct BufferWriter {
73+
inner: termcolor::BufferWriter,
74+
test_target: Option<Target>,
75+
}
76+
77+
pub(in ::fmt) struct Buffer {
78+
inner: termcolor::Buffer,
79+
test_target: Option<Target>,
80+
}
7481

7582
impl BufferWriter {
76-
pub(in ::fmt::writer) fn stderr(write_style: WriteStyle) -> Self {
77-
BufferWriter(termcolor::BufferWriter::stderr(write_style.into_color_choice()))
83+
pub(in ::fmt::writer) fn stderr(is_test: bool, write_style: WriteStyle) -> Self {
84+
BufferWriter {
85+
inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()),
86+
test_target: if is_test {
87+
Some(Target::Stderr)
88+
} else {
89+
None
90+
},
91+
}
7892
}
7993

80-
pub(in ::fmt::writer) fn stdout(write_style: WriteStyle) -> Self {
81-
BufferWriter(termcolor::BufferWriter::stdout(write_style.into_color_choice()))
94+
pub(in ::fmt::writer) fn stdout(is_test: bool, write_style: WriteStyle) -> Self {
95+
BufferWriter {
96+
inner: termcolor::BufferWriter::stdout(write_style.into_color_choice()),
97+
test_target: if is_test {
98+
Some(Target::Stdout)
99+
} else {
100+
None
101+
},
102+
}
82103
}
83104

84105
pub(in ::fmt::writer) fn buffer(&self) -> Buffer {
85-
Buffer(self.0.buffer())
106+
Buffer {
107+
inner: self.inner.buffer(),
108+
test_target: self.test_target,
109+
}
86110
}
87111

88112
pub(in ::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> {
89-
self.0.print(&buf.0)
113+
if let Some(target) = self.test_target {
114+
// This impl uses the `eprint` and `print` macros
115+
// instead of `termcolor`'s buffer.
116+
// This is so their output can be captured by `cargo test`
117+
let log = String::from_utf8_lossy(buf.bytes());
118+
119+
match target {
120+
Target::Stderr => eprint!("{}", log),
121+
Target::Stdout => print!("{}", log),
122+
}
123+
124+
Ok(())
125+
} else {
126+
self.inner.print(&buf.inner)
127+
}
90128
}
91129
}
92130

93131
impl Buffer {
94132
pub(in ::fmt) fn clear(&mut self) {
95-
self.0.clear()
133+
self.inner.clear()
96134
}
97135

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

102140
pub(in ::fmt) fn flush(&mut self) -> io::Result<()> {
103-
self.0.flush()
141+
self.inner.flush()
104142
}
105143

106-
#[cfg(test)]
107144
pub(in ::fmt) fn bytes(&self) -> &[u8] {
108-
self.0.as_slice()
145+
self.inner.as_slice()
109146
}
110147

111148
fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
112-
self.0.set_color(spec)
149+
// Ignore styles for test captured logs because they can't be printed
150+
if self.test_target.is_none() {
151+
self.inner.set_color(spec)
152+
} else {
153+
Ok(())
154+
}
113155
}
114156

115157
fn reset(&mut self) -> io::Result<()> {
116-
self.0.reset()
158+
// Ignore styles for test captured logs because they can't be printed
159+
if self.test_target.is_none() {
160+
self.inner.reset()
161+
} else {
162+
Ok(())
163+
}
117164
}
118165
}
119166

src/fmt/writer/termcolor/shim_impl.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ pub(in ::fmt::writer) struct BufferWriter {
1313
pub(in ::fmt) struct Buffer(Vec<u8>);
1414

1515
impl BufferWriter {
16-
pub(in ::fmt::writer) fn stderr(_: WriteStyle) -> Self {
16+
pub(in ::fmt::writer) fn stderr(_is_test: bool, _write_style: WriteStyle) -> Self {
1717
BufferWriter {
1818
target: Target::Stderr,
1919
}
2020
}
2121

22-
pub(in ::fmt::writer) fn stdout(_: WriteStyle) -> Self {
22+
pub(in ::fmt::writer) fn stdout(_is_test: bool, _write_style: WriteStyle) -> Self {
2323
BufferWriter {
2424
target: Target::Stdout,
2525
}

src/lib.rs

+69-9
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,33 @@
139139
//! * `error,hello=warn/[0-9]scopes` turn on global error logging and also
140140
//! warn for hello. In both cases the log message must include a single digit
141141
//! number followed by 'scopes'.
142+
//!
143+
//! ## Capturing logs in tests
144+
//!
145+
//! Records logged during `cargo test` will not be captured by the test harness by default.
146+
//! The [`Builder::is_test`] method can be used in unit tests to ensure logs will be captured:
147+
//!
148+
//! ```
149+
//! # #[macro_use] extern crate log;
150+
//! # extern crate env_logger;
151+
//! # fn main() {}
152+
//! #[cfg(test)]
153+
//! mod tests {
154+
//! fn init() {
155+
//! let _ = env_logger::builder().is_test(true).try_init();
156+
//! }
157+
//!
158+
//! #[test]
159+
//! fn it_works() {
160+
//! info!("This record will be captured by `cargo test`");
161+
//!
162+
//! assert_eq!(2, 1 + 1);
163+
//! }
164+
//! }
165+
//! ```
166+
//!
167+
//! Enabling test capturing comes at the expense of color and other style support
168+
//! and may have performance implications.
142169
//!
143170
//! ## Disabling colors
144171
//!
@@ -157,9 +184,7 @@
157184
//! The following example excludes the timestamp from the log output:
158185
//!
159186
//! ```
160-
//! use env_logger::Builder;
161-
//!
162-
//! Builder::from_default_env()
187+
//! env_logger::builder()
163188
//! .default_format_timestamp(false)
164189
//! .init();
165190
//! ```
@@ -180,9 +205,8 @@
180205
//!
181206
//! ```
182207
//! use std::io::Write;
183-
//! use env_logger::Builder;
184208
//!
185-
//! Builder::from_default_env()
209+
//! env_logger::builder()
186210
//! .format(|buf, record| {
187211
//! writeln!(buf, "{}: {}", record.level(), record.args())
188212
//! })
@@ -199,13 +223,14 @@
199223
//! isn't set:
200224
//!
201225
//! ```
202-
//! use env_logger::{Builder, Env};
226+
//! use env_logger::Env;
203227
//!
204-
//! Builder::from_env(Env::default().default_filter_or("warn")).init();
228+
//! env_logger::from_env(Env::default().default_filter_or("warn")).init();
205229
//! ```
206230
//!
207231
//! [log-crate-url]: https://docs.rs/log/
208232
//! [`Builder`]: struct.Builder.html
233+
//! [`Builder::is_test`]: struct.Builder.html#method.is_test
209234
//! [`Env`]: struct.Env.html
210235
//! [`fmt`]: fmt/index.html
211236
@@ -404,7 +429,7 @@ impl Builder {
404429
let env = env.into();
405430

406431
if let Some(s) = env.get_filter() {
407-
builder.parse(&s);
432+
builder.parse_filters(&s);
408433
}
409434

410435
if let Some(s) = env.get_write_style() {
@@ -579,7 +604,16 @@ impl Builder {
579604
/// environment variable.
580605
///
581606
/// See the module documentation for more details.
607+
#[deprecated(since = "0.6.0", note = "use `parse_filters` instead.")]
582608
pub fn parse(&mut self, filters: &str) -> &mut Self {
609+
self.parse_filters(filters)
610+
}
611+
612+
/// Parses the directives string in the same form as the `RUST_LOG`
613+
/// environment variable.
614+
///
615+
/// See the module documentation for more details.
616+
pub fn parse_filters(&mut self, filters: &str) -> &mut Self {
583617
self.filter.parse(filters);
584618
self
585619
}
@@ -630,7 +664,16 @@ impl Builder {
630664
///
631665
/// See the module documentation for more details.
632666
pub fn parse_write_style(&mut self, write_style: &str) -> &mut Self {
633-
self.writer.parse(write_style);
667+
self.writer.parse_write_style(write_style);
668+
self
669+
}
670+
671+
/// Sets whether or not the logger will be used in unit tests.
672+
///
673+
/// If `is_test` is `true` then the logger will allow the testing framework to
674+
/// capture log records rather than printing them to the terminal directly.
675+
pub fn is_test(&mut self, is_test: bool) -> &mut Self {
676+
self.writer.is_test(is_test);
634677
self
635678
}
636679

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

1114+
/// Create a new builder with the default environment variables.
1115+
///
1116+
/// The builder can be configured before being initialized.
1117+
pub fn builder() -> Builder {
1118+
Builder::from_default_env()
1119+
}
1120+
1121+
/// Create a builder from the given environment variables.
1122+
///
1123+
/// The builder can be configured before being initialized.
1124+
pub fn from_env<'a, E>(env: E) -> Builder
1125+
where
1126+
E: Into<Env<'a>>
1127+
{
1128+
Builder::from_env(env)
1129+
}
1130+
10711131
#[cfg(test)]
10721132
mod tests {
10731133
use super::*;

tests/init-twice-retains-filter.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ fn main() {
1515
// Init again using a different max level
1616
// This shouldn't clobber the level that was previously set
1717
env_logger::Builder::new()
18-
.parse("info")
18+
.parse_filters("info")
1919
.try_init()
2020
.unwrap_err();
2121

0 commit comments

Comments
 (0)