Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON formatter #377

Merged
merged 30 commits into from
Oct 18, 2019
Merged

JSON formatter #377

merged 30 commits into from
Oct 18, 2019

Conversation

pimeys
Copy link
Contributor

@pimeys pimeys commented Oct 9, 2019

Motivation

A formatter for JSON output.

Solution

Enable json feature from tracing-subscriber crate. Instantiate a new formatter with JSON output:

let subscriber = FmtSubscriber::builder()
    .json()
    .with_env_filter(EnvFilter::from_default_env())
    .finish();

subscriber::set_global_default(subscriber)?;

@pimeys
Copy link
Contributor Author

pimeys commented Oct 9, 2019

Oh and I just started today with the tracing codebase, so I might not know every detail how this code should be formed. :)

@pimeys pimeys changed the title WIP: JSON formatter JSON formatter Oct 10, 2019
tracing-serde/src/lib.rs Outdated Show resolved Hide resolved
tracing-subscriber/src/fmt/format.rs Show resolved Hide resolved
tracing-subscriber/src/fmt/format.rs Outdated Show resolved Hide resolved
tracing-subscriber/src/fmt/format.rs Outdated Show resolved Hide resolved
@davidbarsky
Copy link
Member

Eugh, sorry about the duplicate comments. Computers!

@pimeys
Copy link
Contributor Author

pimeys commented Oct 10, 2019

Understood how the Serialize structs work now. Planning to add one for times so we can skip the trimming part in the JSON output. Tomorrow!

tracing-serde/src/lib.rs Outdated Show resolved Hide resolved
tracing-serde/src/lib.rs Outdated Show resolved Hide resolved
tracing-serde/src/lib.rs Outdated Show resolved Hide resolved
tracing-serde/src/lib.rs Outdated Show resolved Hide resolved
tracing-subscriber/Cargo.toml Show resolved Hide resolved
Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the changes I requested! I left some notes on the structure of the serialized output.

tracing-serde/src/lib.rs Outdated Show resolved Hide resolved
tracing-subscriber/src/fmt/format.rs Outdated Show resolved Hide resolved
serializer.serialize_entry("timestamp", &timestamp)?;
serializer.serialize_entry("level", &meta.level().as_serde())?;

ctx.with_current(|(_, span)| serializer.serialize_entry("span", span.name()))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will only serialize the name of the immediate parent span. it seems like it might be good to serialize the span's fields as well — note that if we don't also serialize fields, the JSON formatter will output strictly less information than the text formatter, which does include the parent span's fields.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current Span implementation in tracing-subscriber formats fields in a format suitable for plain test logs, but looks kind of bad in JSON. See the test example here:

pimeys@b143824#diff-12298a6463a49769f9b40d8e8b01e754R817

I couldn't really track where and why do we stringify the fields in this way. Would be nicer to have a struct that implements Display and AsSerde to the both formats.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fields are formatted by a type implementing the FormatFields trait:

/// A type that can format a [set of fields] to a `fmt::Write`.
///
/// `FormatFields` is primarily used in the context of [`FmtSubscriber`]. Each
/// time a span or event with fields is recorded, the subscriber will format
/// those fields with its associated `FormatFields` implementation.
///
/// [set of fields]: ../field/trait.RecordFields.html
/// [`FmtSubscriber`]: ../fmt/struct.Subscriber.html
pub trait FormatFields<'writer> {
/// Format the provided `fields` to the provided `writer`, returning a result.
fn format_fields<R: RecordFields>(
&self,
writer: &'writer mut dyn fmt::Write,
fields: R,
) -> fmt::Result;
}

The FormatFields implementation used can be set by the Builder here:
/// Sets the Visitor that the subscriber being built will use to record
/// fields.
///
/// For example:
/// ```rust
/// use tracing_subscriber::fmt::{Subscriber, format};
/// use tracing_subscriber::prelude::*;
///
/// let formatter =
/// // Construct a custom formatter for `Debug` fields
/// format::debug_fn(|writer, field, value| write!(writer, "{}: {:?}", field, value))
/// // Use the `tracing_subscriber::MakeFmtExt` trait to wrap the
/// // formatter so that a delimiter is added between fields.
/// .delimited(", ");
///
/// let subscriber = Subscriber::builder()
/// .fmt_fields(formatter)
/// .finish();
/// # drop(subscriber)
/// ```
pub fn fmt_fields<N2>(self, fmt_fields: N2) -> Builder<N2, E, F, W>
where
N2: for<'writer> FormatFields<'writer> + 'static,
{
Builder {
fmt_fields: fmt_fields.into(),
fmt_event: self.fmt_event,
filter: self.filter,
settings: self.settings,
make_writer: self.make_writer,
}
}

FormatFields is also implemented for any MakeVisitor implementation:
impl<'writer, M> FormatFields<'writer> for M
where
M: MakeOutput<&'writer mut dyn fmt::Write, fmt::Result>,
M::Visitor: VisitFmt + VisitOutput<fmt::Result>,
{
fn format_fields<R: RecordFields>(
&self,
writer: &'writer mut dyn fmt::Write,
fields: R,
) -> fmt::Result {
let mut v = self.make_visitor(writer);
fields.record(&mut v);
v.finish()
}
}

So, we could override the default field formatter to record the fields using a Serde visitor. Although the formatted representation would be stored as a string, we could then interpolate that string into the output, and the fields would be formatted as JSON.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<3

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done now but rendering the fields in an escaped form due to serde not really helping us here. We decided in Discord to leave it how it is for now and come back later to fix the architecture to make field formatting better for different formats.

pimeys@53e3394

tracing-subscriber/src/fmt/format.rs Outdated Show resolved Hide resolved
tracing-subscriber/src/lib.rs Outdated Show resolved Hide resolved
# only required by the json feature
serde_json = { version = "1.0", optional = true }
serde = { version = "1.0", optional = true }
tracing-serde = { path = "../tracing-serde", optional = true }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to publish a tracing-serde release to crates.io before we can publish the changes in this branch — we can't publish a crate that depends on an unpublished crate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. Do you have a plan when we want this to happen?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When these changes land, I can take care of that.

tracing-subscriber/src/fmt/format.rs Outdated Show resolved Hide resolved
tracing-core/src/event.rs Outdated Show resolved Hide resolved
Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really close. Beyond moving the WriteAdaptor type out of tracing-serde, I'd be happy to merge this as-is; most of my comments are for follow-up work.

When this merges, it would be good to open new issues to track those follow-ups.

tracing-serde/src/lib.rs Outdated Show resolved Hide resolved
tracing-subscriber/src/fmt/format/json.rs Show resolved Hide resolved
tracing-subscriber/src/fmt/format/json.rs Show resolved Hide resolved
serializer.serialize_entry("timestamp", &timestamp)?;
serializer.serialize_entry("level", &meta.level().as_serde())?;

ctx.with_current(|(_, span)| serializer.serialize_entry("span", &span))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This serializes only the current span in which the event occurred, rather than the span and all its parents (the way the Format<Full, ...> formatter does. We might want to have separate "full" and "compact" JSON formatters as well (though I'm fine with adding that in a follow-up).

)
.get_matches();

match value_t!(matches, "log_format", LogFormat).unwrap_or(LogFormat::Plain) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would still really like it if all of this was factored out of main into a separate function, but it's not a blocker. It would be fine if that function called set_global_default so that we don't have to handle return type issues...

Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me! Thanks @pimeys for all your hard work — I know this took a while to get through.

We can ignore the nightly CI failure for now; it's due to a nightly change that broke our CI scripts and is unrelated. I'm going to go ahead and merge this when the other builds are done, and open tickets for some of the follow-up work we discussed.

Copy link
Member

@davidbarsky davidbarsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hawkw's review is a more fine-grained superset of mine; I'm happy merging as is!

Signed-off-by: Eliza Weisman <[email protected]>
@hawkw hawkw merged commit d3e0c06 into tokio-rs:master Oct 18, 2019
hawkw added a commit that referenced this pull request Feb 4, 2020
0.2.0 (February 4, 2020)

Breaking Changes:

- **fmt**: Renamed `Context` to `FmtContext` (#420, #425)
- **fmt**: Renamed `Builder` to `SubscriberBuilder` (#420)
- **filter**: Removed `Filter`. Use `EnvFilter` instead (#434)

Added:

- **registry**: `Registry`, a reusable span store that `Layer`s can use
  a high-performance, in-memory store. (#420, #425, #432, #433, #435)
- **registry**: Added `LookupSpan` trait, implemented by `Subscriber`s
  to expose stored span data to `Layer`s (#420)
- **fmt**: Added `fmt::Layer`, to allow composing log formatting with
  other `Layer`s (#420)
- **fmt**: Added support for JSON field and event formatting (#377,
  #415)
- **filter**: Documentation for filtering directives (#554)

Changed:

- **fmt**: Renamed `Context` to `FmtContext` (#420, #425) (BREAKING)
- **fmt**: Renamed `Builder` to `SubscriberBuilder` (#420) (BREAKING)
- **fmt**: Reimplemented `fmt::Subscriber` in terms of the `Registry`
  and `Layer`s (#420)

Removed:

- **filter**: Removed `Filter`. Use `EnvFilter` instead (#434) (BREAKING)

Fixed:

- **fmt**: Fixed memory leaks in the slab used to store per-span data
  (3c35048)
- **fmt**: `fmt::SubscriberBuilder::init` not setting up `log`
  compatibility (#489)
- **fmt**: Spans closed by a child span closing not also closing _their_
  parents (#514)
- **Layer**: Fixed `Layered` subscribers failing to downcast to their
  own type (#549)
- **Layer**: Fixed `Layer::downcast_ref` returning invalid references
  (#454)

Signed-off-by: Eliza Weisman <[email protected]>
hawkw added a commit that referenced this pull request Feb 4, 2020
# 0.2.0 (February 4, 2020)

### Breaking Changes

- **fmt**: Renamed `Context` to `FmtContext` (#420, #425)
- **fmt**: Renamed `Builder` to `SubscriberBuilder` (#420)
- **filter**: Removed `Filter`. Use `EnvFilter` instead (#434)

### Added

- **registry**: `Registry`, a `Subscriber` implementation that `Layer`s
  can use as a high-performance, in-memory span store. (#420, #425, 
  #432, #433, #435)
- **registry**: Added `LookupSpan` trait, implemented by `Subscriber`s
  to expose stored span data to `Layer`s (#420)
- **fmt**: Added `fmt::Layer`, to allow composing log formatting with
  other `Layer`s
- **fmt**: Added support for JSON field and event formatting (#377, 
  #415)
- **filter**: Documentation for filtering directives (#554)

### Changed

- **fmt**: Renamed `Context` to `FmtContext` (#420, #425) (BREAKING)
- **fmt**: Renamed `Builder` to `SubscriberBuilder` (#420) (BREAKING)
- **fmt**: Reimplemented `fmt::Subscriber` in terms of the `Registry`
  and `Layer`s (#420)

### Removed

- **filter**: Removed `Filter`. Use `EnvFilter` instead (#434) 
  (BREAKING)

### Fixed

- **fmt**: Fixed memory leaks in the slab used to store per-span data
  (3c35048)
- **fmt**: `fmt::SubscriberBuilder::init` not setting up `log` 
  compatibility (#489)
- **fmt**: Spans closed by a child span closing not also closing 
  _their_ parents (#514)
- **Layer**: Fixed `Layered` subscribers failing to downcast to their
  own type (#549)
- **Layer**: Fixed `Layer::downcast_ref` returning invalid references
  (#454)

Fixes #515
Fixes #458 

Signed-off-by: Eliza Weisman <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants