mirror of
https://github.com/tokio-rs/tracing.git
synced 2025-10-03 07:44:42 +00:00
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:
parent
87c075e349
commit
06daa9512b
70
tracing-fmt/examples/custom_visitor.rs
Normal file
70
tracing-fmt/examples/custom_visitor.rs
Normal 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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
100
tracing-subscriber/src/field/debug.rs
Normal file
100
tracing-subscriber/src/field/debug.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
183
tracing-subscriber/src/field/delimited.rs
Normal file
183
tracing-subscriber/src/field/delimited.rs
Normal 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"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
106
tracing-subscriber/src/field/display.rs
Normal file
106
tracing-subscriber/src/field/display.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
361
tracing-subscriber/src/field/mod.rs
Normal file
361
tracing-subscriber/src/field/mod.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,15 @@
|
|||||||
//! Formatters for logging `tracing` events.
|
//! Formatters for logging `tracing` events.
|
||||||
use super::span;
|
use super::span;
|
||||||
use super::time::{self, FormatTime, SystemTime};
|
use super::time::{self, FormatTime, SystemTime};
|
||||||
#[cfg(feature = "tracing-log")]
|
use crate::field::{MakeOutput, MakeVisitor, RecordFields, VisitFmt, VisitOutput};
|
||||||
use tracing_log::NormalizeEvent;
|
|
||||||
|
|
||||||
use std::fmt::{self, Write};
|
use std::fmt::{self, Write};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use tracing_core::{
|
use tracing_core::{
|
||||||
field::{self, Field},
|
field::{self, Field, Visit},
|
||||||
Event, Level,
|
Event, Level,
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "tracing-log")]
|
||||||
|
use tracing_log::NormalizeEvent;
|
||||||
|
|
||||||
#[cfg(feature = "ansi")]
|
#[cfg(feature = "ansi")]
|
||||||
use ansi_term::{Colour, Style};
|
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
|
/// dispatched to [`FmtSubscriber`], the subscriber forwards it to its associated `FormatEvent` to
|
||||||
/// emit a log message.
|
/// 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> {
|
pub trait FormatEvent<N> {
|
||||||
/// Write a log message for `Event` in `Context` to the given `Write`.
|
/// Write a log message for `Event` in `Context` to the given `Write`.
|
||||||
fn format_event(
|
fn format_event(
|
||||||
@ -43,7 +46,50 @@ impl<N> FormatEvent<N>
|
|||||||
(*self)(ctx, writer, event)
|
(*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.
|
/// 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.
|
/// 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>
|
impl<N, T> FormatEvent<N> for Format<Full, T>
|
||||||
where
|
where
|
||||||
N: for<'a> super::NewVisitor<'a>,
|
N: for<'writer> FormatFields<'writer>,
|
||||||
T: FormatTime,
|
T: FormatTime,
|
||||||
{
|
{
|
||||||
fn format_event(
|
fn format_event(
|
||||||
@ -176,17 +222,14 @@ where
|
|||||||
""
|
""
|
||||||
}
|
}
|
||||||
)?;
|
)?;
|
||||||
{
|
ctx.format_fields(writer, event)?;
|
||||||
let mut recorder = ctx.new_visitor(writer, true);
|
|
||||||
event.record(&mut recorder);
|
|
||||||
}
|
|
||||||
writeln!(writer)
|
writeln!(writer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N, T> FormatEvent<N> for Format<Compact, T>
|
impl<N, T> FormatEvent<N> for Format<Compact, T>
|
||||||
where
|
where
|
||||||
N: for<'a> super::NewVisitor<'a>,
|
N: for<'writer> FormatFields<'writer>,
|
||||||
T: FormatTime,
|
T: FormatTime,
|
||||||
{
|
{
|
||||||
fn format_event(
|
fn format_event(
|
||||||
@ -230,60 +273,107 @@ where
|
|||||||
""
|
""
|
||||||
}
|
}
|
||||||
)?;
|
)?;
|
||||||
{
|
ctx.format_fields(writer, event)?;
|
||||||
let mut recorder = ctx.new_visitor(writer, true);
|
|
||||||
event.record(&mut recorder);
|
|
||||||
}
|
|
||||||
ctx.with_current(|(_, span)| write!(writer, " {}", span.fields()))
|
ctx.with_current(|(_, span)| write!(writer, " {}", span.fields()))
|
||||||
.unwrap_or(Ok(()))?;
|
.unwrap_or(Ok(()))?;
|
||||||
writeln!(writer)
|
writeln!(writer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The default implementation of `NewVisitor` that records fields using the
|
// === impl FormatFields ===
|
||||||
/// default format.
|
|
||||||
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct NewRecorder {
|
pub struct DefaultFields {
|
||||||
_p: (),
|
// reserve the ability to add fields to this without causing a breaking
|
||||||
|
// change in the future.
|
||||||
|
_private: (),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NewRecorder {
|
/// The [visitor] produced by [`DefaultFields`]'s [`MakeVisitor`] implementation.
|
||||||
pub(crate) fn new() -> Self {
|
///
|
||||||
Self { _p: () }
|
/// [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.
|
impl Default for DefaultFields {
|
||||||
pub struct Recorder<'a> {
|
fn default() -> Self {
|
||||||
writer: &'a mut dyn Write,
|
Self::new()
|
||||||
is_empty: bool,
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Recorder<'a> {
|
impl<'a> MakeVisitor<&'a mut dyn Write> for DefaultFields {
|
||||||
pub(crate) fn new(writer: &'a mut dyn Write, is_empty: bool) -> Self {
|
type Visitor = DefaultVisitor<'a>;
|
||||||
Self { writer, is_empty }
|
|
||||||
|
#[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) {
|
fn maybe_pad(&mut self) {
|
||||||
if self.is_empty {
|
if self.is_empty {
|
||||||
self.is_empty = false;
|
self.is_empty = false;
|
||||||
} else {
|
} else {
|
||||||
let _ = write!(self.writer, " ");
|
self.result = write!(self.writer, " ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> super::NewVisitor<'a> for NewRecorder {
|
impl<'a> field::Visit for DefaultVisitor<'a> {
|
||||||
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> {
|
|
||||||
fn record_str(&mut self, field: &Field, value: &str) {
|
fn record_str(&mut self, field: &Field, value: &str) {
|
||||||
|
if self.result.is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if field.name() == "message" {
|
if field.name() == "message" {
|
||||||
self.record_debug(field, &format_args!("{}", value))
|
self.record_debug(field, &format_args!("{}", value))
|
||||||
} else {
|
} else {
|
||||||
@ -303,8 +393,12 @@ impl<'a> field::Visit for Recorder<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
|
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
|
||||||
|
if self.result.is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.maybe_pad();
|
self.maybe_pad();
|
||||||
let _ = match field.name() {
|
self.result = match field.name() {
|
||||||
"message" => write!(self.writer, "{:?}", value),
|
"message" => write!(self.writer, "{:?}", value),
|
||||||
// Skip fields that are actually log metadata that have already been handled
|
// Skip fields that are actually log metadata that have already been handled
|
||||||
#[cfg(feature = "tracing-log")]
|
#[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> crate::field::VisitOutput<fmt::Result> for DefaultVisitor<'a> {
|
||||||
impl<'a> fmt::Debug for Recorder<'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 {
|
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("writer", &format_args!("<dyn fmt::Write>"))
|
||||||
.field("is_empty", &self.is_empty)
|
.field("is_empty", &self.is_empty)
|
||||||
|
.field("result", &self.result)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -344,10 +450,7 @@ impl<'a, N: 'a> FmtCtx<'a, N> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ansi")]
|
#[cfg(feature = "ansi")]
|
||||||
impl<'a, N> fmt::Display for FmtCtx<'a, N>
|
impl<'a, N> fmt::Display for FmtCtx<'a, N> {
|
||||||
where
|
|
||||||
N: super::NewVisitor<'a>,
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let mut seen = false;
|
let mut seen = false;
|
||||||
self.ctx.visit_spans(|_, span| {
|
self.ctx.visit_spans(|_, span| {
|
||||||
@ -406,10 +509,7 @@ impl<'a, N: 'a> FullCtx<'a, N> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ansi")]
|
#[cfg(feature = "ansi")]
|
||||||
impl<'a, N> fmt::Display for FullCtx<'a, N>
|
impl<'a, N> fmt::Display for FullCtx<'a, N> {
|
||||||
where
|
|
||||||
N: super::NewVisitor<'a>,
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let mut seen = false;
|
let mut seen = false;
|
||||||
let style = if self.ansi {
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
|
@ -10,9 +10,9 @@
|
|||||||
//!
|
//!
|
||||||
//! [`tracing`]: https://crates.io/crates/tracing
|
//! [`tracing`]: https://crates.io/crates/tracing
|
||||||
//! [`Subscriber`]: https://docs.rs/tracing/latest/tracing/trait.Subscriber.html
|
//! [`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;
|
pub mod format;
|
||||||
mod span;
|
mod span;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
@ -22,14 +22,18 @@ use crate::filter::LevelFilter;
|
|||||||
use crate::layer::{self, Layer};
|
use crate::layer::{self, Layer};
|
||||||
|
|
||||||
#[doc(inline)]
|
#[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.
|
/// 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)]
|
#[derive(Debug)]
|
||||||
pub struct Subscriber<
|
pub struct Subscriber<
|
||||||
N = format::NewRecorder,
|
N = format::DefaultFields,
|
||||||
E = format::Format<format::Full>,
|
E = format::Format<format::Full>,
|
||||||
F = LevelFilter,
|
F = LevelFilter,
|
||||||
W = fn() -> io::Stdout,
|
W = fn() -> io::Stdout,
|
||||||
@ -41,11 +45,11 @@ pub struct Subscriber<
|
|||||||
/// This type only logs formatted events; it does not perform any filtering.
|
/// This type only logs formatted events; it does not perform any filtering.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Formatter<
|
pub struct Formatter<
|
||||||
N = format::NewRecorder,
|
N = format::DefaultFields,
|
||||||
E = format::Format<format::Full>,
|
E = format::Format<format::Full>,
|
||||||
W = fn() -> io::Stdout,
|
W = fn() -> io::Stdout,
|
||||||
> {
|
> {
|
||||||
new_visitor: N,
|
fmt_fields: N,
|
||||||
fmt_event: E,
|
fmt_event: E,
|
||||||
spans: span::Store,
|
spans: span::Store,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
@ -55,13 +59,13 @@ pub struct Formatter<
|
|||||||
/// Configures and constructs `Subscriber`s.
|
/// Configures and constructs `Subscriber`s.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Builder<
|
pub struct Builder<
|
||||||
N = format::NewRecorder,
|
N = format::DefaultFields,
|
||||||
E = format::Format<format::Full>,
|
E = format::Format<format::Full>,
|
||||||
F = LevelFilter,
|
F = LevelFilter,
|
||||||
W = fn() -> io::Stdout,
|
W = fn() -> io::Stdout,
|
||||||
> {
|
> {
|
||||||
filter: F,
|
filter: F,
|
||||||
new_visitor: N,
|
fmt_fields: N,
|
||||||
fmt_event: E,
|
fmt_event: E,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
make_writer: W,
|
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>
|
impl<N, E, F, W> tracing_core::Subscriber for Subscriber<N, E, F, W>
|
||||||
where
|
where
|
||||||
N: for<'a> NewVisitor<'a> + 'static,
|
N: for<'writer> FormatFields<'writer> + 'static,
|
||||||
E: FormatEvent<N> + 'static,
|
E: FormatEvent<N> + 'static,
|
||||||
F: Layer<Formatter<N, E, W>> + 'static,
|
F: Layer<Formatter<N, E, W>> + 'static,
|
||||||
W: MakeWriter + 'static,
|
W: MakeWriter + 'static,
|
||||||
@ -177,17 +181,17 @@ where
|
|||||||
// === impl Formatter ===
|
// === impl Formatter ===
|
||||||
impl<N, E, W> Formatter<N, E, W>
|
impl<N, E, W> Formatter<N, E, W>
|
||||||
where
|
where
|
||||||
N: for<'a> NewVisitor<'a>,
|
N: for<'writer> FormatFields<'writer>,
|
||||||
{
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn ctx(&self) -> span::Context<'_, N> {
|
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>
|
impl<N, E, W> tracing_core::Subscriber for Formatter<N, E, W>
|
||||||
where
|
where
|
||||||
N: for<'a> NewVisitor<'a> + 'static,
|
N: for<'writer> FormatFields<'writer> + 'static,
|
||||||
E: FormatEvent<N> + 'static,
|
E: FormatEvent<N> + 'static,
|
||||||
W: MakeWriter + 'static,
|
W: MakeWriter + 'static,
|
||||||
{
|
{
|
||||||
@ -201,12 +205,12 @@ where
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn new_span(&self, attrs: &span::Attributes<'_>) -> span::Id {
|
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]
|
#[inline]
|
||||||
fn record(&self, span: &span::Id, values: &span::Record<'_>) {
|
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) {
|
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::<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::<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::<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,
|
_ => 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 Builder =====
|
||||||
|
|
||||||
impl Default for Builder {
|
impl Default for Builder {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Builder {
|
Builder {
|
||||||
filter: Subscriber::DEFAULT_MAX_LEVEL,
|
filter: Subscriber::DEFAULT_MAX_LEVEL,
|
||||||
new_visitor: format::NewRecorder::new(),
|
fmt_fields: format::DefaultFields::default(),
|
||||||
fmt_event: format::Format::default(),
|
fmt_event: format::Format::default(),
|
||||||
settings: Settings::default(),
|
settings: Settings::default(),
|
||||||
make_writer: io::stdout,
|
make_writer: io::stdout,
|
||||||
@ -319,7 +301,7 @@ impl Default for Builder {
|
|||||||
|
|
||||||
impl<N, E, F, W> Builder<N, E, F, W>
|
impl<N, E, F, W> Builder<N, E, F, W>
|
||||||
where
|
where
|
||||||
N: for<'a> NewVisitor<'a> + 'static,
|
N: for<'writer> FormatFields<'writer> + 'static,
|
||||||
E: FormatEvent<N> + 'static,
|
E: FormatEvent<N> + 'static,
|
||||||
W: MakeWriter + 'static,
|
W: MakeWriter + 'static,
|
||||||
F: Layer<Formatter<N, E, W>> + 'static,
|
F: Layer<Formatter<N, E, W>> + 'static,
|
||||||
@ -327,7 +309,7 @@ where
|
|||||||
/// Finish the builder, returning a new `FmtSubscriber`.
|
/// Finish the builder, returning a new `FmtSubscriber`.
|
||||||
pub fn finish(self) -> Subscriber<N, E, F, W> {
|
pub fn finish(self) -> Subscriber<N, E, F, W> {
|
||||||
let subscriber = Formatter {
|
let subscriber = Formatter {
|
||||||
new_visitor: self.new_visitor,
|
fmt_fields: self.fmt_fields,
|
||||||
fmt_event: self.fmt_event,
|
fmt_event: self.fmt_event,
|
||||||
spans: span::Store::with_capacity(self.settings.initial_span_capacity),
|
spans: span::Store::with_capacity(self.settings.initial_span_capacity),
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
@ -341,12 +323,12 @@ where
|
|||||||
|
|
||||||
impl<N, L, T, F, W> Builder<N, format::Format<L, T>, F, W>
|
impl<N, L, T, F, W> Builder<N, format::Format<L, T>, F, W>
|
||||||
where
|
where
|
||||||
N: for<'a> NewVisitor<'a> + 'static,
|
N: for<'writer> FormatFields<'writer> + 'static,
|
||||||
{
|
{
|
||||||
/// Use the given `timer` for log message timestamps.
|
/// Use the given `timer` for log message timestamps.
|
||||||
pub fn with_timer<T2>(self, timer: T2) -> Builder<N, format::Format<L, T2>, F, W> {
|
pub fn with_timer<T2>(self, timer: T2) -> Builder<N, format::Format<L, T2>, F, W> {
|
||||||
Builder {
|
Builder {
|
||||||
new_visitor: self.new_visitor,
|
fmt_fields: self.fmt_fields,
|
||||||
fmt_event: self.fmt_event.with_timer(timer),
|
fmt_event: self.fmt_event.with_timer(timer),
|
||||||
filter: self.filter,
|
filter: self.filter,
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
@ -357,7 +339,7 @@ where
|
|||||||
/// Do not emit timestamps with log messages.
|
/// Do not emit timestamps with log messages.
|
||||||
pub fn without_time(self) -> Builder<N, format::Format<L, ()>, F, W> {
|
pub fn without_time(self) -> Builder<N, format::Format<L, ()>, F, W> {
|
||||||
Builder {
|
Builder {
|
||||||
new_visitor: self.new_visitor,
|
fmt_fields: self.fmt_fields,
|
||||||
fmt_event: self.fmt_event.without_time(),
|
fmt_event: self.fmt_event.without_time(),
|
||||||
filter: self.filter,
|
filter: self.filter,
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
@ -395,7 +377,7 @@ where
|
|||||||
) -> Builder<N, E, crate::reload::Layer<crate::EnvFilter, Formatter<N, E, W>>, W> {
|
) -> Builder<N, E, crate::reload::Layer<crate::EnvFilter, Formatter<N, E, W>>, W> {
|
||||||
let (filter, _) = crate::reload::Layer::new(self.filter);
|
let (filter, _) = crate::reload::Layer::new(self.filter);
|
||||||
Builder {
|
Builder {
|
||||||
new_visitor: self.new_visitor,
|
fmt_fields: self.fmt_fields,
|
||||||
fmt_event: self.fmt_event,
|
fmt_event: self.fmt_event,
|
||||||
filter,
|
filter,
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
@ -419,12 +401,30 @@ where
|
|||||||
impl<N, E, F, W> Builder<N, E, F, W> {
|
impl<N, E, F, W> Builder<N, E, F, W> {
|
||||||
/// Sets the Visitor that the subscriber being built will use to record
|
/// Sets the Visitor that the subscriber being built will use to record
|
||||||
/// fields.
|
/// 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
|
where
|
||||||
N2: for<'a> NewVisitor<'a> + 'static,
|
N2: for<'writer> FormatFields<'writer> + 'static,
|
||||||
{
|
{
|
||||||
Builder {
|
Builder {
|
||||||
new_visitor,
|
fmt_fields: fmt_fields.into(),
|
||||||
fmt_event: self.fmt_event,
|
fmt_event: self.fmt_event,
|
||||||
filter: self.filter,
|
filter: self.filter,
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
@ -495,7 +495,7 @@ impl<N, E, F, W> Builder<N, E, F, W> {
|
|||||||
{
|
{
|
||||||
let filter = filter.into();
|
let filter = filter.into();
|
||||||
Builder {
|
Builder {
|
||||||
new_visitor: self.new_visitor,
|
fmt_fields: self.fmt_fields,
|
||||||
fmt_event: self.fmt_event,
|
fmt_event: self.fmt_event,
|
||||||
filter,
|
filter,
|
||||||
settings: self.settings,
|
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> {
|
pub fn with_max_level(self, filter: impl Into<LevelFilter>) -> Builder<N, E, LevelFilter, W> {
|
||||||
let filter = filter.into();
|
let filter = filter.into();
|
||||||
Builder {
|
Builder {
|
||||||
new_visitor: self.new_visitor,
|
fmt_fields: self.fmt_fields,
|
||||||
fmt_event: self.fmt_event,
|
fmt_event: self.fmt_event,
|
||||||
filter,
|
filter,
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
@ -572,12 +572,12 @@ impl<N, E, F, W> Builder<N, E, F, W> {
|
|||||||
/// See [`format::Compact`].
|
/// See [`format::Compact`].
|
||||||
pub fn compact(self) -> Builder<N, format::Format<format::Compact>, F, W>
|
pub fn compact(self) -> Builder<N, format::Format<format::Compact>, F, W>
|
||||||
where
|
where
|
||||||
N: for<'a> NewVisitor<'a> + 'static,
|
N: for<'writer> FormatFields<'writer> + 'static,
|
||||||
{
|
{
|
||||||
Builder {
|
Builder {
|
||||||
fmt_event: format::Format::default().compact(),
|
fmt_event: format::Format::default().compact(),
|
||||||
filter: self.filter,
|
filter: self.filter,
|
||||||
new_visitor: self.new_visitor,
|
fmt_fields: self.fmt_fields,
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
make_writer: self.make_writer,
|
make_writer: self.make_writer,
|
||||||
}
|
}
|
||||||
@ -590,7 +590,7 @@ impl<N, E, F, W> Builder<N, E, F, W> {
|
|||||||
E2: FormatEvent<N> + 'static,
|
E2: FormatEvent<N> + 'static,
|
||||||
{
|
{
|
||||||
Builder {
|
Builder {
|
||||||
new_visitor: self.new_visitor,
|
fmt_fields: self.fmt_fields,
|
||||||
fmt_event,
|
fmt_event,
|
||||||
filter: self.filter,
|
filter: self.filter,
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
@ -646,7 +646,7 @@ impl<N, E, F, W> Builder<N, E, F, W> {
|
|||||||
W2: MakeWriter + 'static,
|
W2: MakeWriter + 'static,
|
||||||
{
|
{
|
||||||
Builder {
|
Builder {
|
||||||
new_visitor: self.new_visitor,
|
fmt_fields: self.fmt_fields,
|
||||||
fmt_event: self.fmt_event,
|
fmt_event: self.fmt_event,
|
||||||
filter: self.filter,
|
filter: self.filter,
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
@ -748,7 +748,7 @@ mod test {
|
|||||||
fn subscriber_downcasts_to_parts() {
|
fn subscriber_downcasts_to_parts() {
|
||||||
let subscriber = Subscriber::builder().finish();
|
let subscriber = Subscriber::builder().finish();
|
||||||
let dispatch = Dispatch::new(subscriber);
|
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::<LevelFilter>().is_some());
|
||||||
assert!(dispatch.downcast_ref::<format::Format>().is_some())
|
assert!(dispatch.downcast_ref::<format::Format>().is_some())
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,8 @@ use std::collections::HashSet;
|
|||||||
pub(crate) use tracing_core::span::{Attributes, Current, Id, Record};
|
pub(crate) use tracing_core::span::{Attributes, Current, Id, Record};
|
||||||
use tracing_core::{dispatcher, Metadata};
|
use tracing_core::{dispatcher, Metadata};
|
||||||
|
|
||||||
|
use super::format::FormatFields;
|
||||||
|
|
||||||
pub struct Span<'a> {
|
pub struct Span<'a> {
|
||||||
lock: OwningHandle<RwLockReadGuard<'a, Slab>, RwLockReadGuard<'a, Slot>>,
|
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
|
/// Represents the `Subscriber`'s view of the current span context to a
|
||||||
/// formatter.
|
/// formatter.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Context<'a, N> {
|
pub struct Context<'a, F> {
|
||||||
store: &'a Store,
|
store: &'a Store,
|
||||||
new_visitor: &'a N,
|
fmt_fields: &'a F,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores data associated with currently-active spans.
|
/// Stores data associated with currently-active spans.
|
||||||
@ -190,7 +192,7 @@ impl<'a> fmt::Debug for Span<'a> {
|
|||||||
|
|
||||||
// ===== impl Context =====
|
// ===== 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.
|
/// Applies a function to each span in the current trace context.
|
||||||
///
|
///
|
||||||
/// The function is applied in order, beginning with the root of the trace,
|
/// 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
|
/// Note that if we are currently unwinding, this will do nothing, rather
|
||||||
/// than potentially causing a double panic.
|
/// 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
|
where
|
||||||
F: FnMut(&Id, Span<'_>) -> Result<(), E>,
|
N: FnMut(&Id, Span<'_>) -> Result<(), E>,
|
||||||
{
|
{
|
||||||
CONTEXT
|
CONTEXT
|
||||||
.try_with(|current| {
|
.try_with(|current| {
|
||||||
@ -223,9 +225,9 @@ impl<'a, N> Context<'a, N> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Executes a closure with the reference to the current span.
|
/// 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
|
where
|
||||||
F: FnOnce((&Id, Span<'_>)) -> R,
|
N: FnOnce((&Id, Span<'_>)) -> R,
|
||||||
{
|
{
|
||||||
// If the lock is poisoned or the thread local has already been
|
// If the lock is poisoned or the thread local has already been
|
||||||
// destroyed, we might be in the middle of unwinding, so this
|
// destroyed, we might be in the middle of unwinding, so this
|
||||||
@ -244,21 +246,21 @@ impl<'a, N> Context<'a, N> {
|
|||||||
.ok()?
|
.ok()?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new(store: &'a Store, new_visitor: &'a N) -> Self {
|
pub(crate) fn new(store: &'a Store, fmt_fields: &'a F) -> Self {
|
||||||
Self { store, new_visitor }
|
Self { store, fmt_fields }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a new visitor that formats span fields to the provided writer.
|
impl<'ctx, 'writer, F> FormatFields<'writer> for Context<'ctx, F>
|
||||||
/// The visitor configuration is provided by the subscriber.
|
where
|
||||||
pub fn new_visitor<'writer>(
|
F: FormatFields<'writer>,
|
||||||
&self,
|
{
|
||||||
writer: &'writer mut dyn fmt::Write,
|
#[inline]
|
||||||
is_empty: bool,
|
fn format_fields<R>(&self, writer: &'writer mut dyn fmt::Write, fields: R) -> fmt::Result
|
||||||
) -> N::Visitor
|
|
||||||
where
|
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
|
/// recently emptied span will be reused. Otherwise, a new allocation will
|
||||||
/// be added to the slab.
|
/// be added to the slab.
|
||||||
#[inline]
|
#[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
|
where
|
||||||
N: for<'a> super::NewVisitor<'a>,
|
F: for<'writer> FormatFields<'writer>,
|
||||||
{
|
{
|
||||||
let mut span = Some(Data::new(attrs, self));
|
let mut span = Some(Data::new(attrs, self));
|
||||||
|
|
||||||
@ -343,7 +345,7 @@ impl Store {
|
|||||||
// Is our snapshot still valid?
|
// Is our snapshot still valid?
|
||||||
if self.next.compare_and_swap(head, next, Ordering::Release) == head {
|
if self.next.compare_and_swap(head, next, Ordering::Release) == head {
|
||||||
// We can finally fill the slot!
|
// 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);
|
return idx_to_id(head);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -360,7 +362,7 @@ impl Store {
|
|||||||
let len = this.slab.len();
|
let len = this.slab.len();
|
||||||
|
|
||||||
// Insert the span into a new slot.
|
// 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));
|
this.slab.push(RwLock::new(slot));
|
||||||
// TODO: can we grow the slab in chunks to avoid having to
|
// TODO: can we grow the slab in chunks to avoid having to
|
||||||
// realloc as often?
|
// realloc as often?
|
||||||
@ -388,14 +390,14 @@ impl Store {
|
|||||||
|
|
||||||
/// Records that the span with the given `id` has the given `fields`.
|
/// Records that the span with the given `id` has the given `fields`.
|
||||||
#[inline]
|
#[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
|
where
|
||||||
N: for<'a> super::NewVisitor<'a>,
|
F: for<'writer> FormatFields<'writer>,
|
||||||
{
|
{
|
||||||
let slab = try_lock!(self.inner.read(), else return);
|
let slab = try_lock!(self.inner.read(), else return);
|
||||||
let slot = slab.write_slot(id_to_idx(id));
|
let slot = slab.write_slot(id_to_idx(id));
|
||||||
if let Some(mut slot) = slot {
|
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 {
|
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
|
where
|
||||||
N: for<'a> super::NewVisitor<'a>,
|
F: for<'writer> FormatFields<'writer>,
|
||||||
{
|
{
|
||||||
let mut fields = String::new();
|
let mut fields = String::new();
|
||||||
{
|
fmt_fields
|
||||||
let mut recorder = new_visitor.make(&mut fields, true);
|
.format_fields(&mut fields, attrs)
|
||||||
attrs.record(&mut recorder);
|
.expect("formatting to string should not fail");
|
||||||
}
|
|
||||||
if fields.is_empty() {
|
if fields.is_empty() {
|
||||||
data.is_empty = false;
|
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
|
where
|
||||||
N: for<'a> super::NewVisitor<'a>,
|
F: for<'writer> FormatFields<'writer>,
|
||||||
{
|
{
|
||||||
let fields = &mut self.fields;
|
let fields = &mut self.fields;
|
||||||
{
|
fmt_fields
|
||||||
let mut recorder = new_visitor.make(fields, true);
|
.format_fields(fields, attrs)
|
||||||
attrs.record(&mut recorder);
|
.expect("formatting to string should not fail");
|
||||||
}
|
|
||||||
if fields.is_empty() {
|
if fields.is_empty() {
|
||||||
data.is_empty = false;
|
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
|
where
|
||||||
N: for<'a> super::NewVisitor<'a>,
|
F: for<'writer> FormatFields<'writer>,
|
||||||
{
|
{
|
||||||
let state = &mut self.span;
|
let state = &mut self.span;
|
||||||
let buf = &mut self.fields;
|
let buf = &mut self.fields;
|
||||||
match state {
|
match state {
|
||||||
State::Empty(_) => return,
|
State::Empty(_) => return,
|
||||||
State::Full(ref mut data) => {
|
State::Full(ref mut data) => {
|
||||||
{
|
fmt_fields
|
||||||
let mut recorder = new_visitor.make(buf, data.is_empty);
|
.format_fields(buf, fields)
|
||||||
fields.record(&mut recorder);
|
.expect("formatting to string should not fail");
|
||||||
}
|
|
||||||
if buf.is_empty() {
|
if buf.is_empty() {
|
||||||
data.is_empty = false;
|
data.is_empty = false;
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,7 @@ macro_rules! try_lock {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod field;
|
||||||
pub mod filter;
|
pub mod filter;
|
||||||
#[cfg(feature = "fmt")]
|
#[cfg(feature = "fmt")]
|
||||||
pub mod fmt;
|
pub mod fmt;
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
//! This brings into scope a number of extension traits that define methods on
|
//! This brings into scope a number of extension traits that define methods on
|
||||||
//! types defined here and in other crates.
|
//! 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::{
|
pub use crate::layer::{
|
||||||
Layer as __tracing_subscriber_Layer, SubscriberExt as __tracing_subscriber_SubscriberExt,
|
Layer as __tracing_subscriber_Layer, SubscriberExt as __tracing_subscriber_SubscriberExt,
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user