subscriber: Flatten json event metadata (#599)

Signed-off-by: Lucio Franco <luciofranco14@gmail.com>
Co-authored-by: Eliza Weisman <eliza@buoyant.io>
This commit is contained in:
Lucio Franco 2020-02-27 17:02:42 -05:00 committed by GitHub
parent ccf49fede5
commit 7e8b1140bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 163 additions and 23 deletions

View File

@ -183,7 +183,9 @@ impl<'a> Serialize for SerializeRecord<'a> {
} }
} }
struct SerdeMapVisitor<S: SerializeMap> { /// Implements `tracing_core::field::Visit` for some `serde::ser::SerializeMap`.
#[derive(Debug)]
pub struct SerdeMapVisitor<S: SerializeMap> {
serializer: S, serializer: S,
state: Result<(), S::Error>, state: Result<(), S::Error>,
} }
@ -192,7 +194,8 @@ impl<S> SerdeMapVisitor<S>
where where
S: SerializeMap, S: SerializeMap,
{ {
fn new(serializer: S) -> Self { /// Create a new map visitor.
pub fn new(serializer: S) -> Self {
Self { Self {
serializer, serializer,
state: Ok(()), state: Ok(()),
@ -243,13 +246,15 @@ impl<S: SerializeMap> SerdeMapVisitor<S> {
/// Completes serializing the visited object, returning `Ok(())` if all /// Completes serializing the visited object, returning `Ok(())` if all
/// fields were serialized correctly, or `Error(S::Error)` if a field could /// fields were serialized correctly, or `Error(S::Error)` if a field could
/// not be serialized. /// not be serialized.
fn finish(self) -> Result<S::Ok, S::Error> { pub fn finish(self) -> Result<S::Ok, S::Error> {
self.state?; self.state?;
self.serializer.end() self.serializer.end()
} }
} }
struct SerdeStructVisitor<S: SerializeStruct> { /// Implements `tracing_core::field::Visit` for some `serde::ser::SerializeStruct`.
#[derive(Debug)]
pub struct SerdeStructVisitor<S: SerializeStruct> {
serializer: S, serializer: S,
state: Result<(), S::Error>, state: Result<(), S::Error>,
} }
@ -297,7 +302,7 @@ impl<S: SerializeStruct> SerdeStructVisitor<S> {
/// Completes serializing the visited object, returning `Ok(())` if all /// Completes serializing the visited object, returning `Ok(())` if all
/// fields were serialized correctly, or `Error(S::Error)` if a field could /// fields were serialized correctly, or `Error(S::Error)` if a field could
/// not be serialized. /// not be serialized.
fn finish(self) -> Result<S::Ok, S::Error> { pub fn finish(self) -> Result<S::Ok, S::Error> {
self.state?; self.state?;
self.serializer.end() self.serializer.end()
} }

View File

@ -263,6 +263,21 @@ where
} }
/// Sets the layer being built to use a [JSON formatter](../fmt/format/struct.Json.html). /// 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(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))] #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub fn json(self) -> LayerBuilder<S, format::JsonFields, format::Format<format::Json, T>, W> { pub fn json(self) -> LayerBuilder<S, format::JsonFields, format::Format<format::Json, T>, W> {
@ -275,6 +290,25 @@ where
} }
} }
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
impl<S, T, W> LayerBuilder<S, format::JsonFields, format::Format<format::Json, T>, 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<S, format::JsonFields, format::Format<format::Json, T>, 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<S, N, E, W> LayerBuilder<S, N, E, W> { impl<S, N, E, W> LayerBuilder<S, N, E, W> {
/// Sets the field formatter that the layer being built will use to record /// Sets the field formatter that the layer being built will use to record
/// fields. /// fields.

View File

@ -22,8 +22,30 @@ use tracing_log::NormalizeEvent;
/// Marker for `Format` that indicates that the verbose json log format should be used. /// Marker for `Format` that indicates that the verbose json log format should be used.
/// ///
/// The full format includes fields from all entered spans. /// 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<S, N, T> FormatEvent<S, N> for Format<Json, T> impl<S, N, T> FormatEvent<S, N> for Format<Json, T>
where where
@ -41,7 +63,6 @@ where
S: Subscriber + for<'a> LookupSpan<'a>, S: Subscriber + for<'a> LookupSpan<'a>,
{ {
use serde_json::{json, Value}; use serde_json::{json, Value};
use tracing_serde::fields::AsMap;
let mut timestamp = String::new(); let mut timestamp = String::new();
self.timer.format_time(&mut timestamp)?; self.timer.format_time(&mut timestamp)?;
@ -52,8 +73,11 @@ where
#[cfg(not(feature = "tracing-log"))] #[cfg(not(feature = "tracing-log"))]
let meta = event.metadata(); let meta = event.metadata();
let flatten_event = self.format.flatten_event;
let mut visit = || { let mut visit = || {
let mut serializer = Serializer::new(WriteAdaptor::new(writer)); let mut serializer = Serializer::new(WriteAdaptor::new(writer));
let mut serializer = serializer.serialize_map(None)?; let mut serializer = serializer.serialize_map(None)?;
serializer.serialize_entry("timestamp", &timestamp)?; serializer.serialize_entry("timestamp", &timestamp)?;
@ -82,8 +106,16 @@ where
serializer.serialize_entry("target", meta.target())?; serializer.serialize_entry("target", meta.target())?;
} }
serializer.serialize_entry("fields", &event.field_map())?; if !flatten_event {
serializer.end() 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)?; 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. /// The JSON [`FormatFields`] implementation.
/// ///
/// [`FormatFields`]: trait.FormatFields.html /// [`FormatFields`]: trait.FormatFields.html
@ -288,16 +328,31 @@ mod test {
let expected = 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"; "{\"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<Vec<u8>> = 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")] #[cfg(feature = "json")]
fn test_json<T>(make_writer: T, expected: &str, buf: &Mutex<Vec<u8>>) fn test_json<T>(make_writer: T, expected: &str, buf: &Mutex<Vec<u8>>, flatten_event: bool)
where where
T: crate::fmt::MakeWriter + Send + Sync + 'static, T: crate::fmt::MakeWriter + Send + Sync + 'static,
{ {
let subscriber = crate::fmt::Subscriber::builder() let subscriber = crate::fmt::Subscriber::builder()
.json() .json()
.flatten_event(flatten_event)
.with_writer(make_writer) .with_writer(make_writer)
.with_timer(MockTime) .with_timer(MockTime)
.finish(); .finish();

View File

@ -7,10 +7,7 @@ use crate::{
registry::LookupSpan, registry::LookupSpan,
}; };
use std::{ use std::fmt::{self, Write};
fmt::{self, Write},
marker::PhantomData,
};
use tracing_core::{ use tracing_core::{
field::{self, Field, Visit}, field::{self, Field, Visit},
Event, Level, Subscriber, Event, Level, Subscriber,
@ -133,7 +130,7 @@ pub struct Full;
/// span. /// span.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Format<F = Full, T = SystemTime> { pub struct Format<F = Full, T = SystemTime> {
format: PhantomData<F>, format: F,
pub(crate) timer: T, pub(crate) timer: T,
pub(crate) ansi: bool, pub(crate) ansi: bool,
pub(crate) display_target: bool, pub(crate) display_target: bool,
@ -143,7 +140,7 @@ pub struct Format<F = Full, T = SystemTime> {
impl Default for Format<Full, SystemTime> { impl Default for Format<Full, SystemTime> {
fn default() -> Self { fn default() -> Self {
Format { Format {
format: PhantomData, format: Full,
timer: SystemTime, timer: SystemTime,
ansi: true, ansi: true,
display_target: true, display_target: true,
@ -158,7 +155,7 @@ impl<F, T> Format<F, T> {
/// See [`Compact`]. /// See [`Compact`].
pub fn compact(self) -> Format<Compact, T> { pub fn compact(self) -> Format<Compact, T> {
Format { Format {
format: PhantomData, format: Compact,
timer: self.timer, timer: self.timer,
ansi: self.ansi, ansi: self.ansi,
display_target: self.display_target, display_target: self.display_target,
@ -168,12 +165,25 @@ impl<F, T> Format<F, T> {
/// Use the full JSON 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(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))] #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub fn json(self) -> Format<Json, T> { pub fn json(self) -> Format<Json, T> {
Format { Format {
format: PhantomData, format: Json::default(),
timer: self.timer, timer: self.timer,
ansi: self.ansi, ansi: self.ansi,
display_target: self.display_target, display_target: self.display_target,
@ -235,6 +245,25 @@ impl<F, T> Format<F, T> {
} }
} }
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
impl<T> Format<Json, T> {
/// 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<Json, T> {
self.format.flatten_event(flatten_event);
self
}
}
impl<S, N, T> FormatEvent<S, N> for Format<Full, T> impl<S, N, T> FormatEvent<S, N> for Format<Full, T>
where where
S: Subscriber + for<'a> LookupSpan<'a>, S: Subscriber + for<'a> LookupSpan<'a>,

View File

@ -419,7 +419,7 @@ where
/// Sets the subscriber being built to use a less verbose formatter. /// 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<N, format::Format<format::Compact, T>, F, W> pub fn compact(self) -> SubscriberBuilder<N, format::Format<format::Compact, T>, F, W>
where where
N: for<'writer> FormatFields<'writer> + 'static, N: for<'writer> FormatFields<'writer> + 'static,
@ -432,7 +432,7 @@ where
/// Sets the subscriber being built to use a JSON formatter. /// 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(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))] #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub fn json( pub fn json(
@ -448,6 +448,23 @@ where
} }
} }
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
impl<T, F, W> SubscriberBuilder<format::JsonFields, format::Format<format::Json, T>, 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<format::JsonFields, format::Format<format::Json, T>, F, W> {
SubscriberBuilder {
filter: self.filter,
inner: self.inner.flatten_event(flatten_event),
}
}
}
#[cfg(feature = "env-filter")] #[cfg(feature = "env-filter")]
#[cfg_attr(docsrs, doc(cfg(feature = "env-filter")))] #[cfg_attr(docsrs, doc(cfg(feature = "env-filter")))]
impl<N, E, W> SubscriberBuilder<N, E, crate::EnvFilter, W> impl<N, E, W> SubscriberBuilder<N, E, crate::EnvFilter, W>