From 19c08ca132ab6103c087fcf4b08804694d66de19 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Thu, 11 Mar 2021 18:50:48 -0500 Subject: [PATCH] subscriber: update pretty formatter for no ansi (#1240) This backports #1240 from `master`. * subscriber: update pretty formatter for no ansi ## Background Currently, when the `Pretty` event formatter is being used, it does not change its output when the `with_ansi` flag is set to false by the `CollectorBuilder`. ## Overview While this formatter is generally used in situations such as local development, where ANSI escape codes are more often acceptable, there are some situations in which this can lead to mangled output. This commit makes some minor changes to account for this `ansi` flag when formatting events using `Pretty`. Becuase ANSI codes were previously used to imply the event level using colors, this commit additionally modifies `Pretty` so that it respects `display_level` when formatting an event. ## Changes * Changes to ` as FormatEvent>::format_event` * Add a `PrettyVisitor::ansi` boolean flag, used in its `Visit` implementation. * Add a new `PrettyVisitor::with_ansi` builder pattern method to facilitate this. ## Out of Scope One detail worth nothing is that this does not solve the problem of *fields* being formatted without ANSI codes. Configuring a subscriber using this snippet would still lead to bolded fields in parent spans. ```rust tracing_subscriber::fmt() .pretty() .with_ansi(false) .with_level(false) .with_max_level(tracing::Level::TRACE) .init(); ``` This can be worked around by using a different field formatter, via `.fmt_fields(tracing_subscriber::fmt::format::DefaultFields::new())` in the short-term. In the long-term, #658 is worth investigating further. Refs: #658 --- tracing-subscriber/src/fmt/format/mod.rs | 17 ++++ tracing-subscriber/src/fmt/format/pretty.rs | 86 ++++++++++++++++++--- 2 files changed, 93 insertions(+), 10 deletions(-) diff --git a/tracing-subscriber/src/fmt/format/mod.rs b/tracing-subscriber/src/fmt/format/mod.rs index 3c20fb600b..e8ea89cb3d 100644 --- a/tracing-subscriber/src/fmt/format/mod.rs +++ b/tracing-subscriber/src/fmt/format/mod.rs @@ -293,6 +293,23 @@ impl Format { /// See [`Pretty`]. /// /// Note that this requires the "ansi" feature to be enabled. + /// + /// # Options + /// + /// [`Format::with_ansi`] can be used to disable ANSI terminal escape codes (which enable + /// formatting such as colors, bold, italic, etc) in event formatting. However, a field + /// formatter must be manually provided to avoid ANSI in the formatting of parent spans, like + /// so: + /// + /// ``` + /// # use tracing_subscriber::fmt::format; + /// tracing_subscriber::fmt() + /// .pretty() + /// .with_ansi(false) + /// .fmt_fields(format::PrettyFields::new().with_ansi(false)) + /// // ... other settings ... + /// .init(); + /// ``` #[cfg(feature = "ansi")] #[cfg_attr(docsrs, doc(cfg(feature = "ansi")))] pub fn pretty(self) -> Format { diff --git a/tracing-subscriber/src/fmt/format/pretty.rs b/tracing-subscriber/src/fmt/format/pretty.rs index 0b68f841c6..61be844e9c 100644 --- a/tracing-subscriber/src/fmt/format/pretty.rs +++ b/tracing-subscriber/src/fmt/format/pretty.rs @@ -33,10 +33,19 @@ pub struct Pretty { pub struct PrettyVisitor<'a> { writer: &'a mut dyn Write, is_empty: bool, + ansi: bool, style: Style, result: fmt::Result, } +/// An excessively pretty, human-readable [`MakeVisitor`] implementation. +/// +/// [`MakeVisitor`]: crate::field::MakeVisitor +#[derive(Debug)] +pub struct PrettyFields { + ansi: bool, +} + // === impl Pretty === impl Default for Pretty { @@ -99,30 +108,40 @@ where #[cfg(not(feature = "tracing-log"))] let meta = event.metadata(); write!(writer, " ")?; - time::write(&self.timer, writer, true)?; + time::write(&self.timer, writer, self.ansi)?; - let style = if self.display_level { + let style = if self.display_level && self.ansi { Pretty::style_for(meta.level()) } else { Style::new() }; + if self.display_level { + write!(writer, "{}", super::FmtLevel::new(meta.level(), self.ansi))?; + } + if self.display_target { - let bold = style.bold(); + let target_style = if self.ansi { style.bold() } else { style }; write!( writer, "{}{}{}: ", - bold.prefix(), + target_style.prefix(), meta.target(), - bold.infix(style) + target_style.infix(style) )?; } - let mut v = PrettyVisitor::new(writer, true).with_style(style); + let mut v = PrettyVisitor::new(writer, true) + .with_style(style) + .with_ansi(self.ansi); event.record(&mut v); v.finish()?; writer.write_char('\n')?; - let dimmed = Style::new().dimmed().italic(); + let dimmed = if self.ansi { + Style::new().dimmed().italic() + } else { + Style::new() + }; 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()) @@ -157,7 +176,11 @@ where writer.write_char('\n')?; } - let bold = Style::new().bold(); + let bold = if self.ansi { + Style::new().bold() + } else { + Style::new() + }; let span = event .parent() .and_then(|id| ctx.span(&id)) @@ -220,6 +243,35 @@ impl<'writer> FormatFields<'writer> for Pretty { } } +// === impl PrettyFields === + +impl Default for PrettyFields { + fn default() -> Self { + Self::new() + } +} + +impl PrettyFields { + /// Returns a new default [`PrettyFields`] implementation. + pub fn new() -> Self { + Self { ansi: true } + } + + /// Enable ANSI encoding for formatted fields. + pub fn with_ansi(self, ansi: bool) -> Self { + Self { ansi, ..self } + } +} + +impl<'a> MakeVisitor<&'a mut dyn Write> for PrettyFields { + type Visitor = PrettyVisitor<'a>; + + #[inline] + fn make_visitor(&self, target: &'a mut dyn Write) -> Self::Visitor { + PrettyVisitor::new(target, true).with_ansi(self.ansi) + } +} + // === impl PrettyVisitor === impl<'a> PrettyVisitor<'a> { @@ -233,6 +285,7 @@ impl<'a> PrettyVisitor<'a> { Self { writer, is_empty, + ansi: true, style: Style::default(), result: Ok(()), } @@ -242,6 +295,10 @@ impl<'a> PrettyVisitor<'a> { Self { style, ..self } } + pub(crate) fn with_ansi(self, ansi: bool) -> Self { + Self { ansi, ..self } + } + fn write_padded(&mut self, value: &impl fmt::Debug) { let padding = if self.is_empty { self.is_empty = false; @@ -251,6 +308,14 @@ impl<'a> PrettyVisitor<'a> { }; self.result = write!(self.writer, "{}{:?}", padding, value); } + + fn bold(&self) -> Style { + if self.ansi { + self.style.bold() + } else { + Style::new() + } + } } impl<'a> field::Visit for PrettyVisitor<'a> { @@ -268,7 +333,7 @@ impl<'a> field::Visit for PrettyVisitor<'a> { fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) { if let Some(source) = value.source() { - let bold = self.style.bold(); + let bold = self.bold(); self.record_debug( field, &format_args!( @@ -289,7 +354,7 @@ impl<'a> field::Visit for PrettyVisitor<'a> { if self.result.is_err() { return; } - let bold = self.style.bold(); + let bold = self.bold(); match field.name() { "message" => self.write_padded(&format_args!("{}{:?}", self.style.prefix(), value,)), // Skip fields that are actually log metadata that have already been handled @@ -333,6 +398,7 @@ impl<'a> fmt::Debug for PrettyVisitor<'a> { .field("is_empty", &self.is_empty) .field("result", &self.result) .field("style", &self.style) + .field("ansi", &self.ansi) .finish() } }