diff --git a/tracing-subscriber/src/fmt/fmt_layer.rs b/tracing-subscriber/src/fmt/fmt_layer.rs index 631c586bd5..0eddbfb3c6 100644 --- a/tracing-subscriber/src/fmt/fmt_layer.rs +++ b/tracing-subscriber/src/fmt/fmt_layer.rs @@ -69,6 +69,7 @@ pub struct Layer< fmt_fields: N, fmt_event: E, fmt_span: format::FmtSpanConfig, + is_ansi: bool, _inner: PhantomData, } @@ -117,6 +118,7 @@ where fmt_event: e, fmt_span: self.fmt_span, make_writer: self.make_writer, + is_ansi: self.is_ansi, _inner: self._inner, } } @@ -151,6 +153,7 @@ impl Layer { fmt_fields: self.fmt_fields, fmt_event: self.fmt_event, fmt_span: self.fmt_span, + is_ansi: self.is_ansi, make_writer, _inner: self._inner, } @@ -183,10 +186,21 @@ impl Layer { fmt_fields: self.fmt_fields, fmt_event: self.fmt_event, fmt_span: self.fmt_span, + is_ansi: self.is_ansi, make_writer: TestWriter::default(), _inner: self._inner, } } + + /// Enable ANSI terminal colors for formatted output. + #[cfg(feature = "ansi")] + #[cfg_attr(docsrs, doc(cfg(feature = "ansi")))] + pub fn with_ansi(self, ansi: bool) -> Self { + Self { + is_ansi: ansi, + ..self + } + } } impl Layer, W> @@ -213,6 +227,7 @@ where fmt_fields: self.fmt_fields, fmt_span: self.fmt_span, make_writer: self.make_writer, + is_ansi: self.is_ansi, _inner: self._inner, } } @@ -224,6 +239,7 @@ where fmt_fields: self.fmt_fields, fmt_span: self.fmt_span.without_time(), make_writer: self.make_writer, + is_ansi: self.is_ansi, _inner: self._inner, } } @@ -276,16 +292,6 @@ where } } - /// Enable ANSI encoding for formatted events. - #[cfg(feature = "ansi")] - #[cfg_attr(docsrs, doc(cfg(feature = "ansi")))] - pub fn with_ansi(self, ansi: bool) -> Layer, W> { - Layer { - fmt_event: self.fmt_event.with_ansi(ansi), - ..self - } - } - /// Sets whether or not an event's target is displayed. pub fn with_target(self, display_target: bool) -> Layer, W> { Layer { @@ -337,6 +343,7 @@ where fmt_fields: self.fmt_fields, fmt_span: self.fmt_span, make_writer: self.make_writer, + is_ansi: self.is_ansi, _inner: self._inner, } } @@ -350,6 +357,7 @@ where fmt_fields: format::Pretty::default(), fmt_span: self.fmt_span, make_writer: self.make_writer, + is_ansi: self.is_ansi, _inner: self._inner, } } @@ -378,6 +386,8 @@ where fmt_fields: format::JsonFields::new(), fmt_span: self.fmt_span, make_writer: self.make_writer, + // always disable ANSI escapes in JSON mode! + is_ansi: false, _inner: self._inner, } } @@ -443,6 +453,7 @@ impl Layer { fmt_fields, fmt_span: self.fmt_span, make_writer: self.make_writer, + is_ansi: self.is_ansi, _inner: self._inner, } } @@ -455,6 +466,7 @@ impl Default for Layer { fmt_event: format::Format::default(), fmt_span: format::FmtSpanConfig::default(), make_writer: io::stdout, + is_ansi: cfg!(feature = "ansi"), _inner: PhantomData, } } @@ -488,6 +500,7 @@ where #[derive(Default)] pub struct FormattedFields { _format_fields: PhantomData, + was_ansi: bool, /// The formatted fields of a span. pub fields: String, } @@ -497,6 +510,7 @@ impl FormattedFields { pub fn new(fields: String) -> Self { Self { fields, + was_ansi: false, _format_fields: PhantomData, } } @@ -506,7 +520,7 @@ impl FormattedFields { /// The returned [`format::Writer`] can be used with the /// [`FormatFields::format_fields`] method. pub fn as_writer(&mut self) -> format::Writer<'_> { - format::Writer::new(&mut self.fields) + format::Writer::new(&mut self.fields).with_ansi(self.was_ansi) } } @@ -515,6 +529,7 @@ impl fmt::Debug for FormattedFields { f.debug_struct("FormattedFields") .field("fields", &self.fields) .field("formatter", &format_args!("{}", std::any::type_name::())) + .field("was_ansi", &self.was_ansi) .finish() } } @@ -565,9 +580,10 @@ where let mut fields = FormattedFields::::new(String::new()); if self .fmt_fields - .format_fields(fields.as_writer(), attrs) + .format_fields(fields.as_writer().with_ansi(self.is_ansi), attrs) .is_ok() { + fields.was_ansi = self.is_ansi; extensions.insert(fields); } } @@ -599,9 +615,10 @@ where let mut fields = FormattedFields::::new(String::new()); if self .fmt_fields - .format_fields(fields.as_writer(), values) + .format_fields(fields.as_writer().with_ansi(self.is_ansi), values) .is_ok() { + fields.was_ansi = self.is_ansi; extensions.insert(fields); } } @@ -706,7 +723,11 @@ where let ctx = self.make_ctx(ctx); if self .fmt_event - .format_event(&ctx, format::Writer::new(&mut buf), event) + .format_event( + &ctx, + format::Writer::new(&mut buf).with_ansi(self.is_ansi), + event, + ) .is_ok() { let mut writer = self.make_writer.make_writer_for(event.metadata()); diff --git a/tracing-subscriber/src/fmt/format/mod.rs b/tracing-subscriber/src/fmt/format/mod.rs index 91a6f1f934..e2332f2af5 100644 --- a/tracing-subscriber/src/fmt/format/mod.rs +++ b/tracing-subscriber/src/fmt/format/mod.rs @@ -228,6 +228,7 @@ where pub struct Writer<'writer> { writer: &'writer mut dyn fmt::Write, // TODO(eliza): add ANSI support + is_ansi: bool, } /// A [`FormatFields`] implementation that formats fields by calling a function @@ -269,7 +270,7 @@ pub struct Full; pub struct Format { format: F, pub(crate) timer: T, - pub(crate) ansi: bool, + pub(crate) ansi: Option, pub(crate) display_timestamp: bool, pub(crate) display_target: bool, pub(crate) display_level: bool, @@ -287,16 +288,26 @@ impl<'writer> Writer<'writer> { pub(crate) fn new(writer: &'writer mut impl fmt::Write) -> Self { Self { writer: writer as &mut dyn fmt::Write, + is_ansi: false, } } + // TODO(eliza): consider making this a public API? + pub(crate) fn with_ansi(self, is_ansi: bool) -> Self { + Self { is_ansi, ..self } + } + /// Return a new `Writer` that mutably borrows `self`. /// /// This can be used to temporarily borrow a `Writer` to pass a new `Writer` /// to a function that takes a `Writer` by value, allowing the original writer /// to still be used once that function returns. pub fn by_ref(&mut self) -> Writer<'_> { - Writer::new(self) + let is_ansi = self.is_ansi; + Writer { + writer: self as &mut dyn fmt::Write, + is_ansi, + } } /// Writes a string slice into this `Writer`, returning whether the write succeeded. @@ -342,7 +353,7 @@ impl<'writer> Writer<'writer> { self.writer.write_char(c) } - /// Glue for usage of the [`write!`] macro with `Wrriter`s. + /// Glue for usage of the [`write!`] macro with `Writer`s. /// /// This method should generally not be invoked manually, but rather through /// the [`write!`] macro itself. @@ -357,6 +368,14 @@ impl<'writer> Writer<'writer> { pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result { self.writer.write_fmt(args) } + + /// Returns `true` if ANSI escape codes may be used to add colors + /// and other formatting when writing to this `Writer`. + /// + /// If this returns `false`, formatters should not emit ANSI escape codes. + pub fn has_ansi_escapes(&self) -> bool { + self.is_ansi + } } impl fmt::Write for Writer<'_> { @@ -380,6 +399,7 @@ impl fmt::Debug for Writer<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Writer") .field("writer", &format_args!("<&mut dyn fmt::Write>")) + .field("is_ansi", &self.is_ansi) .finish() } } @@ -391,7 +411,7 @@ impl Default for Format { Format { format: Full, timer: SystemTime, - ansi: true, + ansi: None, display_timestamp: true, display_target: true, display_level: true, @@ -529,7 +549,10 @@ impl Format { /// Enable ANSI terminal colors for formatted output. pub fn with_ansi(self, ansi: bool) -> Format { - Format { ansi, ..self } + Format { + ansi: Some(ansi), + ..self + } } /// Sets whether or not an event's target is displayed. @@ -584,7 +607,7 @@ impl Format { // colors. #[cfg(feature = "ansi")] { - if self.ansi { + if writer.has_ansi_escapes() { let style = Style::new().dimmed(); write!(writer, "{}", style.prefix())?; self.timer.format_time(writer)?; @@ -659,13 +682,21 @@ where #[cfg(not(feature = "tracing-log"))] let meta = event.metadata(); + // if the `Format` struct *also* has an ANSI color configuration, + // override the writer...the API for configuring ANSI color codes on the + // `Format` struct is deprecated, but we still need to honor those + // configurations. + if let Some(ansi) = self.ansi { + writer = writer.with_ansi(ansi); + } + self.format_timestamp(&mut writer)?; if self.display_level { let fmt_level = { #[cfg(feature = "ansi")] { - FmtLevel::new(meta.level(), self.ansi) + FmtLevel::new(meta.level(), writer.has_ansi_escapes()) } #[cfg(not(feature = "ansi"))] { @@ -693,18 +724,32 @@ where write!(writer, "{:0>2?} ", std::thread::current().id())?; } - let full_ctx = { - #[cfg(feature = "ansi")] - { - FullCtx::new(ctx, event.parent(), self.ansi) + if let Some(scope) = ctx.ctx.event_scope(event) { + let bold = if writer.has_ansi_escapes() { + Style::new().bold() + } else { + Style::new() + }; + let mut seen = false; + + for span in scope.from_root() { + write!(writer, "{}", bold.paint(span.metadata().name()))?; + seen = true; + + let ext = span.extensions(); + if let Some(fields) = &ext.get::>() { + if !fields.is_empty() { + write!(writer, "{}{}{}", bold.paint("{"), fields, bold.paint("}"))?; + } + } + writer.write_char(':')?; } - #[cfg(not(feature = "ansi"))] - { - FullCtx::new(ctx, event.parent()) + + if seen { + writer.write_char(' ')?; } }; - write!(writer, "{}", full_ctx)?; if self.display_target { write!(writer, "{}: ", meta.target())?; } @@ -733,13 +778,21 @@ where #[cfg(not(feature = "tracing-log"))] let meta = event.metadata(); + // if the `Format` struct *also* has an ANSI color configuration, + // override the writer...the API for configuring ANSI color codes on the + // `Format` struct is deprecated, but we still need to honor those + // configurations. + if let Some(ansi) = self.ansi { + writer = writer.with_ansi(ansi); + } + self.format_timestamp(&mut writer)?; if self.display_level { let fmt_level = { #[cfg(feature = "ansi")] { - FmtLevel::new(meta.level(), self.ansi) + FmtLevel::new(meta.level(), writer.has_ansi_escapes()) } #[cfg(not(feature = "ansi"))] { @@ -767,10 +820,22 @@ where write!(writer, "{:0>2?} ", std::thread::current().id())?; } + if self.display_target { + let target = meta.target(); + #[cfg(feature = "ansi")] + let target = if writer.has_ansi_escapes() { + Style::new().bold().paint(target) + } else { + Style::new().paint(target) + }; + + write!(writer, "{}:", target)?; + } + let fmt_ctx = { #[cfg(feature = "ansi")] { - FmtCtx::new(ctx, event.parent(), self.ansi) + FmtCtx::new(ctx, event.parent(), writer.has_ansi_escapes()) } #[cfg(not(feature = "ansi"))] { @@ -778,11 +843,6 @@ where } }; write!(writer, "{}", fmt_ctx)?; - if self.display_target { - write!(writer, "{}:", meta.target())?; - } - - ctx.format_fields(writer.by_ref(), event)?; let span = event .parent() @@ -791,7 +851,7 @@ where let scope = span.into_iter().flat_map(|span| span.scope()); #[cfg(feature = "ansi")] - let dimmed = if self.ansi { + let dimmed = if writer.has_ansi_escapes() { Style::new().dimmed() } else { Style::new() @@ -1028,85 +1088,6 @@ where } } -struct FullCtx<'a, S, N> -where - S: Subscriber + for<'lookup> LookupSpan<'lookup>, - N: for<'writer> FormatFields<'writer> + 'static, -{ - ctx: &'a FmtContext<'a, S, N>, - span: Option<&'a span::Id>, - #[cfg(feature = "ansi")] - ansi: bool, -} - -impl<'a, S, N: 'a> FullCtx<'a, S, N> -where - S: Subscriber + for<'lookup> LookupSpan<'lookup>, - N: for<'writer> FormatFields<'writer> + 'static, -{ - #[cfg(feature = "ansi")] - pub(crate) fn new( - ctx: &'a FmtContext<'a, S, N>, - span: Option<&'a span::Id>, - ansi: bool, - ) -> Self { - Self { ctx, span, ansi } - } - - #[cfg(not(feature = "ansi"))] - pub(crate) fn new(ctx: &'a FmtContext<'a, S, N>, span: Option<&'a span::Id>) -> Self { - Self { ctx, span } - } - - fn bold(&self) -> Style { - #[cfg(feature = "ansi")] - { - if self.ansi { - return Style::new().bold(); - } - } - - Style::new() - } -} - -impl<'a, S, N> fmt::Display for FullCtx<'a, S, N> -where - S: Subscriber + for<'lookup> LookupSpan<'lookup>, - N: for<'writer> FormatFields<'writer> + 'static, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let bold = self.bold(); - let mut seen = false; - - let span = self - .span - .and_then(|id| self.ctx.ctx.span(id)) - .or_else(|| self.ctx.ctx.lookup_current()); - - let scope = span.into_iter().flat_map(|span| span.scope().from_root()); - - for span in scope { - write!(f, "{}", bold.paint(span.metadata().name()))?; - seen = true; - - let ext = span.extensions(); - let fields = &ext - .get::>() - .expect("Unable to find FormattedFields in extensions; this is a bug"); - if !fields.is_empty() { - write!(f, "{}{}{}", bold.paint("{"), fields, bold.paint("}"))?; - } - f.write_char(':')?; - } - - if seen { - f.write_char(' ')?; - } - Ok(()) - } -} - #[cfg(not(feature = "ansi"))] struct Style; @@ -1115,6 +1096,11 @@ impl Style { fn new() -> Self { Style } + + fn bold(self) -> Self { + self + } + fn paint(&self, d: impl fmt::Display) -> impl fmt::Display { d } diff --git a/tracing-subscriber/src/fmt/format/pretty.rs b/tracing-subscriber/src/fmt/format/pretty.rs index 568fea27aa..cea20c86fd 100644 --- a/tracing-subscriber/src/fmt/format/pretty.rs +++ b/tracing-subscriber/src/fmt/format/pretty.rs @@ -5,7 +5,7 @@ use crate::{ registry::LookupSpan, }; -use std::fmt::{self, Write}; +use std::fmt; use tracing_core::{ field::{self, Field}, Event, Level, Subscriber, @@ -24,13 +24,12 @@ pub struct Pretty { /// The [visitor] produced by [`Pretty`]'s [`MakeVisitor`] implementation. /// -/// [visitor]: ../../field/trait.Visit.html -/// [`DefaultFields`]: struct.DefaultFields.html -/// [`MakeVisitor`]: ../../field/trait.MakeVisitor.html +/// [visitor]: field::Visit +/// [`MakeVisitor`]: crate::field::MakeVisitor +#[derive(Debug)] pub struct PrettyVisitor<'a> { - writer: &'a mut dyn Write, + writer: Writer<'a>, is_empty: bool, - ansi: bool, style: Style, result: fmt::Result, } @@ -106,20 +105,36 @@ where let meta = event.metadata(); write!(&mut writer, " ")?; + // if the `Format` struct *also* has an ANSI color configuration, + // override the writer...the API for configuring ANSI color codes on the + // `Format` struct is deprecated, but we still need to honor those + // configurations. + if let Some(ansi) = self.ansi { + writer = writer.with_ansi(ansi); + } + self.format_timestamp(&mut writer)?; - let style = if self.display_level && self.ansi { + let style = if self.display_level && writer.has_ansi_escapes() { Pretty::style_for(meta.level()) } else { Style::new() }; if self.display_level { - write!(writer, "{} ", super::FmtLevel::new(meta.level(), self.ansi))?; + write!( + writer, + "{} ", + super::FmtLevel::new(meta.level(), writer.has_ansi_escapes()) + )?; } if self.display_target { - let target_style = if self.ansi { style.bold() } else { style }; + let target_style = if writer.has_ansi_escapes() { + style.bold() + } else { + style + }; write!( writer, "{}{}{}: ", @@ -128,14 +143,12 @@ where target_style.infix(style) )?; } - let mut v = PrettyVisitor::new(&mut writer, true) - .with_style(style) - .with_ansi(self.ansi); + let mut v = PrettyVisitor::new(writer.by_ref(), true).with_style(style); event.record(&mut v); v.finish()?; writer.write_char('\n')?; - let dimmed = if self.ansi { + let dimmed = if writer.has_ansi_escapes() { Style::new().dimmed().italic() } else { Style::new() @@ -174,7 +187,7 @@ where writer.write_char('\n')?; } - let bold = if self.ansi { + let bold = if writer.has_ansi_escapes() { Style::new().bold() } else { Style::new() @@ -220,12 +233,8 @@ where } impl<'writer> FormatFields<'writer> for Pretty { - fn format_fields( - &self, - mut writer: Writer<'writer>, - fields: R, - ) -> fmt::Result { - let mut v = PrettyVisitor::new(&mut writer, false); + fn format_fields(&self, writer: Writer<'writer>, fields: R) -> fmt::Result { + let mut v = PrettyVisitor::new(writer, false); fields.record(&mut v); v.finish() } @@ -236,8 +245,8 @@ impl<'writer> FormatFields<'writer> for Pretty { fields: &span::Record<'_>, ) -> fmt::Result { let empty = current.is_empty(); - let mut writer = current.as_writer(); - let mut v = PrettyVisitor::new(&mut writer, empty); + let writer = current.as_writer(); + let mut v = PrettyVisitor::new(writer, empty); fields.record(&mut v); v.finish() } @@ -268,7 +277,7 @@ impl<'a> MakeVisitor> for PrettyFields { #[inline] fn make_visitor(&self, target: Writer<'a>) -> Self::Visitor { - PrettyVisitor::new(target.writer, true).with_ansi(self.ansi) + PrettyVisitor::new(target.with_ansi(self.ansi), true) } } @@ -281,11 +290,10 @@ impl<'a> PrettyVisitor<'a> { /// - `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 { + pub fn new(writer: Writer<'a>, is_empty: bool) -> Self { Self { writer, is_empty, - ansi: true, style: Style::default(), result: Ok(()), } @@ -295,10 +303,6 @@ 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; @@ -310,7 +314,7 @@ impl<'a> PrettyVisitor<'a> { } fn bold(&self) -> Style { - if self.ansi { + if self.writer.has_ansi_escapes() { self.style.bold() } else { Style::new() @@ -379,26 +383,14 @@ impl<'a> field::Visit for PrettyVisitor<'a> { } impl<'a> VisitOutput for PrettyVisitor<'a> { - fn finish(self) -> fmt::Result { - write!(self.writer, "{}", self.style.suffix())?; + fn finish(mut self) -> fmt::Result { + write!(&mut 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) - .field("ansi", &self.ansi) - .finish() + &mut self.writer } }