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,
state: Result<(), S::Error>,
}
@ -192,7 +194,8 @@ impl<S> SerdeMapVisitor<S>
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<S: SerializeMap> SerdeMapVisitor<S> {
/// 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<S::Ok, S::Error> {
pub fn finish(self) -> Result<S::Ok, S::Error> {
self.state?;
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,
state: Result<(), S::Error>,
}
@ -297,7 +302,7 @@ impl<S: SerializeStruct> SerdeStructVisitor<S> {
/// 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<S::Ok, S::Error> {
pub fn finish(self) -> Result<S::Ok, S::Error> {
self.state?;
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).
///
/// 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<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> {
/// Sets the field formatter that the layer being built will use to record
/// 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.
///
/// 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>
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", &timestamp)?;
@ -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<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")]
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
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();

View File

@ -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<F = Full, T = SystemTime> {
format: PhantomData<F>,
format: F,
pub(crate) timer: T,
pub(crate) ansi: bool,
pub(crate) display_target: bool,
@ -143,7 +140,7 @@ pub struct Format<F = Full, T = SystemTime> {
impl Default for Format<Full, SystemTime> {
fn default() -> Self {
Format {
format: PhantomData,
format: Full,
timer: SystemTime,
ansi: true,
display_target: true,
@ -158,7 +155,7 @@ impl<F, T> Format<F, T> {
/// See [`Compact`].
pub fn compact(self) -> Format<Compact, T> {
Format {
format: PhantomData,
format: Compact,
timer: self.timer,
ansi: self.ansi,
display_target: self.display_target,
@ -168,12 +165,25 @@ impl<F, T> Format<F, T> {
/// 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<Json, T> {
Format {
format: PhantomData,
format: Json::default(),
timer: self.timer,
ansi: self.ansi,
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>
where
S: Subscriber + for<'a> LookupSpan<'a>,

View File

@ -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<N, format::Format<format::Compact, T>, 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<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_attr(docsrs, doc(cfg(feature = "env-filter")))]
impl<N, E, W> SubscriberBuilder<N, E, crate::EnvFilter, W>