Skip to content

Commit

Permalink
subscriber: add flatten spans option for json formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
heat1q committed Dec 22, 2023
1 parent bac2508 commit 78929e7
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 13 deletions.
29 changes: 29 additions & 0 deletions tracing-subscriber/src/fmt/fmt_subscriber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,35 @@ impl<C, T, W> Subscriber<C, format::JsonFields, format::Format<format::Json, T>,
..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<C, format::JsonFields, format::Format<format::Json, T>, 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<C, format::JsonFields, format::Format<format::Json, T>, W> {
Subscriber {
fmt_event: self.fmt_event.flatten_span_list(flatten_span_list),
fmt_fields: format::JsonFields::new(),
..self
}
}
}

impl<C, N, E, W> Subscriber<C, N, E, W> {
Expand Down
135 changes: 122 additions & 13 deletions tracing-subscriber/src/fmt/format/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,23 @@ 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)]
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 {
Expand All @@ -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>(
Expand Down Expand Up @@ -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<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
fn serialize_fields<Ser>(&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::<FormattedFields<N>>()
Expand Down Expand Up @@ -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<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
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()
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
}
}
}
Expand Down Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions tracing-subscriber/src/fmt/format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,27 @@ impl<T> Format<Json, T> {
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<Json, T> {
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<Json, T> {
self.format.flatten_span_list(flatten_span_list);
self
}
}

impl<C, N, T> FormatEvent<C, N> for Format<Full, T>
Expand Down
27 changes: 27 additions & 0 deletions tracing-subscriber/src/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,33 @@ impl<T, F, W> CollectorBuilder<format::JsonFields, format::Format<format::Json,
inner: self.inner.with_span_list(display_span_list),
}
}

/// Formats all fields of the current span at root level.
///
/// See [`format::Json`] for details.
pub fn flatten_current_span(
self,
flatten_current_span: bool,
) -> CollectorBuilder<format::JsonFields, format::Format<format::Json, T>, 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<format::JsonFields, format::Format<format::Json, T>, F, W> {
CollectorBuilder {
filter: self.filter,
inner: self.inner.flatten_span_list(flatten_span_list),
}
}
}

impl<N, E, F, W> CollectorBuilder<N, E, reload::Subscriber<F>, W>
Expand Down

0 comments on commit 78929e7

Please sign in to comment.