From 78929e754dbdbfe2cecb0a96a77795e7298b5add Mon Sep 17 00:00:00 2001 From: Patrick Willner <50421879+heat1q@users.noreply.github.com> Date: Wed, 30 Aug 2023 14:22:55 +0200 Subject: [PATCH] subscriber: add flatten spans option for json formatter --- tracing-subscriber/src/fmt/fmt_subscriber.rs | 29 ++++ tracing-subscriber/src/fmt/format/json.rs | 135 +++++++++++++++++-- tracing-subscriber/src/fmt/format/mod.rs | 21 +++ tracing-subscriber/src/fmt/mod.rs | 27 ++++ 4 files changed, 199 insertions(+), 13 deletions(-) diff --git a/tracing-subscriber/src/fmt/fmt_subscriber.rs b/tracing-subscriber/src/fmt/fmt_subscriber.rs index 64f9dfc1bb..4c74c2ad5c 100644 --- a/tracing-subscriber/src/fmt/fmt_subscriber.rs +++ b/tracing-subscriber/src/fmt/fmt_subscriber.rs @@ -638,6 +638,35 @@ impl Subscriber, ..self } } + + /// Formats all fields of the current span at root level. + /// + /// See [`format::Json`] + pub fn flatten_current_span( + self, + flatten_current_span: bool, + ) -> Subscriber, W> { + Subscriber { + fmt_event: self.fmt_event.flatten_current_span(flatten_current_span), + fmt_fields: format::JsonFields::new(), + ..self + } + } + + /// Formats all fields of the span list at root level, overwritting + /// colliding fields from root to leaf. + /// + /// See [`format::Json`] + pub fn flatten_span_list( + self, + flatten_span_list: bool, + ) -> Subscriber, W> { + Subscriber { + fmt_event: self.fmt_event.flatten_span_list(flatten_span_list), + fmt_fields: format::JsonFields::new(), + ..self + } + } } impl Subscriber { diff --git a/tracing-subscriber/src/fmt/format/json.rs b/tracing-subscriber/src/fmt/format/json.rs index f4e61fb123..a2a944b475 100644 --- a/tracing-subscriber/src/fmt/format/json.rs +++ b/tracing-subscriber/src/fmt/format/json.rs @@ -63,10 +63,14 @@ use tracing_log::NormalizeEvent; /// the root /// - [`Json::with_current_span`] can be used to control logging of the current /// span +/// - [`Json::flatten_current_span`] can be used to enable flattening fields of +/// the current span into the root /// - [`Json::with_span_list`] can be used to control logging of the span list /// object. +/// - [`Json::flatten_span_list`] can be used to enable flattening all fields of +/// the span list into the root /// -/// By default, event fields are not flattened, and both current span and span +/// By default, event and span fields are not flattened, and both current span and span /// list are logged. /// #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -74,6 +78,8 @@ pub struct Json { pub(crate) flatten_event: bool, pub(crate) display_current_span: bool, pub(crate) display_span_list: bool, + pub(crate) flatten_current_span: bool, + pub(crate) flatten_span_list: bool, } impl Json { @@ -92,6 +98,16 @@ impl Json { pub fn with_span_list(&mut self, display_span_list: bool) { self.display_span_list = display_span_list; } + + /// If set to `true`, the current span will be flattened into the root object. + pub fn flatten_current_span(&mut self, flatten_current_span: bool) { + self.flatten_current_span = flatten_current_span; + } + + /// If set to `true`, the span list will be flattened into the root object. + pub fn flatten_span_list(&mut self, flatten_span_list: bool) { + self.flatten_span_list = flatten_span_list; + } } struct SerializableContext<'a, 'b, Span, N>( @@ -132,17 +148,15 @@ where Span: for<'lookup> crate::registry::LookupSpan<'lookup>, N: for<'writer> FormatFields<'writer> + 'static; -impl<'a, 'b, Span, N> serde::ser::Serialize for SerializableSpan<'a, 'b, Span, N> +impl<'a, 'b, Span, N> SerializableSpan<'a, 'b, Span, N> where Span: for<'lookup> crate::registry::LookupSpan<'lookup>, N: for<'writer> FormatFields<'writer> + 'static, { - fn serialize(&self, serializer: Ser) -> Result + fn serialize_fields(&self, serializer: &mut Ser) -> Result<(), Ser::Error> where - Ser: serde::ser::Serializer, + Ser: serde::ser::SerializeMap, { - let mut serializer = serializer.serialize_map(None)?; - let ext = self.0.extensions(); let data = ext .get::>() @@ -188,6 +202,25 @@ where // that the fields are not supposed to be missing. Err(e) => serializer.serialize_entry("field_error", &format!("{}", e))?, }; + + Ok(()) + } +} + +impl<'a, 'b, Span, N> serde::ser::Serialize for SerializableSpan<'a, 'b, Span, N> +where + Span: for<'lookup> crate::registry::LookupSpan<'lookup>, + N: for<'writer> FormatFields<'writer> + 'static, +{ + fn serialize(&self, serializer: Ser) -> Result + where + Ser: serde::ser::Serializer, + { + let mut serializer = serializer.serialize_map(None)?; + self.serialize_fields(&mut serializer)?; + // The span name is not a field and will only + // be provided if the span is not flattened + // at root level. serializer.serialize_entry("name", self.0.metadata().name())?; serializer.end() } @@ -271,17 +304,31 @@ where if self.format.display_current_span { if let Some(ref span) = current_span { - serializer - .serialize_entry("span", &SerializableSpan(span, format_field_marker)) - .unwrap_or(()); + let serializable_span = SerializableSpan(span, format_field_marker); + if self.format.flatten_current_span { + serializable_span.serialize_fields(&mut serializer)?; + } else { + serializer + .serialize_entry("span", &serializable_span) + .unwrap_or(()); + } } } if self.format.display_span_list && current_span.is_some() { - serializer.serialize_entry( - "spans", - &SerializableContext(&ctx.ctx, format_field_marker), - )?; + if self.format.flatten_span_list { + if let Some(leaf_span) = ctx.ctx.lookup_current() { + for span in leaf_span.scope().from_root() { + SerializableSpan(&span, format_field_marker) + .serialize_fields(&mut serializer)?; + } + } + } else { + serializer.serialize_entry( + "spans", + &SerializableContext(&ctx.ctx, format_field_marker), + )?; + } } if self.display_thread_name { @@ -318,6 +365,8 @@ impl Default for Json { flatten_event: false, display_current_span: true, display_span_list: true, + flatten_current_span: false, + flatten_span_list: false, } } } @@ -778,6 +827,66 @@ mod test { }); } + #[test] + fn json_flatten_current_span() { + let expected = + "{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"answer\":42,\"number\":3,\"target\":\"tracing_subscriber::fmt::format::json::test\",\"fields\":{\"message\":\"some json test\"}}\n"; + let collector = collector() + .flatten_event(false) + .with_current_span(true) + .flatten_current_span(true) + .with_span_list(false); + test_json(expected, collector, || { + let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3); + let _guard = span.enter(); + tracing::info!("some json test"); + }); + } + + #[test] + fn json_flatten_span_list() { + let expected = + "{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"outer\":\"outer\",\"inner\":\"inner\",\"number\":3,\"target\":\"tracing_subscriber::fmt::format::json::test\",\"fields\":{\"message\":\"some json test\"}}\n"; + let collector = collector() + .flatten_event(false) + .with_current_span(false) + .with_span_list(true) + .flatten_span_list(true); + test_json(expected, collector, || { + let outer_span = tracing::span!( + tracing::Level::INFO, + "json_outer_span", + outer = "outer", + number = 0 + ); + let _outer_guard = outer_span.enter(); + let inner_span = tracing::span!( + tracing::Level::INFO, + "json_inner_span", + inner = "inner", + number = 3 + ); + let _inner_guard = inner_span.enter(); + tracing::info!("some json test"); + }); + } + + #[test] + fn json_flatten_current_span_with_list() { + let expected = + "{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"answer\":42,\"number\":3,\"spans\":[{\"answer\":42,\"name\":\"json_span\",\"number\":3}],\"target\":\"tracing_subscriber::fmt::format::json::test\",\"fields\":{\"message\":\"some json test\"}}\n"; + let collector = collector() + .flatten_event(false) + .with_current_span(true) + .flatten_current_span(true) + .with_span_list(true); + test_json(expected, collector, || { + let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3); + let _guard = span.enter(); + tracing::info!("some json test"); + }); + } + fn parse_as_json(buffer: &MockMakeWriter) -> serde_json::Value { let buf = String::from_utf8(buffer.buf().to_vec()).unwrap(); let json = buf diff --git a/tracing-subscriber/src/fmt/format/mod.rs b/tracing-subscriber/src/fmt/format/mod.rs index d4c619b3fb..a932199c51 100644 --- a/tracing-subscriber/src/fmt/format/mod.rs +++ b/tracing-subscriber/src/fmt/format/mod.rs @@ -919,6 +919,27 @@ impl Format { self.format.with_span_list(display_span_list); self } + + /// Formats all fields of the current span at root level. + /// + /// See [`Json`] + #[cfg(feature = "json")] + #[cfg_attr(docsrs, doc(cfg(feature = "json")))] + pub fn flatten_current_span(mut self, flatten_current_span: bool) -> Format { + self.format.flatten_current_span(flatten_current_span); + self + } + + /// Formats all fields of the span list at root level, overwritting + /// colliding fields from root to leaf. + /// + /// See [`Json`] + #[cfg(feature = "json")] + #[cfg_attr(docsrs, doc(cfg(feature = "json")))] + pub fn flatten_span_list(mut self, flatten_span_list: bool) -> Format { + self.format.flatten_span_list(flatten_span_list); + self + } } impl FormatEvent for Format diff --git a/tracing-subscriber/src/fmt/mod.rs b/tracing-subscriber/src/fmt/mod.rs index 49e23e6007..82440b5a56 100644 --- a/tracing-subscriber/src/fmt/mod.rs +++ b/tracing-subscriber/src/fmt/mod.rs @@ -815,6 +815,33 @@ impl CollectorBuilder CollectorBuilder, F, W> { + CollectorBuilder { + filter: self.filter, + inner: self.inner.flatten_current_span(flatten_current_span), + } + } + + /// Formats all fields of the span list at root level, overwritting + /// colliding fields from root to leaf. + /// + /// See [`format::Json`] for details. + pub fn flatten_span_list( + self, + flatten_span_list: bool, + ) -> CollectorBuilder, F, W> { + CollectorBuilder { + filter: self.filter, + inner: self.inner.flatten_span_list(flatten_span_list), + } + } } impl CollectorBuilder, W>