From 1a095ff3a704f30c4d1da0d21afab978616f7a7f Mon Sep 17 00:00:00 2001 From: Nikita Bishonen Date: Mon, 27 Oct 2025 11:24:41 +0000 Subject: [PATCH] feat(subscriber): Add with_span_list implementation for all standard formats. --- tracing-core/src/field.rs | 4 +- tracing-subscriber/src/fmt/fmt_layer.rs | 24 ++-- tracing-subscriber/src/fmt/format/json.rs | 16 +-- tracing-subscriber/src/fmt/format/mod.rs | 133 ++++++++++++++------ tracing-subscriber/src/fmt/format/pretty.rs | 5 +- tracing-subscriber/src/fmt/mod.rs | 23 ++-- 6 files changed, 122 insertions(+), 83 deletions(-) diff --git a/tracing-core/src/field.rs b/tracing-core/src/field.rs index dc68b4321..826558cf6 100644 --- a/tracing-core/src/field.rs +++ b/tracing-core/src/field.rs @@ -1238,7 +1238,7 @@ mod test { struct MyVisitor; impl Visit for MyVisitor { - fn record_debug(&mut self, field: &Field, _: &dyn (fmt::Debug)) { + fn record_debug(&mut self, field: &Field, _: &dyn fmt::Debug) { assert_eq!(field.callsite(), TEST_META_1.callsite()) } } @@ -1257,7 +1257,7 @@ mod test { struct MyVisitor; impl Visit for MyVisitor { - fn record_debug(&mut self, field: &Field, _: &dyn (fmt::Debug)) { + fn record_debug(&mut self, field: &Field, _: &dyn fmt::Debug) { assert_eq!(field.name(), "bar") } } diff --git a/tracing-subscriber/src/fmt/fmt_layer.rs b/tracing-subscriber/src/fmt/fmt_layer.rs index d0233810e..0e8ad415a 100644 --- a/tracing-subscriber/src/fmt/fmt_layer.rs +++ b/tracing-subscriber/src/fmt/fmt_layer.rs @@ -484,6 +484,15 @@ where } } + /// Sets whether or not the formatter will include a list (from root to leaf) + /// of all currently entered spans in formatted events. + pub fn with_span_list(self, display_span_list: bool) -> Self { + Layer { + fmt_event: self.fmt_event.with_span_list(display_span_list), + ..self + } + } + /// Sets whether or not an event's target is displayed. pub fn with_target(self, display_target: bool) -> Layer, W> { Layer { @@ -643,21 +652,6 @@ impl Layer, W> { ..self } } - - /// Sets whether or not the formatter will include a list (from root to leaf) - /// of all currently entered spans in formatted events. - /// - /// See [`format::Json`][super::format::Json] - pub fn with_span_list( - self, - display_span_list: bool, - ) -> Layer, W> { - Layer { - fmt_event: self.fmt_event.with_span_list(display_span_list), - fmt_fields: format::JsonFields::new(), - ..self - } - } } impl Layer { diff --git a/tracing-subscriber/src/fmt/format/json.rs b/tracing-subscriber/src/fmt/format/json.rs index 6266b475e..bbc39bcd7 100644 --- a/tracing-subscriber/src/fmt/format/json.rs +++ b/tracing-subscriber/src/fmt/format/json.rs @@ -65,8 +65,6 @@ use tracing_log::NormalizeEvent; /// the root /// - [`Json::with_current_span`] can be used to control logging of the current /// span -/// - [`Json::with_span_list`] can be used to control logging of the span list -/// object. /// /// By default, event fields are not flattened, and both current span and span /// list are logged. @@ -84,7 +82,6 @@ use tracing_log::NormalizeEvent; /// /// [`Json::flatten_event`]: Json::flatten_event() /// [`Json::with_current_span`]: Json::with_current_span() -/// [`Json::with_span_list`]: Json::with_span_list() /// [`valuable`]: https://crates.io/crates/valuable /// [unstable]: crate#unstable-features /// [`valuable::Valuable`]: https://docs.rs/valuable/latest/valuable/trait.Valuable.html @@ -92,7 +89,6 @@ use tracing_log::NormalizeEvent; pub struct Json { pub(crate) flatten_event: bool, pub(crate) display_current_span: bool, - pub(crate) display_span_list: bool, } impl Json { @@ -105,12 +101,6 @@ impl Json { pub fn with_current_span(&mut self, display_current_span: bool) { self.display_current_span = display_current_span; } - - /// If set to `false`, formatted events won't contain a list of all currently - /// entered spans. Spans are logged in a list from root to leaf. - pub fn with_span_list(&mut self, display_span_list: bool) { - self.display_span_list = display_span_list; - } } struct SerializableContext<'a, 'b, Span, N>( @@ -252,8 +242,7 @@ where let format_field_marker: std::marker::PhantomData = std::marker::PhantomData; - let current_span = if self.format.display_current_span || self.format.display_span_list - { + let current_span = if self.format.display_current_span || self.display_span_list { event .parent() .and_then(|id| ctx.span(id)) @@ -296,7 +285,7 @@ where } } - if self.format.display_span_list && current_span.is_some() { + if self.display_span_list && current_span.is_some() { serializer.serialize_entry( "spans", &SerializableContext(&ctx.ctx, format_field_marker), @@ -336,7 +325,6 @@ impl Default for Json { Json { flatten_event: false, display_current_span: true, - display_span_list: true, } } } diff --git a/tracing-subscriber/src/fmt/format/mod.rs b/tracing-subscriber/src/fmt/format/mod.rs index ea2a2eca2..9d2bce562 100644 --- a/tracing-subscriber/src/fmt/format/mod.rs +++ b/tracing-subscriber/src/fmt/format/mod.rs @@ -412,6 +412,7 @@ pub struct Format { pub(crate) display_thread_name: bool, pub(crate) display_filename: bool, pub(crate) display_line_number: bool, + pub(crate) display_span_list: bool, } // === impl Writer === @@ -604,6 +605,7 @@ impl Default for Format { display_thread_name: false, display_filename: false, display_line_number: false, + display_span_list: true, } } } @@ -624,6 +626,7 @@ impl Format { display_thread_name: self.display_thread_name, display_filename: self.display_filename, display_line_number: self.display_line_number, + display_span_list: self.display_span_list, } } @@ -663,6 +666,7 @@ impl Format { display_thread_name: self.display_thread_name, display_filename: true, display_line_number: true, + display_span_list: self.display_span_list, } } @@ -694,6 +698,7 @@ impl Format { display_thread_name: self.display_thread_name, display_filename: self.display_filename, display_line_number: self.display_line_number, + display_span_list: self.display_span_list, } } @@ -723,6 +728,7 @@ impl Format { display_thread_name: self.display_thread_name, display_filename: self.display_filename, display_line_number: self.display_line_number, + display_span_list: self.display_span_list, } } @@ -739,6 +745,7 @@ impl Format { display_thread_name: self.display_thread_name, display_filename: self.display_filename, display_line_number: self.display_line_number, + display_span_list: self.display_span_list, } } @@ -820,6 +827,15 @@ impl Format { .with_file(display_location) } + /// If set to `false`, formatted events won't contain a list of all currently + /// entered spans. Spans are logged in a list from root to leaf. + pub fn with_span_list(self, display_span_list: bool) -> Self { + Format { + display_span_list, + ..self + } + } + #[inline] fn format_timestamp(&self, writer: &mut Writer<'_>) -> fmt::Result where @@ -887,17 +903,6 @@ impl Format { self.format.with_current_span(display_current_span); self } - - /// Sets whether or not the formatter will include a list (from root to - /// leaf) of all currently entered spans in formatted events. - /// - /// See [`format::Json`][Json] - #[cfg(feature = "json")] - #[cfg_attr(docsrs, doc(cfg(feature = "json")))] - pub fn with_span_list(mut self, display_span_list: bool) -> Format { - self.format.with_span_list(display_span_list); - self - } } impl FormatEvent for Format @@ -963,15 +968,27 @@ where let dimmed = writer.dimmed(); - if let Some(scope) = ctx.event_scope() { + if let Some(mut scope) = ctx.event_scope() { let bold = writer.bold(); - - let mut seen = false; - - for span in scope.from_root() { + if self.display_span_list { + let scope_from_root = scope.from_root(); + 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("}"))?; + } + } + write!(writer, "{}", dimmed.paint(":"))?; + } + if seen { + writer.write_char(' ')?; + } + } else if let Some(span) = scope.next() { write!(writer, "{}", bold.paint(span.metadata().name()))?; - seen = true; - let ext = span.extensions(); if let Some(fields) = &ext.get::>() { if !fields.is_empty() { @@ -979,9 +996,6 @@ where } } write!(writer, "{}", dimmed.paint(":"))?; - } - - if seen { writer.write_char(' ')?; } }; @@ -1092,11 +1106,16 @@ where let fmt_ctx = { #[cfg(feature = "ansi")] { - FmtCtx::new(ctx, event.parent(), writer.has_ansi_escapes()) + FmtCtx::new( + ctx, + event.parent(), + self.display_span_list, + writer.has_ansi_escapes(), + ) } #[cfg(not(feature = "ansi"))] { - FmtCtx::new(&ctx, event.parent()) + FmtCtx::new(&ctx, event.parent(), self.display_span_list) } }; write!(writer, "{}", fmt_ctx)?; @@ -1144,11 +1163,20 @@ where ctx.format_fields(writer.by_ref(), event)?; - for span in ctx - .event_scope() - .into_iter() - .flat_map(crate::registry::Scope::from_root) - { + if self.display_span_list { + for span in ctx + .event_scope() + .into_iter() + .flat_map(crate::registry::Scope::from_root) + { + let exts = span.extensions(); + if let Some(fields) = exts.get::>() { + if !fields.is_empty() { + write!(writer, " {}", dimmed.paint(&fields.fields))?; + } + } + } + } else if let Some(span) = ctx.event_scope().as_mut().and_then(|scope| scope.next()) { let exts = span.extensions(); if let Some(fields) = exts.get::>() { if !fields.is_empty() { @@ -1347,6 +1375,7 @@ impl Display for ErrorSourceList<'_> { struct FmtCtx<'a, S, N> { ctx: &'a FmtContext<'a, S, N>, span: Option<&'a span::Id>, + display_span_list: bool, #[cfg(feature = "ansi")] ansi: bool, } @@ -1360,14 +1389,28 @@ where pub(crate) fn new( ctx: &'a FmtContext<'_, S, N>, span: Option<&'a span::Id>, + display_span_list: bool, ansi: bool, ) -> Self { - Self { ctx, span, ansi } + Self { + ctx, + span, + ansi, + display_span_list, + } } #[cfg(not(feature = "ansi"))] - pub(crate) fn new(ctx: &'a FmtContext<'_, S, N>, span: Option<&'a span::Id>) -> Self { - Self { ctx, span } + pub(crate) fn new( + ctx: &'a FmtContext<'_, S, N>, + span: Option<&'a span::Id>, + display_span_list: bool, + ) -> Self { + Self { + ctx, + span, + display_span_list, + } } fn bold(&self) -> Style { @@ -1396,14 +1439,19 @@ where .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()); + if self.display_span_list { + let scope = span.into_iter().flat_map(|span| span.scope().from_root()); - for span in scope { - seen = true; - write!(f, "{}:", bold.paint(span.metadata().name()))?; - } + for span in scope { + seen = true; + write!(f, "{}:", bold.paint(span.metadata().name()))?; + } - if seen { + if seen { + f.write_char(' ')?; + } + } else if let Some(span) = span.iter().next() { + write!(f, "{}:", bold.paint(span.metadata().name()))?; f.write_char(' ')?; } Ok(()) @@ -2111,6 +2159,17 @@ pub(super) mod test { test_overridden_parents(expected, crate::fmt::Subscriber::builder().compact()) } + #[test] + fn overridden_parents_with_span_list_false() { + let expected = "fake time span2: tracing_subscriber::fmt::format::test: hello span=2\n"; + test_overridden_parents( + expected, + crate::fmt::Subscriber::builder() + .compact() + .with_span_list(false), + ) + } + #[test] fn overridden_parents_in_scope() { test_overridden_parents_in_scope( diff --git a/tracing-subscriber/src/fmt/format/pretty.rs b/tracing-subscriber/src/fmt/format/pretty.rs index 9b89b6e60..e35a41735 100644 --- a/tracing-subscriber/src/fmt/format/pretty.rs +++ b/tracing-subscriber/src/fmt/format/pretty.rs @@ -327,6 +327,9 @@ where if !fields.is_empty() { write!(writer, " {} {}", dimmed.paint("with"), fields)?; } + if !self.display_span_list { + break; + } writer.write_char('\n')?; } @@ -478,7 +481,7 @@ impl field::Visit for PrettyVisitor<'_> { "message" => { // Escape ANSI characters to prevent malicious patterns (e.g., terminal injection attacks) self.write_padded(&format_args!("{}{:?}", self.style.prefix(), Escape(value))) - }, + } // Skip fields that are actually log metadata that have already been handled #[cfg(feature = "tracing-log")] name if name.starts_with("log.") => self.result = Ok(()), diff --git a/tracing-subscriber/src/fmt/mod.rs b/tracing-subscriber/src/fmt/mod.rs index 785b6c43f..d4ec36757 100644 --- a/tracing-subscriber/src/fmt/mod.rs +++ b/tracing-subscriber/src/fmt/mod.rs @@ -615,6 +615,15 @@ where } } + /// Sets whether or not the subscriber being built will include a list (from + /// root to leaf) of all currently entered spans in formatted events. + pub fn with_span_list(self, display_span_list: bool) -> Self { + SubscriberBuilder { + inner: self.inner.with_span_list(display_span_list), + ..self + } + } + /// Sets whether or not the formatter emits ANSI terminal escape codes /// for colors and other text formatting. /// @@ -808,20 +817,6 @@ impl SubscriberBuilder SubscriberBuilder, F, W> { - SubscriberBuilder { - filter: self.filter, - inner: self.inner.with_span_list(display_span_list), - } - } } #[cfg(feature = "env-filter")]