diff --git a/tracing-fmt/examples/custom_visitor.rs b/tracing-fmt/examples/custom_visitor.rs new file mode 100644 index 00000000..2327adf5 --- /dev/null +++ b/tracing-fmt/examples/custom_visitor.rs @@ -0,0 +1,70 @@ +#![deny(rust_2018_idioms)] +use tracing::{debug, error, info, span, trace, warn, Level}; + +#[tracing::instrument] +fn shave(yak: usize) -> bool { + debug!( + message = "hello! I'm gonna shave a yak.", + excitement = "yay!" + ); + if yak == 3 { + warn!(target: "yak_events", "could not locate yak!"); + false + } else { + trace!(target: "yak_events", "yak shaved successfully"); + true + } +} + +fn shave_all(yaks: usize) -> usize { + let span = span!(Level::TRACE, "shaving_yaks", yaks_to_shave = yaks); + let _enter = span.enter(); + + info!("shaving yaks"); + + let mut num_shaved = 0; + for yak in 1..=yaks { + let shaved = shave(yak); + trace!(target: "yak_events", yak, shaved); + + if !shaved { + error!(message = "failed to shave yak!", yak); + } else { + num_shaved += 1; + } + + trace!(target: "yak_events", yaks_shaved = num_shaved); + } + + num_shaved +} + +fn main() { + use tracing_fmt::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 `tracing-subscriber`'s extension traits to add delimiters + // between fields, and ensure that fields named "message" are + // formatted using fmt::Display. + .display_messages() + .delimited(", "); + + let subscriber = tracing_fmt::FmtSubscriber::builder() + .fmt_fields(formatter) + .finish(); + + tracing::subscriber::with_default(subscriber, || { + let number_of_yaks = 3; + debug!("preparing to shave {} yaks", number_of_yaks); + + let number_shaved = shave_all(number_of_yaks); + + debug!( + message = "yak shaving completed.", + all_yaks_shaved = number_shaved == number_of_yaks, + ); + }); +} diff --git a/tracing-subscriber/src/field/debug.rs b/tracing-subscriber/src/field/debug.rs new file mode 100644 index 00000000..ea7d687c --- /dev/null +++ b/tracing-subscriber/src/field/debug.rs @@ -0,0 +1,100 @@ +//! `MakeVisitor` wrappers for working with `fmt::Debug` fields. +use super::{MakeVisitor, VisitFmt, VisitOutput, VisitWrite}; +use tracing_core::field::{Field, Visit}; + +use std::{fmt, io}; + +/// A visitor wrapper that ensures any `fmt::Debug` fields are formatted using +/// the alternate (`:#`) formatter. +#[derive(Debug, Clone)] +pub struct Alt(V); + +// TODO(eliza): When `error` as a primitive type is stable, add a +// `DisplayErrors` wrapper... + +// === impl Alt === +// +impl Alt { + /// Wraps the provided visitor so that any `fmt::Debug` fields are formated + /// using the alternative (`:#`) formatter. + pub fn new(inner: V) -> Self { + Alt(inner) + } +} + +impl MakeVisitor for Alt +where + V: MakeVisitor, +{ + type Visitor = Alt; + + #[inline] + fn make_visitor(&self, target: T) -> Self::Visitor { + Alt(self.0.make_visitor(target)) + } +} + +impl Visit for Alt +where + V: Visit, +{ + #[inline] + fn record_i64(&mut self, field: &Field, value: i64) { + self.0.record_i64(field, value) + } + + #[inline] + fn record_u64(&mut self, field: &Field, value: u64) { + self.0.record_u64(field, value) + } + + #[inline] + fn record_bool(&mut self, field: &Field, value: bool) { + self.0.record_bool(field, value) + } + + /// Visit a string value. + fn record_str(&mut self, field: &Field, value: &str) { + self.0.record_str(field, value) + } + + // TODO(eliza): add RecordError when stable + // fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) { + // self.record_debug(field, &format_args!("{}", value)) + // } + + #[inline] + fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { + self.0.record_debug(field, &format_args!("{:#?}", value)) + } +} + +impl VisitOutput for Alt +where + V: VisitOutput, +{ + #[inline] + fn finish(self) -> O { + self.0.finish() + } +} + +impl VisitWrite for Alt +where + V: VisitWrite, +{ + #[inline] + fn writer(&mut self) -> &mut dyn io::Write { + self.0.writer() + } +} + +impl VisitFmt for Alt +where + V: VisitFmt, +{ + #[inline] + fn writer(&mut self) -> &mut dyn fmt::Write { + self.0.writer() + } +} diff --git a/tracing-subscriber/src/field/delimited.rs b/tracing-subscriber/src/field/delimited.rs new file mode 100644 index 00000000..1861f877 --- /dev/null +++ b/tracing-subscriber/src/field/delimited.rs @@ -0,0 +1,183 @@ +//! A `MakeVisitor` wrapper that separates formatted fields with a delimiter. +use super::{MakeVisitor, VisitFmt, VisitOutput}; + +use std::fmt; +use tracing_core::field::{Field, Visit}; + +/// A `MakeVisitor` wrapper that wraps a visitor that writes formatted output so +/// that a delimiter is inserted between writing formatted field values. +#[derive(Debug, Clone)] +pub struct Delimited { + delimiter: D, + inner: V, +} + +/// A visitor wrapper that inserts a delimiter after the wrapped visitor formats +/// a field value. +#[derive(Debug)] +pub struct VisitDelimited { + delimiter: D, + seen: bool, + inner: V, + err: fmt::Result, +} + +// === impl Delimited === + +impl MakeVisitor for Delimited +where + D: AsRef + Clone, + V: MakeVisitor, + V::Visitor: VisitFmt, +{ + type Visitor = VisitDelimited; + fn make_visitor(&self, target: T) -> Self::Visitor { + let inner = self.inner.make_visitor(target); + VisitDelimited::new(self.delimiter.clone(), inner) + } +} + +impl Delimited { + /// Returns a new [`MakeVisitor`] implementation that wraps `inner` so that + /// it will format each visited field separated by the provided `delimiter`. + /// + /// [`MakeVisitor`]: ../trait.MakeVisitor.html + pub fn new(delimiter: D, inner: V) -> Self { + Self { delimiter, inner } + } +} + +// === impl VisitDelimited === + +impl VisitDelimited { + /// Returns a new [`Visit`] implementation that wraps `inner` so that + /// each formatted field is separated by the provided `delimiter`. + /// + /// [`Visit`]: https://docs.rs/tracing-core/0.1.6/tracing_core/field/trait.Visit.html + pub fn new(delimiter: D, inner: V) -> Self { + Self { + delimiter, + inner, + seen: false, + err: Ok(()), + } + } + + fn delimit(&mut self) + where + V: VisitFmt, + D: AsRef, + { + if self.err.is_err() { + return; + } + + if self.seen { + self.err = self.inner.writer().write_str(self.delimiter.as_ref()); + } + + self.seen = true; + } +} + +impl Visit for VisitDelimited +where + V: VisitFmt, + D: AsRef, +{ + fn record_i64(&mut self, field: &Field, value: i64) { + self.delimit(); + self.inner.record_i64(field, value); + } + + fn record_u64(&mut self, field: &Field, value: u64) { + self.delimit(); + self.inner.record_u64(field, value); + } + + fn record_bool(&mut self, field: &Field, value: bool) { + self.delimit(); + self.inner.record_bool(field, value); + } + + fn record_str(&mut self, field: &Field, value: &str) { + self.delimit(); + self.inner.record_str(field, value); + } + + fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { + self.delimit(); + self.inner.record_debug(field, value); + } +} + +impl VisitOutput for VisitDelimited +where + V: VisitFmt, + D: AsRef, +{ + fn finish(self) -> fmt::Result { + self.err?; + self.inner.finish() + } +} + +impl VisitFmt for VisitDelimited +where + V: VisitFmt, + D: AsRef, +{ + fn writer(&mut self) -> &mut dyn fmt::Write { + self.inner.writer() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::field::test_util::*; + + #[test] + fn delimited_visitor() { + let mut s = String::new(); + let visitor = DebugVisitor::new(&mut s); + let mut visitor = VisitDelimited::new(", ", visitor); + + TestAttrs1::with(|attrs| attrs.record(&mut visitor)); + visitor.finish().unwrap(); + + assert_eq!( + s.as_str(), + "question=\"life, the universe, and everything\", tricky=true, can_you_do_it=true" + ); + } + + #[test] + fn delimited_new_visitor() { + let make = Delimited::new("; ", MakeDebug); + + TestAttrs1::with(|attrs| { + let mut s = String::new(); + { + let mut v = make.make_visitor(&mut s); + attrs.record(&mut v); + } + assert_eq!( + s.as_str(), + "question=\"life, the universe, and everything\"; tricky=true; can_you_do_it=true" + ); + }); + + TestAttrs2::with(|attrs| { + let mut s = String::new(); + { + let mut v = make.make_visitor(&mut s); + attrs.record(&mut v); + } + assert_eq!( + s.as_str(), + "question=None; question.answer=42; tricky=true; can_you_do_it=false" + ); + }); + } +} diff --git a/tracing-subscriber/src/field/display.rs b/tracing-subscriber/src/field/display.rs new file mode 100644 index 00000000..d4b231dc --- /dev/null +++ b/tracing-subscriber/src/field/display.rs @@ -0,0 +1,106 @@ +//! `MakeVisitor` wrappers for working with `fmt::Displag` fields. +use super::{MakeVisitor, VisitFmt, VisitOutput, VisitWrite}; +use tracing_core::field::{Field, Visit}; + +use std::{fmt, io}; + +/// A visitor wrapper that ensures any strings named "message" are formatted +/// using `fmt::Display` +#[derive(Debug, Clone)] +pub struct Messages(V); + +// TODO(eliza): When `error` as a primitive type is stable, add a +// `DisplayErrors` wrapper... + +// === impl Messages === +// +impl Messages { + /// Returns a new [`MakeVisitor`] implementation that will wrap `inner` so + /// that any strings named `message` are formatted using `fmt::Display`. + /// + /// [`MakeVisitor`]: ../trait.MakeVisitor.html + pub fn new(inner: V) -> Self { + Messages(inner) + } +} + +impl MakeVisitor for Messages +where + V: MakeVisitor, +{ + type Visitor = Messages; + + #[inline] + fn make_visitor(&self, target: T) -> Self::Visitor { + Messages(self.0.make_visitor(target)) + } +} + +impl Visit for Messages +where + V: Visit, +{ + #[inline] + fn record_i64(&mut self, field: &Field, value: i64) { + self.0.record_i64(field, value) + } + + #[inline] + fn record_u64(&mut self, field: &Field, value: u64) { + self.0.record_u64(field, value) + } + + #[inline] + fn record_bool(&mut self, field: &Field, value: bool) { + self.0.record_bool(field, value) + } + + /// Visit a string value. + fn record_str(&mut self, field: &Field, value: &str) { + if field.name() == "message" { + self.0.record_debug(field, &format_args!("{}", value)) + } else { + self.0.record_str(field, value) + } + } + + // TODO(eliza): add RecordError when stable + // fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) { + // self.record_debug(field, &format_args!("{}", value)) + // } + + #[inline] + fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { + self.0.record_debug(field, value) + } +} + +impl VisitOutput for Messages +where + V: VisitOutput, +{ + #[inline] + fn finish(self) -> O { + self.0.finish() + } +} + +impl VisitWrite for Messages +where + V: VisitWrite, +{ + #[inline] + fn writer(&mut self) -> &mut dyn io::Write { + self.0.writer() + } +} + +impl VisitFmt for Messages +where + V: VisitFmt, +{ + #[inline] + fn writer(&mut self) -> &mut dyn fmt::Write { + self.0.writer() + } +} diff --git a/tracing-subscriber/src/field/mod.rs b/tracing-subscriber/src/field/mod.rs new file mode 100644 index 00000000..b523aebe --- /dev/null +++ b/tracing-subscriber/src/field/mod.rs @@ -0,0 +1,361 @@ +//! Utilities for working with [fields] and [field visitors]. +//! +//! [fields]: https://docs.rs/tracing-core/latest/tracing_core/field/index.html +//! [field visitors]: https://docs.rs/tracing-core/latest/tracing_core/field/trait.Visit.html +use std::{fmt, io}; +pub use tracing_core::field::Visit; +use tracing_core::{ + span::{Attributes, Record}, + Event, +}; +pub mod debug; +pub mod delimited; +pub mod display; + +/// Creates new [visitors]. +/// +/// A type implementing `MakeVisitor` represents a composable factory for types +/// implementing the [`Visit` trait][visitors]. The `MakeVisitor` trait defines +/// a single function, `make_visitor`, which takes in a `T`-typed `target` and +/// returns a type implementing `Visit` configured for that target. A target may +/// be a string, output stream, or data structure that the visitor will record +/// data to, configuration variables that determine the visitor's behavior, or +/// `()` when no input is required to produce a visitor. +/// +/// [visitors]: https://docs.rs/tracing-core/latest/tracing_core/field/trait.Visit.html +pub trait MakeVisitor { + /// The visitor type produced by this `MakeVisitor`. + type Visitor: Visit; + + /// Make a new visitor for the provided `target`. + fn make_visitor(&self, target: T) -> Self::Visitor; +} + +/// A [visitor] that produces output once it has visited a set of fields. +/// +/// [visitor]: https://docs.rs/tracing-core/latest/tracing_core/field/trait.Visit.html +pub trait VisitOutput: Visit { + /// Completes the visitor, returning any output. + /// + /// This is called once a full set of fields has been visited. + fn finish(self) -> Out; + + /// Visit a set of fields, and return the output of finishing the visitor + /// once the fields have been visited. + fn visit(mut self, fields: &R) -> Out + where + R: RecordFields, + Self: Sized, + { + fields.record(&mut self); + self.finish() + } +} + +/// Extension trait implemented by types which can be recorded by a [visitor]. +/// +/// This allows writing code that is generic over `tracing_core`'s +/// [`span::Attributes`][attr], [`span::Record`][rec], and [`Event`][event] +/// types. These types all provide inherent `record` methods that allow a +/// visitor to record their fields, but there is no common trait representing this. +/// +/// With `RecordFields`, we can write code like this: +/// ``` +/// use tracing_core::field::Visit; +/// # use tracing_core::field::Field; +/// use tracing_subscriber::field::RecordFields; +/// +/// struct MyVisitor { +/// // ... +/// } +/// # impl MyVisitor { fn new() -> Self { Self{} } } +/// impl Visit for MyVisitor { +/// // ... +/// # fn record_debug(&mut self, _: &Field, _: &dyn std::fmt::Debug) {} +/// } +/// +/// fn record_with_my_visitor(r: R) +/// where +/// R: RecordFields, +/// { +/// let mut visitor = MyVisitor::new(); +/// r.record(&mut visitor); +/// } +/// # fn main() {} +/// ``` +/// [visitor]: https://docs.rs/tracing-core/latest/tracing_core/field/trait.Visit.html +/// [attr]: https://docs.rs/tracing-core/latest/tracing_core/span/struct.Attributes.html +/// [rec]: https://docs.rs/tracing-core/latest/tracing_core/span/struct.Record.html +/// [event]: https://docs.rs/tracing-core/latest/tracing_core/event/struct.Event.html +pub trait RecordFields: crate::sealed::Sealed { + /// Record all the fields in `self` with the provided `visitor`. + fn record(&self, visitor: &mut dyn Visit); +} + +/// Extension trait implemented for all `MakeVisitor` implementations that +/// produce a visitor implementing `VisitOutput`. +pub trait MakeOutput +where + Self: MakeVisitor + crate::sealed::Sealed<(T, Out)>, + Self::Visitor: VisitOutput, +{ + /// Visits all fields in `fields` with a new visitor constructed from + /// `target`. + fn visit_with(&self, target: T, fields: &F) -> Out + where + F: RecordFields, + { + self.make_visitor(target).visit(fields) + } +} + +/// Extension trait implemented by visitors to indicate that they write to an +/// `io::Write` instance, and allow access to that writer. +pub trait VisitWrite: VisitOutput> { + /// Returns the writer that this visitor writes to. + fn writer(&mut self) -> &mut dyn io::Write; +} + +/// Extension trait implemented by visitors to indicate that they write to a +/// `fmt::Write` instance, and allow access to that writer. +pub trait VisitFmt: VisitOutput { + /// Returns the formatter that this visitor writes to. + fn writer(&mut self) -> &mut dyn fmt::Write; +} + +/// Extension trait providing `MakeVisitor` combinators. +pub trait MakeExt +where + Self: MakeVisitor + Sized, + Self: crate::sealed::Sealed>, +{ + /// Wraps `self` so that any `fmt::Debug` fields are recorded using the + /// alternate formatter (`{:#?}`). + fn debug_alt(self) -> debug::Alt { + debug::Alt::new(self) + } + + /// Wraps `self` so that any string fields named "message" are recorded + /// using `fmt::Display`. + fn display_messages(self) -> display::Messages { + display::Messages::new(self) + } + + /// Wraps `self` so that when fields are formatted to a writer, they are + /// separated by the provided `delimiter`. + fn delimited(self, delimiter: D) -> delimited::Delimited + where + D: AsRef + Clone, + Self::Visitor: VisitFmt, + { + delimited::Delimited::new(delimiter, self) + } +} + +// === impl RecordFields === + +impl<'a> crate::sealed::Sealed for Event<'a> {} +impl<'a> RecordFields for Event<'a> { + fn record(&self, visitor: &mut dyn Visit) { + Event::record(&self, visitor) + } +} + +impl<'a> crate::sealed::Sealed for Attributes<'a> {} +impl<'a> RecordFields for Attributes<'a> { + fn record(&self, visitor: &mut dyn Visit) { + Attributes::record(&self, visitor) + } +} + +impl<'a> crate::sealed::Sealed for Record<'a> {} +impl<'a> RecordFields for Record<'a> { + fn record(&self, visitor: &mut dyn Visit) { + Record::record(&self, visitor) + } +} + +impl<'a, F> crate::sealed::Sealed for &'a F where F: RecordFields {} +impl<'a, F> RecordFields for &'a F +where + F: RecordFields, +{ + fn record(&self, visitor: &mut dyn Visit) { + F::record(*self, visitor) + } +} + +// === blanket impls === + +impl MakeVisitor for F +where + F: Fn(T) -> V, + V: Visit, +{ + type Visitor = V; + fn make_visitor(&self, target: T) -> Self::Visitor { + (self)(target) + } +} + +impl crate::sealed::Sealed<(T, Out)> for M +where + M: MakeVisitor, + M::Visitor: VisitOutput, +{ +} + +impl MakeOutput for M +where + M: MakeVisitor, + M::Visitor: VisitOutput, +{ +} + +impl crate::sealed::Sealed> for M where M: MakeVisitor + Sized {} + +impl MakeExt for M +where + M: MakeVisitor + Sized, + M: crate::sealed::Sealed>, +{ +} + +#[derive(Debug)] +#[doc(hidden)] +pub struct MakeExtMarker { + _p: std::marker::PhantomData, +} + +#[derive(Debug)] +#[doc(hidden)] +pub struct RecordFieldsMarker { + _p: (), +} + +#[cfg(test)] +#[macro_use] +pub(in crate::field) mod test_util { + use super::*; + use tracing_core::{ + callsite::Callsite, + field::{Field, Value}, + metadata::{Kind, Level, Metadata}, + }; + + pub(crate) struct TestAttrs1; + pub(crate) struct TestAttrs2; + + impl TestAttrs1 { + pub(crate) fn with(f: impl FnOnce(Attributes<'_>) -> T) -> T { + let fieldset = TEST_META_1.fields(); + let values = &[ + ( + &fieldset.field("question").unwrap(), + Some(&"life, the universe, and everything" as &dyn Value), + ), + (&fieldset.field("question.answer").unwrap(), None), + ( + &fieldset.field("tricky").unwrap(), + Some(&true as &dyn Value), + ), + ( + &fieldset.field("can_you_do_it").unwrap(), + Some(&true as &dyn Value), + ), + ]; + let valueset = fieldset.value_set(values); + let attrs = tracing_core::span::Attributes::new(&TEST_META_1, &valueset); + f(attrs) + } + } + + impl TestAttrs2 { + pub(crate) fn with(f: impl FnOnce(Attributes<'_>) -> T) -> T { + let fieldset = TEST_META_1.fields(); + let none = tracing_core::field::debug(&Option::<&str>::None); + let values = &[ + ( + &fieldset.field("question").unwrap(), + Some(&none as &dyn Value), + ), + ( + &fieldset.field("question.answer").unwrap(), + Some(&42 as &dyn Value), + ), + ( + &fieldset.field("tricky").unwrap(), + Some(&true as &dyn Value), + ), + ( + &fieldset.field("can_you_do_it").unwrap(), + Some(&false as &dyn Value), + ), + ]; + let valueset = fieldset.value_set(values); + let attrs = tracing_core::span::Attributes::new(&TEST_META_1, &valueset); + f(attrs) + } + } + + struct TestCallsite1; + static TEST_CALLSITE_1: &'static dyn Callsite = &TestCallsite1; + static TEST_META_1: Metadata<'static> = tracing_core::metadata! { + name: "field_test1", + target: module_path!(), + level: Level::INFO, + fields: &["question", "question.answer", "tricky", "can_you_do_it"], + callsite: TEST_CALLSITE_1, + kind: Kind::SPAN, + }; + + impl Callsite for TestCallsite1 { + fn set_interest(&self, _: tracing_core::subscriber::Interest) { + unimplemented!() + } + + fn metadata(&self) -> &Metadata<'_> { + &TEST_META_1 + } + } + + pub(crate) struct MakeDebug; + pub(crate) struct DebugVisitor<'a> { + writer: &'a mut dyn fmt::Write, + err: fmt::Result, + } + + impl<'a> DebugVisitor<'a> { + pub(crate) fn new(writer: &'a mut dyn fmt::Write) -> Self { + Self { + writer, + err: Ok(()), + } + } + } + + impl<'a> Visit for DebugVisitor<'a> { + fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { + write!(&mut self.writer, "{}={:?}", field, value).unwrap(); + } + } + + impl<'a> VisitOutput for DebugVisitor<'a> { + fn finish(self) -> fmt::Result { + self.err + } + } + + impl<'a> VisitFmt for DebugVisitor<'a> { + fn writer(&mut self) -> &mut dyn fmt::Write { + self.writer + } + } + + impl<'a> MakeVisitor<&'a mut dyn fmt::Write> for MakeDebug { + type Visitor = DebugVisitor<'a>; + fn make_visitor(&self, w: &'a mut dyn fmt::Write) -> DebugVisitor<'a> { + DebugVisitor::new(w) + } + } +} diff --git a/tracing-subscriber/src/fmt/format.rs b/tracing-subscriber/src/fmt/format.rs index ed341b21..b9c37e3c 100644 --- a/tracing-subscriber/src/fmt/format.rs +++ b/tracing-subscriber/src/fmt/format.rs @@ -1,15 +1,15 @@ //! Formatters for logging `tracing` events. use super::span; use super::time::{self, FormatTime, SystemTime}; -#[cfg(feature = "tracing-log")] -use tracing_log::NormalizeEvent; - +use crate::field::{MakeOutput, MakeVisitor, RecordFields, VisitFmt, VisitOutput}; use std::fmt::{self, Write}; use std::marker::PhantomData; use tracing_core::{ - field::{self, Field}, + field::{self, Field, Visit}, Event, Level, }; +#[cfg(feature = "tracing-log")] +use tracing_log::NormalizeEvent; #[cfg(feature = "ansi")] use ansi_term::{Colour, Style}; @@ -20,7 +20,10 @@ use ansi_term::{Colour, Style}; /// dispatched to [`FmtSubscriber`], the subscriber forwards it to its associated `FormatEvent` to /// emit a log message. /// -/// This trait is already implemented for function pointers with the same signature as `format`. +/// This trait is already implemented for function pointers with the same +/// signature as `format_event`. +/// +/// [`FmtSubscriber`]: ../fmt/struct.Subscriber.html pub trait FormatEvent { /// Write a log message for `Event` in `Context` to the given `Write`. fn format_event( @@ -43,7 +46,50 @@ impl FormatEvent (*self)(ctx, writer, event) } } +/// 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( + &self, + writer: &'writer mut dyn fmt::Write, + fields: R, + ) -> fmt::Result; +} +/// Returns a [`FormatFields`] implementation that formats fields using the +/// provided function or closure. +/// +/// [`FormatFields`]: trait.FormatFields.html +pub fn debug_fn(f: F) -> FieldFn +where + F: Fn(&mut dyn fmt::Write, &Field, &dyn fmt::Debug) -> fmt::Result + Clone, +{ + FieldFn(f) +} + +/// A [`FormatFields`] implementation that formats fields by calling a function +/// or closure. +/// +/// [`FormatFields`]: trait.FormatFields.html +#[derive(Debug, Clone)] +pub struct FieldFn(F); +/// The [visitor] produced by [`FieldFn`]'s [`MakeVisitor`] implementation. +/// +/// [visitor]: ../../field/trait.Visit.html +/// [`FieldFn`]: struct.FieldFn.html +/// [`MakeVisitor`]: ../../field/trait.MakeVisitor.html +pub struct FieldFnVisitor<'a, F> { + f: F, + writer: &'a mut dyn fmt::Write, + result: fmt::Result, +} /// Marker for `Format` that indicates that the compact log format should be used. /// /// The compact format only includes the fields from the most recently entered span. @@ -131,7 +177,7 @@ impl Format { impl FormatEvent for Format where - N: for<'a> super::NewVisitor<'a>, + N: for<'writer> FormatFields<'writer>, T: FormatTime, { fn format_event( @@ -176,17 +222,14 @@ where "" } )?; - { - let mut recorder = ctx.new_visitor(writer, true); - event.record(&mut recorder); - } + ctx.format_fields(writer, event)?; writeln!(writer) } } impl FormatEvent for Format where - N: for<'a> super::NewVisitor<'a>, + N: for<'writer> FormatFields<'writer>, T: FormatTime, { fn format_event( @@ -230,60 +273,107 @@ where "" } )?; - { - let mut recorder = ctx.new_visitor(writer, true); - event.record(&mut recorder); - } + ctx.format_fields(writer, event)?; ctx.with_current(|(_, span)| write!(writer, " {}", span.fields())) .unwrap_or(Ok(()))?; writeln!(writer) } } -/// The default implementation of `NewVisitor` that records fields using the -/// default format. +// === impl FormatFields === + +impl<'writer, M> FormatFields<'writer> for M +where + M: MakeOutput<&'writer mut dyn fmt::Write, fmt::Result>, + M::Visitor: VisitFmt + VisitOutput, +{ + fn format_fields( + &self, + writer: &'writer mut dyn fmt::Write, + fields: R, + ) -> fmt::Result { + let mut v = self.make_visitor(writer); + fields.record(&mut v); + v.finish() + } +} +/// The default [`FormatFields`] implementation. +/// +/// [`FormatFields`]: trait.FormatFields.html #[derive(Debug)] -pub struct NewRecorder { - _p: (), +pub struct DefaultFields { + // reserve the ability to add fields to this without causing a breaking + // change in the future. + _private: (), } -impl NewRecorder { - pub(crate) fn new() -> Self { - Self { _p: () } +/// The [visitor] produced by [`DefaultFields`]'s [`MakeVisitor`] implementation. +/// +/// [visitor]: ../../field/trait.Visit.html +/// [`DefaultFields`]: struct.DefaultFields.html +/// [`MakeVisitor`]: ../../field/trait.MakeVisitor.html +pub struct DefaultVisitor<'a> { + writer: &'a mut dyn Write, + is_empty: bool, + result: fmt::Result, +} + +impl DefaultFields { + /// Returns a new default [`FormatFields`] implementation. + /// + /// [`FormatFields`]: trait.FormatFields.html + pub fn new() -> Self { + Self { _private: () } } } -/// A visitor that records fields using the default format. -pub struct Recorder<'a> { - writer: &'a mut dyn Write, - is_empty: bool, +impl Default for DefaultFields { + fn default() -> Self { + Self::new() + } } -impl<'a> Recorder<'a> { - pub(crate) fn new(writer: &'a mut dyn Write, is_empty: bool) -> Self { - Self { writer, is_empty } +impl<'a> MakeVisitor<&'a mut dyn Write> for DefaultFields { + type Visitor = DefaultVisitor<'a>; + + #[inline] + fn make_visitor(&self, target: &'a mut dyn Write) -> Self::Visitor { + DefaultVisitor::new(target, true) + } +} + +// === impl DefaultVisitor === + +impl<'a> DefaultVisitor<'a> { + /// Returns a new default visitor that formats to the provided `writer`. + /// + /// # Arguments + /// - `writer`: the writer to format to. + /// - `is_empty`: whether or not any fields have been previously written to + /// that writer. + pub fn new(writer: &'a mut dyn Write, is_empty: bool) -> Self { + Self { + writer, + is_empty, + result: Ok(()), + } } fn maybe_pad(&mut self) { if self.is_empty { self.is_empty = false; } else { - let _ = write!(self.writer, " "); + self.result = write!(self.writer, " "); } } } -impl<'a> super::NewVisitor<'a> for NewRecorder { - type Visitor = Recorder<'a>; - - #[inline] - fn make(&self, writer: &'a mut dyn Write, is_empty: bool) -> Self::Visitor { - Recorder::new(writer, is_empty) - } -} - -impl<'a> field::Visit for Recorder<'a> { +impl<'a> field::Visit for DefaultVisitor<'a> { fn record_str(&mut self, field: &Field, value: &str) { + if self.result.is_err() { + return; + } + if field.name() == "message" { self.record_debug(field, &format_args!("{}", value)) } else { @@ -303,8 +393,12 @@ impl<'a> field::Visit for Recorder<'a> { } fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { + if self.result.is_err() { + return; + } + self.maybe_pad(); - let _ = match field.name() { + self.result = match field.name() { "message" => write!(self.writer, "{:?}", value), // Skip fields that are actually log metadata that have already been handled #[cfg(feature = "tracing-log")] @@ -315,12 +409,24 @@ impl<'a> field::Visit for Recorder<'a> { } } -// This has to be a manual impl, as `&mut dyn Writer` doesn't implement `Debug`. -impl<'a> fmt::Debug for Recorder<'a> { +impl<'a> crate::field::VisitOutput for DefaultVisitor<'a> { + fn finish(self) -> fmt::Result { + self.result + } +} + +impl<'a> crate::field::VisitFmt for DefaultVisitor<'a> { + fn writer(&mut self) -> &mut dyn fmt::Write { + self.writer + } +} + +impl<'a> fmt::Debug for DefaultVisitor<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Recorder") + f.debug_struct("DefaultVisitor") .field("writer", &format_args!("")) .field("is_empty", &self.is_empty) + .field("result", &self.result) .finish() } } @@ -344,10 +450,7 @@ impl<'a, N: 'a> FmtCtx<'a, N> { } #[cfg(feature = "ansi")] -impl<'a, N> fmt::Display for FmtCtx<'a, N> -where - N: super::NewVisitor<'a>, -{ +impl<'a, N> fmt::Display for FmtCtx<'a, N> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut seen = false; self.ctx.visit_spans(|_, span| { @@ -406,10 +509,7 @@ impl<'a, N: 'a> FullCtx<'a, N> { } #[cfg(feature = "ansi")] -impl<'a, N> fmt::Display for FullCtx<'a, N> -where - N: super::NewVisitor<'a>, -{ +impl<'a, N> fmt::Display for FullCtx<'a, N> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut seen = false; let style = if self.ansi { @@ -510,6 +610,62 @@ impl<'a> fmt::Display for FmtLevel<'a> { } } +// === impl FieldFn === + +impl<'a, F> MakeVisitor<&'a mut dyn fmt::Write> for FieldFn +where + F: Fn(&mut dyn fmt::Write, &Field, &dyn fmt::Debug) -> fmt::Result + Clone, +{ + type Visitor = FieldFnVisitor<'a, F>; + + fn make_visitor(&self, writer: &'a mut dyn fmt::Write) -> Self::Visitor { + FieldFnVisitor { + writer, + f: self.0.clone(), + result: Ok(()), + } + } +} + +impl<'a, F> Visit for FieldFnVisitor<'a, F> +where + F: Fn(&mut dyn fmt::Write, &Field, &dyn fmt::Debug) -> fmt::Result, +{ + fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { + if self.result.is_ok() { + self.result = (self.f)(&mut self.writer, field, value) + } + } +} + +impl<'a, F> VisitOutput for FieldFnVisitor<'a, F> +where + F: Fn(&mut dyn fmt::Write, &Field, &dyn fmt::Debug) -> fmt::Result, +{ + fn finish(self) -> fmt::Result { + self.result + } +} + +impl<'a, F> VisitFmt for FieldFnVisitor<'a, F> +where + F: Fn(&mut dyn fmt::Write, &Field, &dyn fmt::Debug) -> fmt::Result, +{ + fn writer(&mut self) -> &mut dyn fmt::Write { + &mut *self.writer + } +} + +impl<'a, F> fmt::Debug for FieldFnVisitor<'a, F> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FieldFnVisitor") + .field("f", &format_args!("")) + .field("writer", &format_args!("")) + .field("result", &self.result) + .finish() + } +} + #[cfg(test)] mod test { diff --git a/tracing-subscriber/src/fmt/mod.rs b/tracing-subscriber/src/fmt/mod.rs index a129c6a9..301786da 100644 --- a/tracing-subscriber/src/fmt/mod.rs +++ b/tracing-subscriber/src/fmt/mod.rs @@ -10,9 +10,9 @@ //! //! [`tracing`]: https://crates.io/crates/tracing //! [`Subscriber`]: https://docs.rs/tracing/latest/tracing/trait.Subscriber.html -use tracing_core::{field, subscriber::Interest, Event, Metadata}; +use std::{any::TypeId, cell::RefCell, io}; +use tracing_core::{subscriber::Interest, Event, Metadata}; -use std::{any::TypeId, cell::RefCell, fmt, io}; pub mod format; mod span; pub mod time; @@ -22,14 +22,18 @@ use crate::filter::LevelFilter; use crate::layer::{self, Layer}; #[doc(inline)] -pub use self::{format::FormatEvent, span::Context, writer::MakeWriter}; +pub use self::{ + format::{FormatEvent, FormatFields}, + span::Context, + writer::MakeWriter, +}; /// A `Subscriber` that logs formatted representations of `tracing` events. /// -/// This consists of an inner`Formatter` wrapped in a layer that performs filtering. +/// This consists of an inner `Formatter` wrapped in a layer that performs filtering. #[derive(Debug)] pub struct Subscriber< - N = format::NewRecorder, + N = format::DefaultFields, E = format::Format, F = LevelFilter, W = fn() -> io::Stdout, @@ -41,11 +45,11 @@ pub struct Subscriber< /// This type only logs formatted events; it does not perform any filtering. #[derive(Debug)] pub struct Formatter< - N = format::NewRecorder, + N = format::DefaultFields, E = format::Format, W = fn() -> io::Stdout, > { - new_visitor: N, + fmt_fields: N, fmt_event: E, spans: span::Store, settings: Settings, @@ -55,13 +59,13 @@ pub struct Formatter< /// Configures and constructs `Subscriber`s. #[derive(Debug)] pub struct Builder< - N = format::NewRecorder, + N = format::DefaultFields, E = format::Format, F = LevelFilter, W = fn() -> io::Stdout, > { filter: F, - new_visitor: N, + fmt_fields: N, fmt_event: E, settings: Settings, make_writer: W, @@ -103,7 +107,7 @@ impl Default for Subscriber { impl tracing_core::Subscriber for Subscriber where - N: for<'a> NewVisitor<'a> + 'static, + N: for<'writer> FormatFields<'writer> + 'static, E: FormatEvent + 'static, F: Layer> + 'static, W: MakeWriter + 'static, @@ -177,17 +181,17 @@ where // === impl Formatter === impl Formatter where - N: for<'a> NewVisitor<'a>, + N: for<'writer> FormatFields<'writer>, { #[inline] fn ctx(&self) -> span::Context<'_, N> { - span::Context::new(&self.spans, &self.new_visitor) + span::Context::new(&self.spans, &self.fmt_fields) } } impl tracing_core::Subscriber for Formatter where - N: for<'a> NewVisitor<'a> + 'static, + N: for<'writer> FormatFields<'writer> + 'static, E: FormatEvent + 'static, W: MakeWriter + 'static, { @@ -201,12 +205,12 @@ where #[inline] fn new_span(&self, attrs: &span::Attributes<'_>) -> span::Id { - self.spans.new_span(attrs, &self.new_visitor) + self.spans.new_span(attrs, &self.fmt_fields) } #[inline] fn record(&self, span: &span::Id, values: &span::Record<'_>) { - self.spans.record(span, values, &self.new_visitor) + self.spans.record(span, values, &self.fmt_fields) } fn record_follows_from(&self, _span: &span::Id, _follows: &span::Id) { @@ -275,41 +279,19 @@ where _ if id == TypeId::of::() => Some(self as *const Self as *const ()), // _ if id == TypeId::of::() => Some(&self.filter as *const F as *const ()), _ if id == TypeId::of::() => Some(&self.fmt_event as *const E as *const ()), - _ if id == TypeId::of::() => Some(&self.new_visitor as *const N as *const ()), + _ if id == TypeId::of::() => Some(&self.fmt_fields as *const N as *const ()), _ => None, } } } -/// A type that can construct a new field visitor for formatting the fields on a -/// span or event. -pub trait NewVisitor<'a> { - /// The type of the returned `Visitor`. - type Visitor: field::Visit + 'a; - /// Returns a new `Visitor` that writes to the provided `writer`. - fn make(&self, writer: &'a mut dyn fmt::Write, is_empty: bool) -> Self::Visitor; -} - -impl<'a, F, R> NewVisitor<'a> for F -where - F: Fn(&'a mut dyn fmt::Write, bool) -> R, - R: field::Visit + 'a, -{ - type Visitor = R; - - #[inline] - fn make(&self, writer: &'a mut dyn fmt::Write, is_empty: bool) -> Self::Visitor { - (self)(writer, is_empty) - } -} - // ===== impl Builder ===== impl Default for Builder { fn default() -> Self { Builder { filter: Subscriber::DEFAULT_MAX_LEVEL, - new_visitor: format::NewRecorder::new(), + fmt_fields: format::DefaultFields::default(), fmt_event: format::Format::default(), settings: Settings::default(), make_writer: io::stdout, @@ -319,7 +301,7 @@ impl Default for Builder { impl Builder where - N: for<'a> NewVisitor<'a> + 'static, + N: for<'writer> FormatFields<'writer> + 'static, E: FormatEvent + 'static, W: MakeWriter + 'static, F: Layer> + 'static, @@ -327,7 +309,7 @@ where /// Finish the builder, returning a new `FmtSubscriber`. pub fn finish(self) -> Subscriber { let subscriber = Formatter { - new_visitor: self.new_visitor, + fmt_fields: self.fmt_fields, fmt_event: self.fmt_event, spans: span::Store::with_capacity(self.settings.initial_span_capacity), settings: self.settings, @@ -341,12 +323,12 @@ where impl Builder, F, W> where - N: for<'a> NewVisitor<'a> + 'static, + N: for<'writer> FormatFields<'writer> + 'static, { /// Use the given `timer` for log message timestamps. pub fn with_timer(self, timer: T2) -> Builder, F, W> { Builder { - new_visitor: self.new_visitor, + fmt_fields: self.fmt_fields, fmt_event: self.fmt_event.with_timer(timer), filter: self.filter, settings: self.settings, @@ -357,7 +339,7 @@ where /// Do not emit timestamps with log messages. pub fn without_time(self) -> Builder, F, W> { Builder { - new_visitor: self.new_visitor, + fmt_fields: self.fmt_fields, fmt_event: self.fmt_event.without_time(), filter: self.filter, settings: self.settings, @@ -395,7 +377,7 @@ where ) -> Builder>, W> { let (filter, _) = crate::reload::Layer::new(self.filter); Builder { - new_visitor: self.new_visitor, + fmt_fields: self.fmt_fields, fmt_event: self.fmt_event, filter, settings: self.settings, @@ -419,12 +401,30 @@ where impl Builder { /// Sets the Visitor that the subscriber being built will use to record /// fields. - pub fn with_visitor(self, new_visitor: N2) -> Builder + /// + /// 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(self, fmt_fields: N2) -> Builder where - N2: for<'a> NewVisitor<'a> + 'static, + N2: for<'writer> FormatFields<'writer> + 'static, { Builder { - new_visitor, + fmt_fields: fmt_fields.into(), fmt_event: self.fmt_event, filter: self.filter, settings: self.settings, @@ -495,7 +495,7 @@ impl Builder { { let filter = filter.into(); Builder { - new_visitor: self.new_visitor, + fmt_fields: self.fmt_fields, fmt_event: self.fmt_event, filter, settings: self.settings, @@ -559,7 +559,7 @@ impl Builder { pub fn with_max_level(self, filter: impl Into) -> Builder { let filter = filter.into(); Builder { - new_visitor: self.new_visitor, + fmt_fields: self.fmt_fields, fmt_event: self.fmt_event, filter, settings: self.settings, @@ -572,12 +572,12 @@ impl Builder { /// See [`format::Compact`]. pub fn compact(self) -> Builder, F, W> where - N: for<'a> NewVisitor<'a> + 'static, + N: for<'writer> FormatFields<'writer> + 'static, { Builder { fmt_event: format::Format::default().compact(), filter: self.filter, - new_visitor: self.new_visitor, + fmt_fields: self.fmt_fields, settings: self.settings, make_writer: self.make_writer, } @@ -590,7 +590,7 @@ impl Builder { E2: FormatEvent + 'static, { Builder { - new_visitor: self.new_visitor, + fmt_fields: self.fmt_fields, fmt_event, filter: self.filter, settings: self.settings, @@ -646,7 +646,7 @@ impl Builder { W2: MakeWriter + 'static, { Builder { - new_visitor: self.new_visitor, + fmt_fields: self.fmt_fields, fmt_event: self.fmt_event, filter: self.filter, settings: self.settings, @@ -748,7 +748,7 @@ mod test { fn subscriber_downcasts_to_parts() { let subscriber = Subscriber::builder().finish(); let dispatch = Dispatch::new(subscriber); - assert!(dispatch.downcast_ref::().is_some()); + assert!(dispatch.downcast_ref::().is_some()); assert!(dispatch.downcast_ref::().is_some()); assert!(dispatch.downcast_ref::().is_some()) } diff --git a/tracing-subscriber/src/fmt/span.rs b/tracing-subscriber/src/fmt/span.rs index ea8378c1..6238a367 100644 --- a/tracing-subscriber/src/fmt/span.rs +++ b/tracing-subscriber/src/fmt/span.rs @@ -11,6 +11,8 @@ use std::collections::HashSet; pub(crate) use tracing_core::span::{Attributes, Current, Id, Record}; use tracing_core::{dispatcher, Metadata}; +use super::format::FormatFields; + pub struct Span<'a> { lock: OwningHandle, RwLockReadGuard<'a, Slot>>, } @@ -18,9 +20,9 @@ pub struct Span<'a> { /// Represents the `Subscriber`'s view of the current span context to a /// formatter. #[derive(Debug)] -pub struct Context<'a, N> { +pub struct Context<'a, F> { store: &'a Store, - new_visitor: &'a N, + fmt_fields: &'a F, } /// Stores data associated with currently-active spans. @@ -190,7 +192,7 @@ impl<'a> fmt::Debug for Span<'a> { // ===== impl Context ===== -impl<'a, N> Context<'a, N> { +impl<'a, F> Context<'a, F> { /// Applies a function to each span in the current trace context. /// /// The function is applied in order, beginning with the root of the trace, @@ -201,9 +203,9 @@ impl<'a, N> Context<'a, N> { /// /// Note that if we are currently unwinding, this will do nothing, rather /// than potentially causing a double panic. - pub fn visit_spans(&self, mut f: F) -> Result<(), E> + pub fn visit_spans(&self, mut f: N) -> Result<(), E> where - F: FnMut(&Id, Span<'_>) -> Result<(), E>, + N: FnMut(&Id, Span<'_>) -> Result<(), E>, { CONTEXT .try_with(|current| { @@ -223,9 +225,9 @@ impl<'a, N> Context<'a, N> { } /// Executes a closure with the reference to the current span. - pub fn with_current(&self, f: F) -> Option + pub fn with_current(&self, f: N) -> Option where - F: FnOnce((&Id, Span<'_>)) -> R, + N: FnOnce((&Id, Span<'_>)) -> R, { // If the lock is poisoned or the thread local has already been // destroyed, we might be in the middle of unwinding, so this @@ -244,21 +246,21 @@ impl<'a, N> Context<'a, N> { .ok()? } - pub(crate) fn new(store: &'a Store, new_visitor: &'a N) -> Self { - Self { store, new_visitor } + pub(crate) fn new(store: &'a Store, fmt_fields: &'a F) -> Self { + Self { store, fmt_fields } } +} - /// Returns a new visitor that formats span fields to the provided writer. - /// The visitor configuration is provided by the subscriber. - pub fn new_visitor<'writer>( - &self, - writer: &'writer mut dyn fmt::Write, - is_empty: bool, - ) -> N::Visitor +impl<'ctx, 'writer, F> FormatFields<'writer> for Context<'ctx, F> +where + F: FormatFields<'writer>, +{ + #[inline] + fn format_fields(&self, writer: &'writer mut dyn fmt::Write, fields: R) -> fmt::Result where - N: super::NewVisitor<'writer>, + R: crate::field::RecordFields, { - self.new_visitor.make(writer, is_empty) + self.fmt_fields.format_fields(writer, fields) } } @@ -311,9 +313,9 @@ impl Store { /// recently emptied span will be reused. Otherwise, a new allocation will /// be added to the slab. #[inline] - pub(crate) fn new_span(&self, attrs: &Attributes<'_>, new_visitor: &N) -> Id + pub(crate) fn new_span(&self, attrs: &Attributes<'_>, fmt_fields: &F) -> Id where - N: for<'a> super::NewVisitor<'a>, + F: for<'writer> FormatFields<'writer>, { let mut span = Some(Data::new(attrs, self)); @@ -343,7 +345,7 @@ impl Store { // Is our snapshot still valid? if self.next.compare_and_swap(head, next, Ordering::Release) == head { // We can finally fill the slot! - slot.fill(span.take().unwrap(), attrs, new_visitor); + slot.fill(span.take().unwrap(), attrs, fmt_fields); return idx_to_id(head); } } @@ -360,7 +362,7 @@ impl Store { let len = this.slab.len(); // Insert the span into a new slot. - let slot = Slot::new(span.take().unwrap(), attrs, new_visitor); + let slot = Slot::new(span.take().unwrap(), attrs, fmt_fields); this.slab.push(RwLock::new(slot)); // TODO: can we grow the slab in chunks to avoid having to // realloc as often? @@ -388,14 +390,14 @@ impl Store { /// Records that the span with the given `id` has the given `fields`. #[inline] - pub(crate) fn record(&self, id: &Id, fields: &Record<'_>, new_recorder: &N) + pub(crate) fn record(&self, id: &Id, fields: &Record<'_>, fmt_fields: &F) where - N: for<'a> super::NewVisitor<'a>, + F: for<'writer> FormatFields<'writer>, { let slab = try_lock!(self.inner.read(), else return); let slot = slab.write_slot(id_to_idx(id)); if let Some(mut slot) = slot { - slot.record(fields, new_recorder); + slot.record(fields, fmt_fields); } } @@ -481,15 +483,14 @@ impl Drop for Data { } impl Slot { - fn new(mut data: Data, attrs: &Attributes<'_>, new_visitor: &N) -> Self + fn new(mut data: Data, attrs: &Attributes<'_>, fmt_fields: &F) -> Self where - N: for<'a> super::NewVisitor<'a>, + F: for<'writer> FormatFields<'writer>, { let mut fields = String::new(); - { - let mut recorder = new_visitor.make(&mut fields, true); - attrs.record(&mut recorder); - } + fmt_fields + .format_fields(&mut fields, attrs) + .expect("formatting to string should not fail"); if fields.is_empty() { data.is_empty = false; } @@ -506,15 +507,14 @@ impl Slot { } } - fn fill(&mut self, mut data: Data, attrs: &Attributes<'_>, new_visitor: &N) -> usize + fn fill(&mut self, mut data: Data, attrs: &Attributes<'_>, fmt_fields: &F) -> usize where - N: for<'a> super::NewVisitor<'a>, + F: for<'writer> FormatFields<'writer>, { let fields = &mut self.fields; - { - let mut recorder = new_visitor.make(fields, true); - attrs.record(&mut recorder); - } + fmt_fields + .format_fields(fields, attrs) + .expect("formatting to string should not fail"); if fields.is_empty() { data.is_empty = false; } @@ -524,19 +524,18 @@ impl Slot { } } - fn record(&mut self, fields: &Record<'_>, new_visitor: &N) + fn record(&mut self, fields: &Record<'_>, fmt_fields: &F) where - N: for<'a> super::NewVisitor<'a>, + F: for<'writer> FormatFields<'writer>, { let state = &mut self.span; let buf = &mut self.fields; match state { State::Empty(_) => return, State::Full(ref mut data) => { - { - let mut recorder = new_visitor.make(buf, data.is_empty); - fields.record(&mut recorder); - } + fmt_fields + .format_fields(buf, fields) + .expect("formatting to string should not fail"); if buf.is_empty() { data.is_empty = false; } diff --git a/tracing-subscriber/src/lib.rs b/tracing-subscriber/src/lib.rs index 50706afc..f3a0c889 100644 --- a/tracing-subscriber/src/lib.rs +++ b/tracing-subscriber/src/lib.rs @@ -93,6 +93,7 @@ macro_rules! try_lock { }; } +pub mod field; pub mod filter; #[cfg(feature = "fmt")] pub mod fmt; diff --git a/tracing-subscriber/src/prelude.rs b/tracing-subscriber/src/prelude.rs index 327cca55..fa08fe3c 100644 --- a/tracing-subscriber/src/prelude.rs +++ b/tracing-subscriber/src/prelude.rs @@ -3,6 +3,10 @@ //! This brings into scope a number of extension traits that define methods on //! types defined here and in other crates. +pub use crate::field::{ + MakeExt as __tracing_subscriber_field_MakeExt, + RecordFields as __tracing_subscriber_field_RecordFields, +}; pub use crate::layer::{ Layer as __tracing_subscriber_Layer, SubscriberExt as __tracing_subscriber_SubscriberExt, };