Skip to content

Commit 78929e7

Browse files
committed
subscriber: add flatten spans option for json formatter
1 parent bac2508 commit 78929e7

File tree

4 files changed

+199
-13
lines changed

4 files changed

+199
-13
lines changed

tracing-subscriber/src/fmt/fmt_subscriber.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,35 @@ impl<C, T, W> Subscriber<C, format::JsonFields, format::Format<format::Json, T>,
638638
..self
639639
}
640640
}
641+
642+
/// Formats all fields of the current span at root level.
643+
///
644+
/// See [`format::Json`]
645+
pub fn flatten_current_span(
646+
self,
647+
flatten_current_span: bool,
648+
) -> Subscriber<C, format::JsonFields, format::Format<format::Json, T>, W> {
649+
Subscriber {
650+
fmt_event: self.fmt_event.flatten_current_span(flatten_current_span),
651+
fmt_fields: format::JsonFields::new(),
652+
..self
653+
}
654+
}
655+
656+
/// Formats all fields of the span list at root level, overwritting
657+
/// colliding fields from root to leaf.
658+
///
659+
/// See [`format::Json`]
660+
pub fn flatten_span_list(
661+
self,
662+
flatten_span_list: bool,
663+
) -> Subscriber<C, format::JsonFields, format::Format<format::Json, T>, W> {
664+
Subscriber {
665+
fmt_event: self.fmt_event.flatten_span_list(flatten_span_list),
666+
fmt_fields: format::JsonFields::new(),
667+
..self
668+
}
669+
}
641670
}
642671

643672
impl<C, N, E, W> Subscriber<C, N, E, W> {

tracing-subscriber/src/fmt/format/json.rs

Lines changed: 122 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,23 @@ use tracing_log::NormalizeEvent;
6363
/// the root
6464
/// - [`Json::with_current_span`] can be used to control logging of the current
6565
/// span
66+
/// - [`Json::flatten_current_span`] can be used to enable flattening fields of
67+
/// the current span into the root
6668
/// - [`Json::with_span_list`] can be used to control logging of the span list
6769
/// object.
70+
/// - [`Json::flatten_span_list`] can be used to enable flattening all fields of
71+
/// the span list into the root
6872
///
69-
/// By default, event fields are not flattened, and both current span and span
73+
/// By default, event and span fields are not flattened, and both current span and span
7074
/// list are logged.
7175
///
7276
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
7377
pub struct Json {
7478
pub(crate) flatten_event: bool,
7579
pub(crate) display_current_span: bool,
7680
pub(crate) display_span_list: bool,
81+
pub(crate) flatten_current_span: bool,
82+
pub(crate) flatten_span_list: bool,
7783
}
7884

7985
impl Json {
@@ -92,6 +98,16 @@ impl Json {
9298
pub fn with_span_list(&mut self, display_span_list: bool) {
9399
self.display_span_list = display_span_list;
94100
}
101+
102+
/// If set to `true`, the current span will be flattened into the root object.
103+
pub fn flatten_current_span(&mut self, flatten_current_span: bool) {
104+
self.flatten_current_span = flatten_current_span;
105+
}
106+
107+
/// If set to `true`, the span list will be flattened into the root object.
108+
pub fn flatten_span_list(&mut self, flatten_span_list: bool) {
109+
self.flatten_span_list = flatten_span_list;
110+
}
95111
}
96112

97113
struct SerializableContext<'a, 'b, Span, N>(
@@ -132,17 +148,15 @@ where
132148
Span: for<'lookup> crate::registry::LookupSpan<'lookup>,
133149
N: for<'writer> FormatFields<'writer> + 'static;
134150

135-
impl<'a, 'b, Span, N> serde::ser::Serialize for SerializableSpan<'a, 'b, Span, N>
151+
impl<'a, 'b, Span, N> SerializableSpan<'a, 'b, Span, N>
136152
where
137153
Span: for<'lookup> crate::registry::LookupSpan<'lookup>,
138154
N: for<'writer> FormatFields<'writer> + 'static,
139155
{
140-
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
156+
fn serialize_fields<Ser>(&self, serializer: &mut Ser) -> Result<(), Ser::Error>
141157
where
142-
Ser: serde::ser::Serializer,
158+
Ser: serde::ser::SerializeMap,
143159
{
144-
let mut serializer = serializer.serialize_map(None)?;
145-
146160
let ext = self.0.extensions();
147161
let data = ext
148162
.get::<FormattedFields<N>>()
@@ -188,6 +202,25 @@ where
188202
// that the fields are not supposed to be missing.
189203
Err(e) => serializer.serialize_entry("field_error", &format!("{}", e))?,
190204
};
205+
206+
Ok(())
207+
}
208+
}
209+
210+
impl<'a, 'b, Span, N> serde::ser::Serialize for SerializableSpan<'a, 'b, Span, N>
211+
where
212+
Span: for<'lookup> crate::registry::LookupSpan<'lookup>,
213+
N: for<'writer> FormatFields<'writer> + 'static,
214+
{
215+
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
216+
where
217+
Ser: serde::ser::Serializer,
218+
{
219+
let mut serializer = serializer.serialize_map(None)?;
220+
self.serialize_fields(&mut serializer)?;
221+
// The span name is not a field and will only
222+
// be provided if the span is not flattened
223+
// at root level.
191224
serializer.serialize_entry("name", self.0.metadata().name())?;
192225
serializer.end()
193226
}
@@ -271,17 +304,31 @@ where
271304

272305
if self.format.display_current_span {
273306
if let Some(ref span) = current_span {
274-
serializer
275-
.serialize_entry("span", &SerializableSpan(span, format_field_marker))
276-
.unwrap_or(());
307+
let serializable_span = SerializableSpan(span, format_field_marker);
308+
if self.format.flatten_current_span {
309+
serializable_span.serialize_fields(&mut serializer)?;
310+
} else {
311+
serializer
312+
.serialize_entry("span", &serializable_span)
313+
.unwrap_or(());
314+
}
277315
}
278316
}
279317

280318
if self.format.display_span_list && current_span.is_some() {
281-
serializer.serialize_entry(
282-
"spans",
283-
&SerializableContext(&ctx.ctx, format_field_marker),
284-
)?;
319+
if self.format.flatten_span_list {
320+
if let Some(leaf_span) = ctx.ctx.lookup_current() {
321+
for span in leaf_span.scope().from_root() {
322+
SerializableSpan(&span, format_field_marker)
323+
.serialize_fields(&mut serializer)?;
324+
}
325+
}
326+
} else {
327+
serializer.serialize_entry(
328+
"spans",
329+
&SerializableContext(&ctx.ctx, format_field_marker),
330+
)?;
331+
}
285332
}
286333

287334
if self.display_thread_name {
@@ -318,6 +365,8 @@ impl Default for Json {
318365
flatten_event: false,
319366
display_current_span: true,
320367
display_span_list: true,
368+
flatten_current_span: false,
369+
flatten_span_list: false,
321370
}
322371
}
323372
}
@@ -778,6 +827,66 @@ mod test {
778827
});
779828
}
780829

830+
#[test]
831+
fn json_flatten_current_span() {
832+
let expected =
833+
"{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"answer\":42,\"number\":3,\"target\":\"tracing_subscriber::fmt::format::json::test\",\"fields\":{\"message\":\"some json test\"}}\n";
834+
let collector = collector()
835+
.flatten_event(false)
836+
.with_current_span(true)
837+
.flatten_current_span(true)
838+
.with_span_list(false);
839+
test_json(expected, collector, || {
840+
let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3);
841+
let _guard = span.enter();
842+
tracing::info!("some json test");
843+
});
844+
}
845+
846+
#[test]
847+
fn json_flatten_span_list() {
848+
let expected =
849+
"{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"outer\":\"outer\",\"inner\":\"inner\",\"number\":3,\"target\":\"tracing_subscriber::fmt::format::json::test\",\"fields\":{\"message\":\"some json test\"}}\n";
850+
let collector = collector()
851+
.flatten_event(false)
852+
.with_current_span(false)
853+
.with_span_list(true)
854+
.flatten_span_list(true);
855+
test_json(expected, collector, || {
856+
let outer_span = tracing::span!(
857+
tracing::Level::INFO,
858+
"json_outer_span",
859+
outer = "outer",
860+
number = 0
861+
);
862+
let _outer_guard = outer_span.enter();
863+
let inner_span = tracing::span!(
864+
tracing::Level::INFO,
865+
"json_inner_span",
866+
inner = "inner",
867+
number = 3
868+
);
869+
let _inner_guard = inner_span.enter();
870+
tracing::info!("some json test");
871+
});
872+
}
873+
874+
#[test]
875+
fn json_flatten_current_span_with_list() {
876+
let expected =
877+
"{\"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";
878+
let collector = collector()
879+
.flatten_event(false)
880+
.with_current_span(true)
881+
.flatten_current_span(true)
882+
.with_span_list(true);
883+
test_json(expected, collector, || {
884+
let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3);
885+
let _guard = span.enter();
886+
tracing::info!("some json test");
887+
});
888+
}
889+
781890
fn parse_as_json(buffer: &MockMakeWriter) -> serde_json::Value {
782891
let buf = String::from_utf8(buffer.buf().to_vec()).unwrap();
783892
let json = buf

tracing-subscriber/src/fmt/format/mod.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,27 @@ impl<T> Format<Json, T> {
919919
self.format.with_span_list(display_span_list);
920920
self
921921
}
922+
923+
/// Formats all fields of the current span at root level.
924+
///
925+
/// See [`Json`]
926+
#[cfg(feature = "json")]
927+
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
928+
pub fn flatten_current_span(mut self, flatten_current_span: bool) -> Format<Json, T> {
929+
self.format.flatten_current_span(flatten_current_span);
930+
self
931+
}
932+
933+
/// Formats all fields of the span list at root level, overwritting
934+
/// colliding fields from root to leaf.
935+
///
936+
/// See [`Json`]
937+
#[cfg(feature = "json")]
938+
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
939+
pub fn flatten_span_list(mut self, flatten_span_list: bool) -> Format<Json, T> {
940+
self.format.flatten_span_list(flatten_span_list);
941+
self
942+
}
922943
}
923944

924945
impl<C, N, T> FormatEvent<C, N> for Format<Full, T>

tracing-subscriber/src/fmt/mod.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,33 @@ impl<T, F, W> CollectorBuilder<format::JsonFields, format::Format<format::Json,
815815
inner: self.inner.with_span_list(display_span_list),
816816
}
817817
}
818+
819+
/// Formats all fields of the current span at root level.
820+
///
821+
/// See [`format::Json`] for details.
822+
pub fn flatten_current_span(
823+
self,
824+
flatten_current_span: bool,
825+
) -> CollectorBuilder<format::JsonFields, format::Format<format::Json, T>, F, W> {
826+
CollectorBuilder {
827+
filter: self.filter,
828+
inner: self.inner.flatten_current_span(flatten_current_span),
829+
}
830+
}
831+
832+
/// Formats all fields of the span list at root level, overwritting
833+
/// colliding fields from root to leaf.
834+
///
835+
/// See [`format::Json`] for details.
836+
pub fn flatten_span_list(
837+
self,
838+
flatten_span_list: bool,
839+
) -> CollectorBuilder<format::JsonFields, format::Format<format::Json, T>, F, W> {
840+
CollectorBuilder {
841+
filter: self.filter,
842+
inner: self.inner.flatten_span_list(flatten_span_list),
843+
}
844+
}
818845
}
819846

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

0 commit comments

Comments
 (0)