diff --git a/tracing-subscriber/src/fmt/fmt_layer.rs b/tracing-subscriber/src/fmt/fmt_layer.rs index f17f3419d0..519dcfd3fb 100644 --- a/tracing-subscriber/src/fmt/fmt_layer.rs +++ b/tracing-subscriber/src/fmt/fmt_layer.rs @@ -1,6 +1,6 @@ use crate::{ field::RecordFields, - fmt::{format, FormatEvent, FormatFields, MakeWriter}, + fmt::{format, FormatEvent, FormatFields, MakeWriter, TestWriter}, layer::{self, Context, Scope}, registry::{LookupSpan, SpanRef}, }; @@ -181,6 +181,38 @@ impl Layer { _inner: self._inner, } } + + /// Configures the subscriber to support [`libtest`'s output capturing][capturing] when used in + /// unit tests. + /// + /// See [`TestWriter`] for additional details. + /// + /// # Examples + /// + /// Using [`TestWriter`] to let `cargo test` capture test output: + /// + /// ```rust + /// use std::io; + /// use tracing_subscriber::fmt; + /// + /// let layer = fmt::layer() + /// .with_test_writer(); + /// # // this is necessary for type inference. + /// # use tracing_subscriber::Layer as _; + /// # let _ = layer.with_subscriber(tracing_subscriber::registry::Registry::default()); + /// ``` + /// [capturing]: + /// https://doc.rust-lang.org/book/ch11-02-running-tests.html#showing-function-output + /// [`TestWriter`]: writer/struct.TestWriter.html + pub fn with_test_writer(self) -> Layer { + Layer { + fmt_fields: self.fmt_fields, + fmt_event: self.fmt_event, + fmt_span: self.fmt_span, + make_writer: TestWriter::default(), + _inner: self._inner, + } + } } impl Layer, W> diff --git a/tracing-subscriber/src/fmt/mod.rs b/tracing-subscriber/src/fmt/mod.rs index bb58e7415c..c3f256d69c 100644 --- a/tracing-subscriber/src/fmt/mod.rs +++ b/tracing-subscriber/src/fmt/mod.rs @@ -143,7 +143,7 @@ use crate::{ pub use self::{ format::{format, FormatEvent, FormatFields}, time::time, - writer::MakeWriter, + writer::{MakeWriter, TestWriter}, }; /// A `Subscriber` that logs formatted representations of `tracing` events. @@ -873,6 +873,37 @@ impl SubscriberBuilder { inner: self.inner.with_writer(make_writer), } } + + /// Configures the subscriber to support [`libtest`'s output capturing][capturing] when used in + /// unit tests. + /// + /// See [`TestWriter`] for additional details. + /// + /// # Examples + /// + /// Using [`TestWriter`] to let `cargo test` capture test output. Note that we do not install it + /// globally as it may cause conflicts. + /// + /// ```rust + /// use tracing_subscriber::fmt; + /// use tracing::subscriber; + /// + /// subscriber::set_default( + /// fmt() + /// .with_test_writer() + /// .finish() + /// ); + /// ``` + /// + /// [capturing]: + /// https://doc.rust-lang.org/book/ch11-02-running-tests.html#showing-function-output + /// [`TestWriter`]: writer/struct.TestWriter.html + pub fn with_test_writer(self) -> SubscriberBuilder { + SubscriberBuilder { + filter: self.filter, + inner: self.inner.with_writer(TestWriter::default()), + } + } } /// Install a global tracing subscriber that listens for events and diff --git a/tracing-subscriber/src/fmt/writer.rs b/tracing-subscriber/src/fmt/writer.rs index c0817da4bf..2b93726a32 100644 --- a/tracing-subscriber/src/fmt/writer.rs +++ b/tracing-subscriber/src/fmt/writer.rs @@ -54,6 +54,55 @@ where } } +/// A writer intended to support [`libtest`'s output capturing][capturing] for use in unit tests. +/// +/// `TestWriter` is used by [`fmt::Subscriber`] or [`fmt::Layer`] to enable capturing support. +/// +/// `cargo test` can only capture output from the standard library's [`print!`] macro. See +/// [`libtest`'s output capturing][capturing] for more details about output capturing. +/// +/// Writing to [`io::stdout`] and [`io::stderr`] produces the same results as using +/// [`libtest`'s `--nocapture` option][nocapture] which may make the results look unreadable. +/// +/// [`fmt::Subscriber`]: ../struct.Subscriber.html +/// [`fmt::Layer`]: ../struct.Layer.html +/// [capturing]: https://doc.rust-lang.org/book/ch11-02-running-tests.html#showing-function-output +/// [nocapture]: https://doc.rust-lang.org/cargo/commands/cargo-test.html +/// [`io::stdout`]: https://doc.rust-lang.org/std/io/fn.stdout.html +/// [`io::stderr`]: https://doc.rust-lang.org/std/io/fn.stderr.html +/// [`print!`]: https://doc.rust-lang.org/std/macro.print.html +#[derive(Default, Debug)] +pub struct TestWriter { + _p: (), +} + +impl TestWriter { + /// Returns a new `TestWriter` with the default configuration. + pub fn new() -> Self { + Self::default() + } +} + +impl io::Write for TestWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + let out_str = String::from_utf8_lossy(buf); + print!("{}", out_str); + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl MakeWriter for TestWriter { + type Writer = Self; + + fn make_writer(&self) -> Self::Writer { + Self::default() + } +} + #[cfg(test)] mod test { use super::MakeWriter;