From 8bdc6c367dbfa7964b488d0c1e4b1c1b8628aa67 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Mon, 2 Nov 2020 13:20:46 -0800 Subject: [PATCH] subscriber: add `Pretty` formatter (backports #1067) (#1080) This backports PR #1067 to v0.1.x. Since this has already been approved on master, I'm just going to go ahead and merge it when CI passes. ## Motivation Currently, the `tracing_subscriber::fmt` module contains only single-line event formatters. Some users have requested a human-readable, pretty-printing event formatter optimized for aesthetics. ## Solution This branch adds a new `Pretty` formatter which uses an _excessively_ pretty output format. It's neither compact, single-line oriented, nor easily machine-readable, but it looks _quite_ nice, in my opinion. This is well suited for local development or potentially for user-facing logs in a CLI application. Additionally, I tried to improve the docs for the different formatters currently provided, including example output. Check out [the Netlify preview][1]! [1]: https://deploy-preview-1067--tracing-rs.netlify.app/tracing_subscriber/fmt/index.html#formatters Signed-off-by: Eliza Weisman --- examples/examples/fmt-json.rs | 21 ++ examples/examples/fmt-pretty.rs | 23 ++ examples/examples/fmt.rs | 13 +- examples/examples/fmt/yak_shave.rs | 24 +- tracing-subscriber/src/fmt/fmt_layer.rs | 13 + tracing-subscriber/src/fmt/format/mod.rs | 35 +- tracing-subscriber/src/fmt/format/pretty.rs | 339 ++++++++++++++++++++ tracing-subscriber/src/fmt/mod.rs | 170 +++++++++- tracing-subscriber/src/lib.rs | 6 + 9 files changed, 607 insertions(+), 37 deletions(-) create mode 100644 examples/examples/fmt-json.rs create mode 100644 examples/examples/fmt-pretty.rs create mode 100644 tracing-subscriber/src/fmt/format/pretty.rs diff --git a/examples/examples/fmt-json.rs b/examples/examples/fmt-json.rs new file mode 100644 index 0000000000..a0771b9809 --- /dev/null +++ b/examples/examples/fmt-json.rs @@ -0,0 +1,21 @@ +#![deny(rust_2018_idioms)] +#[path = "fmt/yak_shave.rs"] +mod yak_shave; + +fn main() { + tracing_subscriber::fmt() + .json() + .with_max_level(tracing::Level::TRACE) + .with_current_span(false) + .init(); + + let number_of_yaks = 3; + // this creates a new event, outside of any spans. + tracing::info!(number_of_yaks, "preparing to shave yaks"); + + let number_shaved = yak_shave::shave_all(number_of_yaks); + tracing::info!( + all_yaks_shaved = number_shaved == number_of_yaks, + "yak shaving completed" + ); +} diff --git a/examples/examples/fmt-pretty.rs b/examples/examples/fmt-pretty.rs new file mode 100644 index 0000000000..570b5fb71a --- /dev/null +++ b/examples/examples/fmt-pretty.rs @@ -0,0 +1,23 @@ +#![deny(rust_2018_idioms)] +#[path = "fmt/yak_shave.rs"] +mod yak_shave; + +fn main() { + tracing_subscriber::fmt() + .pretty() + .with_thread_names(true) + // enable everything + .with_max_level(tracing::Level::TRACE) + // sets this to be the default, global collector for this application. + .init(); + + let number_of_yaks = 3; + // this creates a new event, outside of any spans. + tracing::info!(number_of_yaks, "preparing to shave yaks"); + + let number_shaved = yak_shave::shave_all(number_of_yaks); + tracing::info!( + all_yaks_shaved = number_shaved == number_of_yaks, + "yak shaving completed" + ); +} diff --git a/examples/examples/fmt.rs b/examples/examples/fmt.rs index 5ae5cbf854..a990821df4 100644 --- a/examples/examples/fmt.rs +++ b/examples/examples/fmt.rs @@ -1,23 +1,20 @@ #![deny(rust_2018_idioms)] -use tracing::{info, Level}; - #[path = "fmt/yak_shave.rs"] mod yak_shave; fn main() { tracing_subscriber::fmt() - // all spans/events with a level higher than DEBUG (e.g, info, warn, etc.) - // will be written to stdout. - .with_max_level(Level::DEBUG) - // sets this to be the default, global subscriber for this application. + // enable everything + .with_max_level(tracing::Level::TRACE) + // sets this to be the default, global collector for this application. .init(); let number_of_yaks = 3; // this creates a new event, outside of any spans. - info!(number_of_yaks, "preparing to shave yaks"); + tracing::info!(number_of_yaks, "preparing to shave yaks"); let number_shaved = yak_shave::shave_all(number_of_yaks); - info!( + tracing::info!( all_yaks_shaved = number_shaved == number_of_yaks, "yak shaving completed." ); diff --git a/examples/examples/fmt/yak_shave.rs b/examples/examples/fmt/yak_shave.rs index 94854b8b8a..2e3a2c478f 100644 --- a/examples/examples/fmt/yak_shave.rs +++ b/examples/examples/fmt/yak_shave.rs @@ -1,38 +1,38 @@ use std::{error::Error, io}; -use tracing::{debug, error, info, span, warn, Level}; +use tracing::{debug, error, info, span, trace, warn, Level}; // the `#[tracing::instrument]` attribute creates and enters a span // every time the instrumented function is called. The span is named after the // the function or method. Paramaters passed to the function are recorded as fields. #[tracing::instrument] pub fn shave(yak: usize) -> Result<(), Box> { - // this creates an event at the DEBUG log level with two fields: + // this creates an event at the TRACE log level with two fields: // - `excitement`, with the key "excitement" and the value "yay!" // - `message`, with the key "message" and the value "hello! I'm gonna shave a yak." // // unlike other fields, `message`'s shorthand initialization is just the string itself. - debug!(excitement = "yay!", "hello! I'm gonna shave a yak."); + trace!(excitement = "yay!", "hello! I'm gonna shave a yak"); if yak == 3 { - warn!("could not locate yak!"); + warn!("could not locate yak"); // note that this is intended to demonstrate `tracing`'s features, not idiomatic // error handling! in a library or application, you should consider returning // a dedicated `YakError`. libraries like snafu or thiserror make this easy. - return Err(io::Error::new(io::ErrorKind::Other, "shaving yak failed!").into()); + return Err(io::Error::new(io::ErrorKind::Other, "missing yak").into()); } else { - debug!("yak shaved successfully"); + trace!("yak shaved successfully"); } Ok(()) } pub fn shave_all(yaks: usize) -> usize { - // Constructs a new span named "shaving_yaks" at the TRACE level, + // Constructs a new span named "shaving_yaks" at the INFO level, // and a field whose key is "yaks". This is equivalent to writing: // - // let span = span!(Level::TRACE, "shaving_yaks", yaks = yaks); + // let span = span!(Level::INFO, "shaving_yaks", yaks = yaks); // // local variables (`yaks`) can be used as field values // without an assignment, similar to struct initializers. - let span = span!(Level::TRACE, "shaving_yaks", yaks); + let span = span!(Level::INFO, "shaving_yaks", yaks); let _enter = span.enter(); info!("shaving yaks"); @@ -40,16 +40,16 @@ pub fn shave_all(yaks: usize) -> usize { let mut yaks_shaved = 0; for yak in 1..=yaks { let res = shave(yak); - debug!(yak, shaved = res.is_ok()); + debug!(target: "yak_events", yak, shaved = res.is_ok()); if let Err(ref error) = res { // Like spans, events can also use the field initialization shorthand. // In this instance, `yak` is the field being initalized. - error!(yak, error = error.as_ref(), "failed to shave yak!"); + error!(yak, error = error.as_ref(), "failed to shave yak"); } else { yaks_shaved += 1; } - debug!(yaks_shaved); + trace!(yaks_shaved); } yaks_shaved diff --git a/tracing-subscriber/src/fmt/fmt_layer.rs b/tracing-subscriber/src/fmt/fmt_layer.rs index 0b48ca1da8..75b46b3f45 100644 --- a/tracing-subscriber/src/fmt/fmt_layer.rs +++ b/tracing-subscriber/src/fmt/fmt_layer.rs @@ -367,6 +367,19 @@ where } } + /// Sets the layer being built to use an [excessively pretty, human-readable formatter](crate::fmt::format::Pretty). + #[cfg(feature = "ansi")] + #[cfg_attr(docsrs, doc(cfg(feature = "ansi")))] + pub fn pretty(self) -> Layer, W> { + Layer { + fmt_event: self.fmt_event.pretty(), + fmt_fields: format::Pretty::default(), + fmt_span: self.fmt_span, + make_writer: self.make_writer, + _inner: self._inner, + } + } + /// Sets the layer being built to use a [JSON formatter](../fmt/format/struct.Json.html). /// /// The full format includes fields from all entered spans. diff --git a/tracing-subscriber/src/fmt/format/mod.rs b/tracing-subscriber/src/fmt/format/mod.rs index 5f57711cb5..9020a6390f 100644 --- a/tracing-subscriber/src/fmt/format/mod.rs +++ b/tracing-subscriber/src/fmt/format/mod.rs @@ -24,12 +24,18 @@ use ansi_term::{Colour, Style}; #[cfg(feature = "json")] mod json; - -use fmt::{Debug, Display}; #[cfg(feature = "json")] #[cfg_attr(docsrs, doc(cfg(feature = "json")))] pub use json::*; +#[cfg(feature = "ansi")] +mod pretty; +#[cfg(feature = "ansi")] +#[cfg_attr(docsrs, doc(cfg(feature = "ansi")))] +pub use pretty::*; + +use fmt::{Debug, Display}; + /// A type that can format a tracing `Event` for a `fmt::Write`. /// /// `FormatEvent` is primarily used in the context of [`fmt::Subscriber`] or [`fmt::Layer`]. Each time an event is @@ -214,6 +220,25 @@ impl Format { } } + /// Use an excessively pretty, human-readable output format. + /// + /// See [`Pretty`]. + /// + /// Note that this requires the "ansi" feature to be enabled. + #[cfg(feature = "ansi")] + #[cfg_attr(docsrs, doc(cfg(feature = "ansi")))] + pub fn pretty(self) -> Format { + Format { + format: Pretty::default(), + timer: self.timer, + ansi: self.ansi, + display_target: self.display_target, + display_level: self.display_level, + display_thread_id: self.display_thread_id, + display_thread_name: self.display_thread_name, + } + } + /// Use the full JSON format. /// /// The full format includes fields from all entered spans. @@ -521,6 +546,7 @@ where } // === impl FormatFields === + impl<'writer, M> FormatFields<'writer> for M where M: MakeOutput<&'writer mut dyn fmt::Write, fmt::Result>, @@ -622,10 +648,7 @@ impl<'a> field::Visit for DefaultVisitor<'a> { fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) { if let Some(source) = value.source() { - self.record_debug( - field, - &format_args!("{} {}.source={}", value, field, source), - ) + self.record_debug(field, &format_args!("{}, {}: {}", value, field, source)) } else { self.record_debug(field, &format_args!("{}", value)) } diff --git a/tracing-subscriber/src/fmt/format/pretty.rs b/tracing-subscriber/src/fmt/format/pretty.rs new file mode 100644 index 0000000000..d3d269493b --- /dev/null +++ b/tracing-subscriber/src/fmt/format/pretty.rs @@ -0,0 +1,339 @@ +use super::*; +use crate::{ + field::{VisitFmt, VisitOutput}, + fmt::fmt_layer::{FmtContext, FormattedFields}, + registry::LookupSpan, +}; + +use std::{ + fmt::{self, Write}, + iter, +}; +use tracing_core::{ + field::{self, Field}, + Event, Level, Subscriber, +}; + +#[cfg(feature = "tracing-log")] +use tracing_log::NormalizeEvent; + +use ansi_term::{Colour, Style}; + +/// An excessively pretty, human-readable event formatter. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Pretty { + display_location: bool, +} + +/// The [visitor] produced by [`Pretty`]'s [`MakeVisitor`] implementation. +/// +/// [visitor]: ../../field/trait.Visit.html +/// [`DefaultFields`]: struct.DefaultFields.html +/// [`MakeVisitor`]: ../../field/trait.MakeVisitor.html +pub struct PrettyVisitor<'a> { + writer: &'a mut dyn Write, + is_empty: bool, + style: Style, + result: fmt::Result, +} + +// === impl Pretty === + +impl Default for Pretty { + fn default() -> Self { + Self { + display_location: true, + } + } +} + +impl Pretty { + fn style_for(level: &Level) -> Style { + match *level { + Level::TRACE => Style::new().fg(Colour::Purple), + Level::DEBUG => Style::new().fg(Colour::Blue), + Level::INFO => Style::new().fg(Colour::Green), + Level::WARN => Style::new().fg(Colour::Yellow), + Level::ERROR => Style::new().fg(Colour::Red), + } + } + + /// Sets whether the event's source code location is displayed. + /// + /// This defaults to `true`. + pub fn with_source_location(self, display_location: bool) -> Self { + Self { + display_location, + ..self + } + } +} + +impl Format { + /// Sets whether or not the source code location from which an event + /// originated is displayed. + /// + /// This defaults to `true`. + pub fn with_source_location(mut self, display_location: bool) -> Self { + self.format = self.format.with_source_location(display_location); + self + } +} + +impl FormatEvent for Format +where + C: Subscriber + for<'a> LookupSpan<'a>, + N: for<'a> FormatFields<'a> + 'static, + T: FormatTime, +{ + fn format_event( + &self, + ctx: &FmtContext<'_, C, N>, + writer: &mut dyn fmt::Write, + event: &Event<'_>, + ) -> fmt::Result { + #[cfg(feature = "tracing-log")] + let normalized_meta = event.normalized_metadata(); + #[cfg(feature = "tracing-log")] + let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata()); + #[cfg(not(feature = "tracing-log"))] + let meta = event.metadata(); + write!(writer, " ")?; + time::write(&self.timer, writer, true)?; + + let style = if self.display_level { + Pretty::style_for(meta.level()) + } else { + Style::new() + }; + + if self.display_target { + let bold = style.bold(); + write!( + writer, + "{}{}{}: ", + bold.prefix(), + meta.target(), + bold.infix(style) + )?; + } + let mut v = PrettyVisitor::new(writer, true).with_style(style); + event.record(&mut v); + v.finish()?; + writer.write_char('\n')?; + + let dimmed = Style::new().dimmed().italic(); + let thread = self.display_thread_name || self.display_thread_id; + if let (true, Some(file), Some(line)) = + (self.format.display_location, meta.file(), meta.line()) + { + write!( + writer, + " {} {}:{}{}", + dimmed.paint("at"), + file, + line, + dimmed.paint(if thread { " " } else { "\n" }) + )?; + } else if thread { + write!(writer, " ")?; + } + + if thread { + write!(writer, "{} ", dimmed.paint("on"))?; + let thread = std::thread::current(); + if self.display_thread_name { + if let Some(name) = thread.name() { + write!(writer, "{}", name)?; + if self.display_thread_id { + write!(writer, " ({:?})", thread.id())?; + } + } else if !self.display_thread_id { + write!(writer, " {:?}", thread.id())?; + } + } else if self.display_thread_id { + write!(writer, " {:?}", thread.id())?; + } + writer.write_char('\n')?; + } + + let bold = Style::new().bold(); + let span = event + .parent() + .and_then(|id| ctx.span(&id)) + .or_else(|| ctx.lookup_current()); + + let scope = span.into_iter().flat_map(|span| { + let parents = span.parents(); + iter::once(span).chain(parents) + }); + + for span in scope { + let meta = span.metadata(); + if self.display_target { + write!( + writer, + " {} {}::{}", + dimmed.paint("in"), + meta.target(), + bold.paint(meta.name()), + )?; + } else { + write!( + writer, + " {} {}", + dimmed.paint("in"), + bold.paint(meta.name()), + )?; + } + + let ext = span.extensions(); + let fields = &ext + .get::>() + .expect("Unable to find FormattedFields in extensions; this is a bug"); + if !fields.is_empty() { + write!(writer, " {} {}", dimmed.paint("with"), fields)?; + } + writer.write_char('\n')?; + } + + writer.write_char('\n') + } +} + +impl<'writer> FormatFields<'writer> for Pretty { + fn format_fields( + &self, + writer: &'writer mut dyn fmt::Write, + fields: R, + ) -> fmt::Result { + let mut v = PrettyVisitor::new(writer, true); + fields.record(&mut v); + v.finish() + } + + fn add_fields(&self, current: &'writer mut String, fields: &span::Record<'_>) -> fmt::Result { + let empty = current.is_empty(); + let mut v = PrettyVisitor::new(current, empty); + fields.record(&mut v); + v.finish() + } +} + +// === impl PrettyVisitor === + +impl<'a> PrettyVisitor<'a> { + /// Returns a new default visitor that formats to the provided `writer`. + /// + /// # Arguments + /// - `writer`: the writer to format to. + /// - `is_empty`: whether or not any fields have been previously written to + /// that writer. + pub fn new(writer: &'a mut dyn Write, is_empty: bool) -> Self { + Self { + writer, + is_empty, + style: Style::default(), + result: Ok(()), + } + } + + pub(crate) fn with_style(self, style: Style) -> Self { + Self { style, ..self } + } + + fn maybe_pad(&mut self) { + if self.is_empty { + self.is_empty = false; + } else { + self.result = write!(self.writer, ", "); + } + } +} + +impl<'a> field::Visit for PrettyVisitor<'a> { + fn record_str(&mut self, field: &Field, value: &str) { + if self.result.is_err() { + return; + } + + if field.name() == "message" { + self.record_debug(field, &format_args!("{}", value)) + } else { + self.record_debug(field, &value) + } + } + + fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) { + if let Some(source) = value.source() { + let bold = self.style.bold(); + self.record_debug( + field, + &format_args!( + "{}, {}{}.source{}: {}", + value, + bold.prefix(), + field, + bold.infix(self.style), + source, + ), + ) + } else { + self.record_debug(field, &format_args!("{}", value)) + } + } + + fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { + if self.result.is_err() { + return; + } + let bold = self.style.bold(); + self.maybe_pad(); + self.result = match field.name() { + "message" => write!(self.writer, "{}{:?}", self.style.prefix(), value,), + // Skip fields that are actually log metadata that have already been handled + #[cfg(feature = "tracing-log")] + name if name.starts_with("log.") => Ok(()), + name if name.starts_with("r#") => write!( + self.writer, + "{}{}{}: {:?}", + bold.prefix(), + &name[2..], + bold.infix(self.style), + value + ), + name => write!( + self.writer, + "{}{}{}: {:?}", + bold.prefix(), + name, + bold.infix(self.style), + value + ), + }; + } +} + +impl<'a> VisitOutput for PrettyVisitor<'a> { + fn finish(self) -> fmt::Result { + write!(self.writer, "{}", self.style.suffix())?; + self.result + } +} + +impl<'a> VisitFmt for PrettyVisitor<'a> { + fn writer(&mut self) -> &mut dyn fmt::Write { + self.writer + } +} + +impl<'a> fmt::Debug for PrettyVisitor<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PrettyVisitor") + .field("writer", &format_args!("")) + .field("is_empty", &self.is_empty) + .field("result", &self.result) + .field("style", &self.style) + .finish() + } +} diff --git a/tracing-subscriber/src/fmt/mod.rs b/tracing-subscriber/src/fmt/mod.rs index c3f256d69c..8b3c64eb48 100644 --- a/tracing-subscriber/src/fmt/mod.rs +++ b/tracing-subscriber/src/fmt/mod.rs @@ -27,7 +27,7 @@ //! //! ## Filtering Events with Environment Variables //! -//! The default subscriber installed by `init` enables you to filter events +//! The default collector installed by `init` enables you to filter events //! at runtime using environment variables (using the [`EnvFilter`]). //! //! The filter syntax is a superset of the [`env_logger`] syntax. @@ -52,12 +52,148 @@ //! You can create one by calling: //! //! ```rust -//! let subscriber = tracing_subscriber::fmt() +//! let collector = tracing_subscriber::fmt() //! // ... add configuration //! .finish(); //! ``` //! -//! You can find the configuration methods for [`FmtSubscriber`] in [`fmtBuilder`]. +//! You can find the configuration methods for [`FmtSubscriber`] in +//! [`SubscriberBuilder`]. +//! +//! ### Formatters +//! +//! The output format used by the layer and subscriber in this module is +//! represented by implementing the [`FormatEvent`] trait, and can be +//! customized. This module provides a number of formatter implementations: +//! +//! * [`format::Full`]: The default formatter. This emits human-readable, +//! single-line logs for each event that occurs, with the current span context +//! displayed before the formatted representation of the event. +//! +//! For example: +//!
    Finished dev [unoptimized + debuginfo] target(s) in 1.59s
+//!        Running `target/debug/examples/fmt`
+//!   Oct 24 12:55:47.814  INFO fmt: preparing to shave yaks number_of_yaks=3
+//!   Oct 24 12:55:47.814  INFO shaving_yaks{yaks=3}: fmt::yak_shave: shaving yaks
+//!   Oct 24 12:55:47.814 TRACE shaving_yaks{yaks=3}:shave{yak=1}: fmt::yak_shave: hello! I'm gonna shave a yak excitement="yay!"
+//!   Oct 24 12:55:47.814 TRACE shaving_yaks{yaks=3}:shave{yak=1}: fmt::yak_shave: yak shaved successfully
+//!   Oct 24 12:55:47.814 DEBUG shaving_yaks{yaks=3}: yak_events: yak=1 shaved=true
+//!   Oct 24 12:55:47.814 TRACE shaving_yaks{yaks=3}: fmt::yak_shave: yaks_shaved=1
+//!   Oct 24 12:55:47.815 TRACE shaving_yaks{yaks=3}:shave{yak=2}: fmt::yak_shave: hello! I'm gonna shave a yak excitement="yay!"
+//!   Oct 24 12:55:47.815 TRACE shaving_yaks{yaks=3}:shave{yak=2}: fmt::yak_shave: yak shaved successfully
+//!   Oct 24 12:55:47.815 DEBUG shaving_yaks{yaks=3}: yak_events: yak=2 shaved=true
+//!   Oct 24 12:55:47.815 TRACE shaving_yaks{yaks=3}: fmt::yak_shave: yaks_shaved=2
+//!   Oct 24 12:55:47.815 TRACE shaving_yaks{yaks=3}:shave{yak=3}: fmt::yak_shave: hello! I'm gonna shave a yak excitement="yay!"
+//!   Oct 24 12:55:47.815  WARN shaving_yaks{yaks=3}:shave{yak=3}: fmt::yak_shave: could not locate yak
+//!   Oct 24 12:55:47.815 DEBUG shaving_yaks{yaks=3}: yak_events: yak=3 shaved=false
+//!   Oct 24 12:55:47.815 ERROR shaving_yaks{yaks=3}: fmt::yak_shave: failed to shave yak yak=3 error=missing yak
+//!   Oct 24 12:55:47.815 TRACE shaving_yaks{yaks=3}: fmt::yak_shave: yaks_shaved=2
+//!   Oct 24 12:55:47.815  INFO fmt: yak shaving completed all_yaks_shaved=false
+//!   
+//! +//! * [`format::Pretty`]: Emits excessively pretty, multi-line logs, optimized +//! for human readability. This is primarily intended to be used in local +//! development and debugging, or for command-line applications, where +//! automated analysis and compact storage of logs is less of a priority than +//! readability and visual appeal. +//! +//! For example: +//!
    Finished dev [unoptimized + debuginfo] target(s) in 1.61s
+//!        Running `target/debug/examples/fmt-pretty`
+//!   Oct 24 12:57:29.386 fmt_pretty: preparing to shave yaks, number_of_yaks: 3
+//!     at examples/examples/fmt-pretty.rs:16 on main
+//!
+//!   Oct 24 12:57:29.386 fmt_pretty::yak_shave: shaving yaks
+//!     at examples/examples/fmt/yak_shave.rs:38 on main
+//!     in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
+//!
+//!   Oct 24 12:57:29.387 fmt_pretty::yak_shave: hello! I'm gonna shave a yak, excitement: "yay!"
+//!     at examples/examples/fmt/yak_shave.rs:14 on main
+//!     in fmt_pretty::yak_shave::shave with yak: 1
+//!     in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
+//!
+//!   Oct 24 12:57:29.387 fmt_pretty::yak_shave: yak shaved successfully
+//!     at examples/examples/fmt/yak_shave.rs:22 on main
+//!     in fmt_pretty::yak_shave::shave with yak: 1
+//!     in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
+//!
+//!   Oct 24 12:57:29.387 yak_events: yak: 1, shaved: true
+//!     at examples/examples/fmt/yak_shave.rs:43 on main
+//!     in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
+//!
+//!   Oct 24 12:57:29.387 fmt_pretty::yak_shave: yaks_shaved: 1
+//!     at examples/examples/fmt/yak_shave.rs:52 on main
+//!     in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
+//!
+//!   Oct 24 12:57:29.387 fmt_pretty::yak_shave: hello! I'm gonna shave a yak, excitement: "yay!"
+//!     at examples/examples/fmt/yak_shave.rs:14 on main
+//!     in fmt_pretty::yak_shave::shave with yak: 2
+//!     in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
+//!
+//!   Oct 24 12:57:29.387 fmt_pretty::yak_shave: yak shaved successfully
+//!     at examples/examples/fmt/yak_shave.rs:22 on main
+//!     in fmt_pretty::yak_shave::shave with yak: 2
+//!     in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
+//!
+//!   Oct 24 12:57:29.387 yak_events: yak: 2, shaved: true
+//!     at examples/examples/fmt/yak_shave.rs:43 on main
+//!     in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
+//!
+//!   Oct 24 12:57:29.387 fmt_pretty::yak_shave: yaks_shaved: 2
+//!     at examples/examples/fmt/yak_shave.rs:52 on main
+//!     in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
+//!
+//!   Oct 24 12:57:29.387 fmt_pretty::yak_shave: hello! I'm gonna shave a yak, excitement: "yay!"
+//!     at examples/examples/fmt/yak_shave.rs:14 on main
+//!     in fmt_pretty::yak_shave::shave with yak: 3
+//!     in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
+//!
+//!   Oct 24 12:57:29.387 fmt_pretty::yak_shave: could not locate yak
+//!     at examples/examples/fmt/yak_shave.rs:16 on main
+//!     in fmt_pretty::yak_shave::shave with yak: 3
+//!     in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
+//!
+//!   Oct 24 12:57:29.387 yak_events: yak: 3, shaved: false
+//!     at examples/examples/fmt/yak_shave.rs:43 on main
+//!     in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
+//!
+//!   Oct 24 12:57:29.387 fmt_pretty::yak_shave: failed to shave yak, yak: 3, error: missing yak
+//!     at examples/examples/fmt/yak_shave.rs:48 on main
+//!     in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
+//!
+//!   Oct 24 12:57:29.387 fmt_pretty::yak_shave: yaks_shaved: 2
+//!     at examples/examples/fmt/yak_shave.rs:52 on main
+//!     in fmt_pretty::yak_shave::shaving_yaks with yaks: 3
+//!
+//!   Oct 24 12:57:29.387 fmt_pretty: yak shaving completed, all_yaks_shaved: false
+//!     at examples/examples/fmt-pretty.rs:19 on main
+//!   
+//! +//! * [`format::Json`]: Outputs newline-delimited JSON logs. This is intended +//! for production use with systems where structured logs are consumed as JSON +//! by analysis and viewing tools. The JSON output, as seen below, is *not* +//! optimized for human readability. +//! +//! For example: +//!
    Finished dev [unoptimized + debuginfo] target(s) in 1.58s
+//!        Running `target/debug/examples/fmt-json`
+//!   {"timestamp":"Oct 24 13:00:00.873","level":"INFO","fields":{"message":"preparing to shave yaks","number_of_yaks":3},"target":"fmt_json"}
+//!   {"timestamp":"Oct 24 13:00:00.874","level":"INFO","fields":{"message":"shaving yaks"},"target":"fmt_json::yak_shave","spans":[{"yaks":3,"name":"shaving_yaks"}]}
+//!   {"timestamp":"Oct 24 13:00:00.874","level":"TRACE","fields":{"message":"hello! I'm gonna shave a yak","excitement":"yay!"},"target":"fmt_json::yak_shave","spans":[{"yaks":3,"name":"shaving_yaks"},{"yak":"1","name":"shave"}]}
+//!   {"timestamp":"Oct 24 13:00:00.874","level":"TRACE","fields":{"message":"yak shaved successfully"},"target":"fmt_json::yak_shave","spans":[{"yaks":3,"name":"shaving_yaks"},{"yak":"1","name":"shave"}]}
+//!   {"timestamp":"Oct 24 13:00:00.874","level":"DEBUG","fields":{"yak":1,"shaved":true},"target":"yak_events","spans":[{"yaks":3,"name":"shaving_yaks"}]}
+//!   {"timestamp":"Oct 24 13:00:00.874","level":"TRACE","fields":{"yaks_shaved":1},"target":"fmt_json::yak_shave","spans":[{"yaks":3,"name":"shaving_yaks"}]}
+//!   {"timestamp":"Oct 24 13:00:00.874","level":"TRACE","fields":{"message":"hello! I'm gonna shave a yak","excitement":"yay!"},"target":"fmt_json::yak_shave","spans":[{"yaks":3,"name":"shaving_yaks"},{"yak":"2","name":"shave"}]}
+//!   {"timestamp":"Oct 24 13:00:00.874","level":"TRACE","fields":{"message":"yak shaved successfully"},"target":"fmt_json::yak_shave","spans":[{"yaks":3,"name":"shaving_yaks"},{"yak":"2","name":"shave"}]}
+//!   {"timestamp":"Oct 24 13:00:00.874","level":"DEBUG","fields":{"yak":2,"shaved":true},"target":"yak_events","spans":[{"yaks":3,"name":"shaving_yaks"}]}
+//!   {"timestamp":"Oct 24 13:00:00.874","level":"TRACE","fields":{"yaks_shaved":2},"target":"fmt_json::yak_shave","spans":[{"yaks":3,"name":"shaving_yaks"}]}
+//!   {"timestamp":"Oct 24 13:00:00.874","level":"TRACE","fields":{"message":"hello! I'm gonna shave a yak","excitement":"yay!"},"target":"fmt_json::yak_shave","spans":[{"yaks":3,"name":"shaving_yaks"},{"yak":"3","name":"shave"}]}
+//!   {"timestamp":"Oct 24 13:00:00.875","level":"WARN","fields":{"message":"could not locate yak"},"target":"fmt_json::yak_shave","spans":[{"yaks":3,"name":"shaving_yaks"},{"yak":"3","name":"shave"}]}
+//!   {"timestamp":"Oct 24 13:00:00.875","level":"DEBUG","fields":{"yak":3,"shaved":false},"target":"yak_events","spans":[{"yaks":3,"name":"shaving_yaks"}]}
+//!   {"timestamp":"Oct 24 13:00:00.875","level":"ERROR","fields":{"message":"failed to shave yak","yak":3,"error":"missing yak"},"target":"fmt_json::yak_shave","spans":[{"yaks":3,"name":"shaving_yaks"}]}
+//!   {"timestamp":"Oct 24 13:00:00.875","level":"TRACE","fields":{"yaks_shaved":2},"target":"fmt_json::yak_shave","spans":[{"yaks":3,"name":"shaving_yaks"}]}
+//!   {"timestamp":"Oct 24 13:00:00.875","level":"INFO","fields":{"message":"yak shaving completed","all_yaks_shaved":false},"target":"fmt_json"}
+//!   
//! //! ### Filters //! @@ -116,7 +252,7 @@ //! [`EnvFilter`]: ../filter/struct.EnvFilter.html //! [`env_logger`]: https://docs.rs/env_logger/ //! [`filter`]: ../filter/index.html -//! [`fmtBuilder`]: ./struct.SubscriberBuilder.html +//! [`SubscriberBuilder`]: ./struct.SubscriberBuilder.html //! [`FmtSubscriber`]: ./struct.Subscriber.html //! [`Subscriber`]: //! https://docs.rs/tracing/latest/tracing/trait.Subscriber.html @@ -200,7 +336,7 @@ pub struct SubscriberBuilder< /// .with_target(false) /// .with_timer(tracing_subscriber::fmt::time::uptime()) /// .with_level(true) -/// // Set the subscriber as the default. +/// // Set the collector as the default. /// .init(); /// ``` /// @@ -211,11 +347,11 @@ pub struct SubscriberBuilder< /// /// fn init_subscriber() -> Result<(), Box> { /// tracing_subscriber::fmt() -/// // Configure the subscriber to emit logs in JSON format. +/// // Configure the collector to emit logs in JSON format. /// .json() -/// // Configure the subscriber to flatten event fields in the output JSON objects. +/// // Configure the collector to flatten event fields in the output JSON objects. /// .flatten_event(true) -/// // Set the subscriber as the default, returning an error if this fails. +/// // Set the collector as the default, returning an error if this fails. /// .try_init()?; /// /// Ok(()) @@ -572,7 +708,7 @@ where /// Sets the subscriber being built to use a less verbose formatter. /// - /// See [`format::Compact`](../fmt/format/struct.Compact.html). + /// See [`format::Compact`]. pub fn compact(self) -> SubscriberBuilder, F, W> where N: for<'writer> FormatFields<'writer> + 'static, @@ -583,6 +719,18 @@ where } } + /// Sets the subscriber being built to use an [excessively pretty, human-readable formatter](crate::fmt::format::Pretty). + #[cfg(feature = "ansi")] + #[cfg_attr(docsrs, doc(cfg(feature = "ansi")))] + pub fn pretty( + self, + ) -> SubscriberBuilder, F, W> { + SubscriberBuilder { + filter: self.filter, + inner: self.inner.pretty(), + } + } + /// Sets the subscriber being built to use a JSON formatter. /// /// See [`format::Json`](../fmt/format/struct.Json.html) @@ -617,7 +765,7 @@ impl SubscriberBuilder SubscriberBuilder