From 7e8b1140bfd1b12491c9f63315559daa097f3e09 Mon Sep 17 00:00:00 2001 From: Lucio Franco Date: Thu, 27 Feb 2020 17:02:42 -0500 Subject: [PATCH] subscriber: Flatten json event metadata (#599) Signed-off-by: Lucio Franco Co-authored-by: Eliza Weisman --- tracing-serde/src/lib.rs | 15 +++-- tracing-subscriber/src/fmt/fmt_layer.rs | 34 +++++++++++ tracing-subscriber/src/fmt/format/json.rs | 69 ++++++++++++++++++++--- tracing-subscriber/src/fmt/format/mod.rs | 47 ++++++++++++--- tracing-subscriber/src/fmt/mod.rs | 21 ++++++- 5 files changed, 163 insertions(+), 23 deletions(-) diff --git a/tracing-serde/src/lib.rs b/tracing-serde/src/lib.rs index 93c8b191..7e143d64 100644 --- a/tracing-serde/src/lib.rs +++ b/tracing-serde/src/lib.rs @@ -183,7 +183,9 @@ impl<'a> Serialize for SerializeRecord<'a> { } } -struct SerdeMapVisitor { +/// Implements `tracing_core::field::Visit` for some `serde::ser::SerializeMap`. +#[derive(Debug)] +pub struct SerdeMapVisitor { serializer: S, state: Result<(), S::Error>, } @@ -192,7 +194,8 @@ impl SerdeMapVisitor where S: SerializeMap, { - fn new(serializer: S) -> Self { + /// Create a new map visitor. + pub fn new(serializer: S) -> Self { Self { serializer, state: Ok(()), @@ -243,13 +246,15 @@ impl SerdeMapVisitor { /// Completes serializing the visited object, returning `Ok(())` if all /// fields were serialized correctly, or `Error(S::Error)` if a field could /// not be serialized. - fn finish(self) -> Result { + pub fn finish(self) -> Result { self.state?; self.serializer.end() } } -struct SerdeStructVisitor { +/// Implements `tracing_core::field::Visit` for some `serde::ser::SerializeStruct`. +#[derive(Debug)] +pub struct SerdeStructVisitor { serializer: S, state: Result<(), S::Error>, } @@ -297,7 +302,7 @@ impl SerdeStructVisitor { /// Completes serializing the visited object, returning `Ok(())` if all /// fields were serialized correctly, or `Error(S::Error)` if a field could /// not be serialized. - fn finish(self) -> Result { + pub fn finish(self) -> Result { self.state?; self.serializer.end() } diff --git a/tracing-subscriber/src/fmt/fmt_layer.rs b/tracing-subscriber/src/fmt/fmt_layer.rs index 826d9a6c..4de8a688 100644 --- a/tracing-subscriber/src/fmt/fmt_layer.rs +++ b/tracing-subscriber/src/fmt/fmt_layer.rs @@ -263,6 +263,21 @@ where } /// Sets the layer being built to use a [JSON formatter](../fmt/format/struct.Json.html). + /// + /// The full format includes fields from all entered spans. + /// + /// # Example Output + /// + /// ```ignore,json + /// {"timestamp":"Feb 20 11:28:15.096","level":"INFO","target":"mycrate","fields":{"message":"some message", "key": "value"}} + /// ``` + /// + /// # Options + /// + /// - [`LayerBuilder::flatten_event`] can be used to enable flattening event fields into the root + /// object. + /// + /// [`LayerBuilder::flatten_event`]: #method.flatten_event #[cfg(feature = "json")] #[cfg_attr(docsrs, doc(cfg(feature = "json")))] pub fn json(self) -> LayerBuilder, W> { @@ -275,6 +290,25 @@ where } } +#[cfg(feature = "json")] +#[cfg_attr(docsrs, doc(cfg(feature = "json")))] +impl LayerBuilder, W> { + /// Sets the JSON layer being built to flatten event metadata. + /// + /// See [`format::Json`](../fmt/format/struct.Json.html) + pub fn flatten_event( + self, + flatten_event: bool, + ) -> LayerBuilder, W> { + LayerBuilder { + fmt_event: self.fmt_event.flatten_event(flatten_event), + fmt_fields: format::JsonFields::new(), + make_writer: self.make_writer, + _inner: self._inner, + } + } +} + impl LayerBuilder { /// Sets the field formatter that the layer being built will use to record /// fields. diff --git a/tracing-subscriber/src/fmt/format/json.rs b/tracing-subscriber/src/fmt/format/json.rs index b3e3b2ab..1cf6b37c 100644 --- a/tracing-subscriber/src/fmt/format/json.rs +++ b/tracing-subscriber/src/fmt/format/json.rs @@ -22,8 +22,30 @@ use tracing_log::NormalizeEvent; /// Marker for `Format` that indicates that the verbose json log format should be used. /// /// The full format includes fields from all entered spans. -#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] -pub struct Json; +/// +/// # Example Output +/// +/// ```ignore,json +/// {"timestamp":"Feb 20 11:28:15.096","level":"INFO","target":"mycrate","fields":{"message":"some message", "key": "value"}} +/// ``` +/// +/// # Options +/// +/// - [`Json::flatten_event`] can be used to enable flattening event fields into the root +/// object. +/// +/// [`Json::flatten_event`]: #method.flatten_event +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct Json { + pub(crate) flatten_event: bool, +} + +impl Json { + /// If set to `true` event metadata will be flattened into the root object. + pub fn flatten_event(&mut self, flatten_event: bool) { + self.flatten_event = flatten_event; + } +} impl FormatEvent for Format where @@ -41,7 +63,6 @@ where S: Subscriber + for<'a> LookupSpan<'a>, { use serde_json::{json, Value}; - use tracing_serde::fields::AsMap; let mut timestamp = String::new(); self.timer.format_time(&mut timestamp)?; @@ -52,8 +73,11 @@ where #[cfg(not(feature = "tracing-log"))] let meta = event.metadata(); + let flatten_event = self.format.flatten_event; + let mut visit = || { let mut serializer = Serializer::new(WriteAdaptor::new(writer)); + let mut serializer = serializer.serialize_map(None)?; serializer.serialize_entry("timestamp", ×tamp)?; @@ -82,8 +106,16 @@ where serializer.serialize_entry("target", meta.target())?; } - serializer.serialize_entry("fields", &event.field_map())?; - serializer.end() + if !flatten_event { + use tracing_serde::fields::AsMap; + serializer.serialize_entry("fields", &event.field_map())?; + serializer.end() + } else { + let mut visitor = tracing_serde::SerdeMapVisitor::new(serializer); + event.record(&mut visitor); + + visitor.finish() + } }; visit().map_err(|_| fmt::Error)?; @@ -91,6 +123,14 @@ where } } +impl Default for Json { + fn default() -> Json { + Json { + flatten_event: false, + } + } +} + /// The JSON [`FormatFields`] implementation. /// /// [`FormatFields`]: trait.FormatFields.html @@ -288,16 +328,31 @@ mod test { let expected = "{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"span\":{\"answer\":42,\"name\":\"json_span\",\"number\":3},\"target\":\"tracing_subscriber::fmt::format::json::test\",\"fields\":{\"message\":\"some json test\"}}\n"; - test_json(make_writer, expected, &BUF); + test_json(make_writer, expected, &BUF, false); + } + + #[test] + fn json_flattened_event() { + lazy_static! { + static ref BUF: Mutex> = Mutex::new(vec![]); + } + + let make_writer = || MockWriter::new(&BUF); + + let expected = + "{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"span\":{\"answer\":42,\"name\":\"json_span\",\"number\":3},\"target\":\"tracing_subscriber::fmt::format::json::test\",\"message\":\"some json test\"}\n"; + + test_json(make_writer, expected, &BUF, true); } #[cfg(feature = "json")] - fn test_json(make_writer: T, expected: &str, buf: &Mutex>) + fn test_json(make_writer: T, expected: &str, buf: &Mutex>, flatten_event: bool) where T: crate::fmt::MakeWriter + Send + Sync + 'static, { let subscriber = crate::fmt::Subscriber::builder() .json() + .flatten_event(flatten_event) .with_writer(make_writer) .with_timer(MockTime) .finish(); diff --git a/tracing-subscriber/src/fmt/format/mod.rs b/tracing-subscriber/src/fmt/format/mod.rs index 193787ae..dd8d22b9 100644 --- a/tracing-subscriber/src/fmt/format/mod.rs +++ b/tracing-subscriber/src/fmt/format/mod.rs @@ -7,10 +7,7 @@ use crate::{ registry::LookupSpan, }; -use std::{ - fmt::{self, Write}, - marker::PhantomData, -}; +use std::fmt::{self, Write}; use tracing_core::{ field::{self, Field, Visit}, Event, Level, Subscriber, @@ -133,7 +130,7 @@ pub struct Full; /// span. #[derive(Debug, Clone)] pub struct Format { - format: PhantomData, + format: F, pub(crate) timer: T, pub(crate) ansi: bool, pub(crate) display_target: bool, @@ -143,7 +140,7 @@ pub struct Format { impl Default for Format { fn default() -> Self { Format { - format: PhantomData, + format: Full, timer: SystemTime, ansi: true, display_target: true, @@ -158,7 +155,7 @@ impl Format { /// See [`Compact`]. pub fn compact(self) -> Format { Format { - format: PhantomData, + format: Compact, timer: self.timer, ansi: self.ansi, display_target: self.display_target, @@ -168,12 +165,25 @@ impl Format { /// Use the full JSON format. /// - /// See [`Json`]. + /// The full format includes fields from all entered spans. + /// + /// # Example Output + /// + /// ```ignore,json + /// {"timestamp":"Feb 20 11:28:15.096","level":"INFO","target":"mycrate","fields":{"message":"some message", "key": "value"}} + /// ``` + /// + /// # Options + /// + /// - [`Format::flatten_event`] can be used to enable flattening event fields into the root + /// object. + /// + /// [`Format::flatten_event`]: #method.flatten_event #[cfg(feature = "json")] #[cfg_attr(docsrs, doc(cfg(feature = "json")))] pub fn json(self) -> Format { Format { - format: PhantomData, + format: Json::default(), timer: self.timer, ansi: self.ansi, display_target: self.display_target, @@ -235,6 +245,25 @@ impl Format { } } +#[cfg(feature = "json")] +#[cfg_attr(docsrs, doc(cfg(feature = "json")))] +impl Format { + /// Use the full JSON format with the event's event fields flattened. + /// + /// # Example Output + /// + /// ```ignore,json + /// {"timestamp":"Feb 20 11:28:15.096","level":"INFO","target":"mycrate", "message":"some message", "key": "value"} + /// ``` + /// See [`Json`](../format/struct.Json.html). + #[cfg(feature = "json")] + #[cfg_attr(docsrs, doc(cfg(feature = "json")))] + pub fn flatten_event(mut self, flatten_event: bool) -> Format { + self.format.flatten_event(flatten_event); + self + } +} + impl FormatEvent for Format where S: Subscriber + for<'a> LookupSpan<'a>, diff --git a/tracing-subscriber/src/fmt/mod.rs b/tracing-subscriber/src/fmt/mod.rs index 7669aa40..b297855f 100644 --- a/tracing-subscriber/src/fmt/mod.rs +++ b/tracing-subscriber/src/fmt/mod.rs @@ -419,7 +419,7 @@ where /// Sets the subscriber being built to use a less verbose formatter. /// - /// See [`format::Compact`]. + /// See [`format::Compact`](../fmt/format/struct.Compact.html). pub fn compact(self) -> SubscriberBuilder, F, W> where N: for<'writer> FormatFields<'writer> + 'static, @@ -432,7 +432,7 @@ where /// Sets the subscriber being built to use a JSON formatter. /// - /// See [`format::Json`] + /// See [`format::Json`](../fmt/format/struct.Json.html) #[cfg(feature = "json")] #[cfg_attr(docsrs, doc(cfg(feature = "json")))] pub fn json( @@ -448,6 +448,23 @@ where } } +#[cfg(feature = "json")] +#[cfg_attr(docsrs, doc(cfg(feature = "json")))] +impl SubscriberBuilder, F, W> { + /// Sets the json subscriber being built to flatten event metadata. + /// + /// See [`format::Json`](../fmt/format/struct.Json.html) + pub fn flatten_event( + self, + flatten_event: bool, + ) -> SubscriberBuilder, F, W> { + SubscriberBuilder { + filter: self.filter, + inner: self.inner.flatten_event(flatten_event), + } + } +} + #[cfg(feature = "env-filter")] #[cfg_attr(docsrs, doc(cfg(feature = "env-filter")))] impl SubscriberBuilder