Skip to content

Commit 5d3cc11

Browse files
committed
subscriber: add flatten spans option for json formatter
1 parent bdbaf80 commit 5d3cc11

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
@@ -659,6 +659,35 @@ impl<C, T, W> Subscriber<C, format::JsonFields, format::Format<format::Json, T>,
659659
..self
660660
}
661661
}
662+
663+
/// Formats all fields of the current span at root level.
664+
///
665+
/// See [`format::Json`]
666+
pub fn flatten_current_span(
667+
self,
668+
flatten_current_span: bool,
669+
) -> Subscriber<C, format::JsonFields, format::Format<format::Json, T>, W> {
670+
Subscriber {
671+
fmt_event: self.fmt_event.flatten_current_span(flatten_current_span),
672+
fmt_fields: format::JsonFields::new(),
673+
..self
674+
}
675+
}
676+
677+
/// Formats all fields of the span list at root level, overwritting
678+
/// colliding fields from root to leaf.
679+
///
680+
/// See [`format::Json`]
681+
pub fn flatten_span_list(
682+
self,
683+
flatten_span_list: bool,
684+
) -> Subscriber<C, format::JsonFields, format::Format<format::Json, T>, W> {
685+
Subscriber {
686+
fmt_event: self.fmt_event.flatten_span_list(flatten_span_list),
687+
fmt_fields: format::JsonFields::new(),
688+
..self
689+
}
690+
}
662691
}
663692

664693
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
}
@@ -789,6 +838,66 @@ mod test {
789838
});
790839
}
791840

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

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

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

923944
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)