subscriber: add an abstraction for building visitors (#241)

## Motivation

The `tracing-fmt` crate crate currently uses a `NewVisitor` trait to
abstract over constructing field visitors of different types or with
different configurations:
d51d4d69d8/tracing-fmt/src/lib.rs (L192-L196)

There have been other use-cases where having this abstraction seems
valuable. For example, issue #73 is a proposal for adding utilities for
wrapping a visitor to skip a set of excluded field names. A `MakeVisitor`
abstraction would allow us to provide much more ergonomic APIs for
composing this kind of visitor behaviour.

Additionally, the current trait in `tracing-fmt` is tied pretty closely
to `tracing-fmt- specific behaviour. A generalized version of this trait
should be generic over the input type used to build the visitor.

## Solution

This PR adds a new `MakeVisitor` trait in `tracing-subscriber`.
`MakeVisitor` generalizes the `NewVisitor` trait in `tracing-fmt` to
represent constructing visitors for arbitrary targets. In addition, I've
added some additional traits representing common patterns for visitors,
such as those that format to a `fmt::Write` instance or write to an IO
stream, or those that produce output once a set of fields have been
visited. I've also added some extension traits & combinators to make
composing visitors easier, and to demonstrate that the trait in this
branch can be used for implementing this kind of wrapper.

I've also rewritten the `tracing-fmt` crate to use the new
`tracing-subscriber::MakeVisitor` trait rather than its homegrown
`fmt`-specifc version.

Closes: #240 

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
This commit is contained in:
Eliza Weisman 2019-10-08 14:06:37 -07:00 committed by GitHub
parent 87c075e349
commit 06daa9512b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1130 additions and 150 deletions

View File

@ -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,
);
});
}

View File

@ -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>(V);
// TODO(eliza): When `error` as a primitive type is stable, add a
// `DisplayErrors` wrapper...
// === impl Alt ===
//
impl<V> Alt<V> {
/// 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<T, V> MakeVisitor<T> for Alt<V>
where
V: MakeVisitor<T>,
{
type Visitor = Alt<V::Visitor>;
#[inline]
fn make_visitor(&self, target: T) -> Self::Visitor {
Alt(self.0.make_visitor(target))
}
}
impl<V> Visit for Alt<V>
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<V, O> VisitOutput<O> for Alt<V>
where
V: VisitOutput<O>,
{
#[inline]
fn finish(self) -> O {
self.0.finish()
}
}
impl<V> VisitWrite for Alt<V>
where
V: VisitWrite,
{
#[inline]
fn writer(&mut self) -> &mut dyn io::Write {
self.0.writer()
}
}
impl<V> VisitFmt for Alt<V>
where
V: VisitFmt,
{
#[inline]
fn writer(&mut self) -> &mut dyn fmt::Write {
self.0.writer()
}
}

View File

@ -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<D, V> {
delimiter: D,
inner: V,
}
/// A visitor wrapper that inserts a delimiter after the wrapped visitor formats
/// a field value.
#[derive(Debug)]
pub struct VisitDelimited<D, V> {
delimiter: D,
seen: bool,
inner: V,
err: fmt::Result,
}
// === impl Delimited ===
impl<D, V, T> MakeVisitor<T> for Delimited<D, V>
where
D: AsRef<str> + Clone,
V: MakeVisitor<T>,
V::Visitor: VisitFmt,
{
type Visitor = VisitDelimited<D, V::Visitor>;
fn make_visitor(&self, target: T) -> Self::Visitor {
let inner = self.inner.make_visitor(target);
VisitDelimited::new(self.delimiter.clone(), inner)
}
}
impl<D, V> Delimited<D, V> {
/// 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<D, V> VisitDelimited<D, V> {
/// 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<str>,
{
if self.err.is_err() {
return;
}
if self.seen {
self.err = self.inner.writer().write_str(self.delimiter.as_ref());
}
self.seen = true;
}
}
impl<D, V> Visit for VisitDelimited<D, V>
where
V: VisitFmt,
D: AsRef<str>,
{
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<D, V> VisitOutput<fmt::Result> for VisitDelimited<D, V>
where
V: VisitFmt,
D: AsRef<str>,
{
fn finish(self) -> fmt::Result {
self.err?;
self.inner.finish()
}
}
impl<D, V> VisitFmt for VisitDelimited<D, V>
where
V: VisitFmt,
D: AsRef<str>,
{
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"
);
});
}
}

View File

@ -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>(V);
// TODO(eliza): When `error` as a primitive type is stable, add a
// `DisplayErrors` wrapper...
// === impl Messages ===
//
impl<V> Messages<V> {
/// 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<T, V> MakeVisitor<T> for Messages<V>
where
V: MakeVisitor<T>,
{
type Visitor = Messages<V::Visitor>;
#[inline]
fn make_visitor(&self, target: T) -> Self::Visitor {
Messages(self.0.make_visitor(target))
}
}
impl<V> Visit for Messages<V>
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<V, O> VisitOutput<O> for Messages<V>
where
V: VisitOutput<O>,
{
#[inline]
fn finish(self) -> O {
self.0.finish()
}
}
impl<V> VisitWrite for Messages<V>
where
V: VisitWrite,
{
#[inline]
fn writer(&mut self) -> &mut dyn io::Write {
self.0.writer()
}
}
impl<V> VisitFmt for Messages<V>
where
V: VisitFmt,
{
#[inline]
fn writer(&mut self) -> &mut dyn fmt::Write {
self.0.writer()
}
}

View File

@ -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<T> {
/// 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<Out>: 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<R>(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: 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<RecordFieldsMarker> {
/// 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<T, Out>
where
Self: MakeVisitor<T> + crate::sealed::Sealed<(T, Out)>,
Self::Visitor: VisitOutput<Out>,
{
/// Visits all fields in `fields` with a new visitor constructed from
/// `target`.
fn visit_with<F>(&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<Result<(), io::Error>> {
/// 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<fmt::Result> {
/// Returns the formatter that this visitor writes to.
fn writer(&mut self) -> &mut dyn fmt::Write;
}
/// Extension trait providing `MakeVisitor` combinators.
pub trait MakeExt<T>
where
Self: MakeVisitor<T> + Sized,
Self: crate::sealed::Sealed<MakeExtMarker<T>>,
{
/// Wraps `self` so that any `fmt::Debug` fields are recorded using the
/// alternate formatter (`{:#?}`).
fn debug_alt(self) -> debug::Alt<Self> {
debug::Alt::new(self)
}
/// Wraps `self` so that any string fields named "message" are recorded
/// using `fmt::Display`.
fn display_messages(self) -> display::Messages<Self> {
display::Messages::new(self)
}
/// Wraps `self` so that when fields are formatted to a writer, they are
/// separated by the provided `delimiter`.
fn delimited<D>(self, delimiter: D) -> delimited::Delimited<D, Self>
where
D: AsRef<str> + Clone,
Self::Visitor: VisitFmt,
{
delimited::Delimited::new(delimiter, self)
}
}
// === impl RecordFields ===
impl<'a> crate::sealed::Sealed<RecordFieldsMarker> 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<RecordFieldsMarker> 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<RecordFieldsMarker> 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<RecordFieldsMarker> 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<T, V, F> MakeVisitor<T> for F
where
F: Fn(T) -> V,
V: Visit,
{
type Visitor = V;
fn make_visitor(&self, target: T) -> Self::Visitor {
(self)(target)
}
}
impl<T, Out, M> crate::sealed::Sealed<(T, Out)> for M
where
M: MakeVisitor<T>,
M::Visitor: VisitOutput<Out>,
{
}
impl<T, Out, M> MakeOutput<T, Out> for M
where
M: MakeVisitor<T>,
M::Visitor: VisitOutput<Out>,
{
}
impl<T, M> crate::sealed::Sealed<MakeExtMarker<T>> for M where M: MakeVisitor<T> + Sized {}
impl<T, M> MakeExt<T> for M
where
M: MakeVisitor<T> + Sized,
M: crate::sealed::Sealed<MakeExtMarker<T>>,
{
}
#[derive(Debug)]
#[doc(hidden)]
pub struct MakeExtMarker<T> {
_p: std::marker::PhantomData<T>,
}
#[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<T>(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<T>(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<fmt::Result> 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)
}
}
}

View File

@ -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<N> {
/// Write a log message for `Event` in `Context` to the given `Write`.
fn format_event(
@ -43,7 +46,50 @@ impl<N> FormatEvent<N>
(*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<R: RecordFields>(
&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: F) -> FieldFn<F>
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>(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<F, T> Format<F, T> {
impl<N, T> FormatEvent<N> for Format<Full, T>
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<N, T> FormatEvent<N> for Format<Compact, T>
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<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()
}
}
/// 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<fmt::Result> 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!("<dyn fmt::Write>"))
.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<F>
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<fmt::Result> 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!("<Fn>"))
.field("writer", &format_args!("<dyn fmt::Write>"))
.field("result", &self.result)
.finish()
}
}
#[cfg(test)]
mod test {

View File

@ -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<format::Full>,
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<format::Full>,
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<format::Full>,
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<N, E, F, W> tracing_core::Subscriber for Subscriber<N, E, F, W>
where
N: for<'a> NewVisitor<'a> + 'static,
N: for<'writer> FormatFields<'writer> + 'static,
E: FormatEvent<N> + 'static,
F: Layer<Formatter<N, E, W>> + 'static,
W: MakeWriter + 'static,
@ -177,17 +181,17 @@ where
// === impl Formatter ===
impl<N, E, W> Formatter<N, E, W>
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<N, E, W> tracing_core::Subscriber for Formatter<N, E, W>
where
N: for<'a> NewVisitor<'a> + 'static,
N: for<'writer> FormatFields<'writer> + 'static,
E: FormatEvent<N> + '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::<Self>() => Some(self as *const Self as *const ()),
// _ if id == TypeId::of::<F>() => Some(&self.filter as *const F as *const ()),
_ if id == TypeId::of::<E>() => Some(&self.fmt_event as *const E as *const ()),
_ if id == TypeId::of::<N>() => Some(&self.new_visitor as *const N as *const ()),
_ if id == TypeId::of::<N>() => 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<N, E, F, W> Builder<N, E, F, W>
where
N: for<'a> NewVisitor<'a> + 'static,
N: for<'writer> FormatFields<'writer> + 'static,
E: FormatEvent<N> + 'static,
W: MakeWriter + 'static,
F: Layer<Formatter<N, E, W>> + 'static,
@ -327,7 +309,7 @@ where
/// Finish the builder, returning a new `FmtSubscriber`.
pub fn finish(self) -> Subscriber<N, E, F, W> {
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<N, L, T, F, W> Builder<N, format::Format<L, T>, 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<T2>(self, timer: T2) -> Builder<N, format::Format<L, T2>, 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<N, format::Format<L, ()>, 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<N, E, crate::reload::Layer<crate::EnvFilter, Formatter<N, E, W>>, 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<N, E, F, W> Builder<N, E, F, W> {
/// Sets the Visitor that the subscriber being built will use to record
/// fields.
pub fn with_visitor<N2>(self, new_visitor: N2) -> Builder<N2, E, F, W>
///
/// 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<'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<N, E, F, W> Builder<N, E, F, W> {
{
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<N, E, F, W> Builder<N, E, F, W> {
pub fn with_max_level(self, filter: impl Into<LevelFilter>) -> Builder<N, E, LevelFilter, W> {
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<N, E, F, W> Builder<N, E, F, W> {
/// See [`format::Compact`].
pub fn compact(self) -> Builder<N, format::Format<format::Compact>, 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<N, E, F, W> Builder<N, E, F, W> {
E2: FormatEvent<N> + '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<N, E, F, W> Builder<N, E, F, W> {
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::<format::NewRecorder>().is_some());
assert!(dispatch.downcast_ref::<format::DefaultFields>().is_some());
assert!(dispatch.downcast_ref::<LevelFilter>().is_some());
assert!(dispatch.downcast_ref::<format::Format>().is_some())
}

View File

@ -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, Slab>, 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<F, E>(&self, mut f: F) -> Result<(), E>
pub fn visit_spans<N, E>(&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<F, R>(&self, f: F) -> Option<R>
pub fn with_current<N, R>(&self, f: N) -> Option<R>
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<R>(&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<N>(&self, attrs: &Attributes<'_>, new_visitor: &N) -> Id
pub(crate) fn new_span<F>(&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<N>(&self, id: &Id, fields: &Record<'_>, new_recorder: &N)
pub(crate) fn record<F>(&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<N>(mut data: Data, attrs: &Attributes<'_>, new_visitor: &N) -> Self
fn new<F>(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<N>(&mut self, mut data: Data, attrs: &Attributes<'_>, new_visitor: &N) -> usize
fn fill<F>(&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<N>(&mut self, fields: &Record<'_>, new_visitor: &N)
fn record<F>(&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;
}

View File

@ -93,6 +93,7 @@ macro_rules! try_lock {
};
}
pub mod field;
pub mod filter;
#[cfg(feature = "fmt")]
pub mod fmt;

View File

@ -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,
};