Skip to content

Commit

Permalink
subscriber: Add SubscriberBuilder/Layer accessors (#1871)
Browse files Browse the repository at this point in the history
`SubscriberBuilder`s and `Layer`s configured with custom event/field
formatters do not provide any means of accessing or mutating those
formatters. Any configuration that needs to be done must be done before
setting them on the builder/layer. This is frustrating as it makes it
difficult to provide a pre-configured API akin to
`tracing_subscriber::fmt()` along with accessors like `.compact()` that
modify the formatter.

Add accessors `.map_event_format()` and `.map_fmt_fields()` to
`SubscriberBuilder` and `Layer` that map the existing formatter through
a closure. This allows the closure to modify it or to derive a new
formatter from it with a different type.

Also add a `.map_writer()` method that does the same thing for the
`MakeWriter`, to round out the accessors for the various type
parameters.

The filter type is currently restricted to just `LevelFilter` or
`EnvFilter` and so this does not add a corresponding `.map_filter()`.
That can be added later if we add the ability to attach arbitrary
filters.

Also fix some minor docs issues that were spotted as part of
implementing this.

Fixes #1756
  • Loading branch information
lilyball authored and hawkw committed Aug 24, 2022
1 parent 41337ba commit 7a32d3b
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 6 deletions.
92 changes: 92 additions & 0 deletions tracing-subscriber/src/fmt/fmt_subscriber.rs
Expand Up @@ -120,6 +120,35 @@ where
_inner: self._inner,
}
}

/// Updates the event formatter by applying a function to the existing event formatter.
///
/// This sets the event formatter that the subscriber being built will use to record fields.
///
/// # Examples
///
/// Updating an event formatter:
///
/// ```rust
/// let subscriber = tracing_subscriber::fmt::subscriber()
/// .map_event_format(|e| e.compact());
/// # // this is necessary for type inference.
/// # use tracing_subscriber::Subscribe as _;
/// # let _ = subscriber.with_collector(tracing_subscriber::registry::Registry::default());
/// ```
pub fn map_event_format<E2>(self, f: impl FnOnce(E) -> E2) -> Subscriber<C, N, E2, W>
where
E2: FormatEvent<C, N> + 'static,
{
Subscriber {
fmt_fields: self.fmt_fields,
fmt_event: f(self.fmt_event),
fmt_span: self.fmt_span,
make_writer: self.make_writer,
is_ansi: self.is_ansi,
_inner: self._inner,
}
}
}

// This needs to be a separate impl block because they place different bounds on the type parameters.
Expand Down Expand Up @@ -249,6 +278,39 @@ impl<C, N, E, W> Subscriber<C, N, E, W> {
..self
}
}

/// Updates the [`MakeWriter`] by applying a function to the existing [`MakeWriter`].
///
/// This sets the [`MakeWriter`] that the subscriber being built will use to write events.
///
/// # Examples
///
/// Redirect output to stderr if level is <= WARN:
///
/// ```rust
/// use tracing::Level;
/// use tracing_subscriber::fmt::{self, writer::MakeWriterExt};
///
/// let stderr = std::io::stderr.with_max_level(Level::WARN);
/// let subscriber = fmt::subscriber()
/// .map_writer(move |w| stderr.or_else(w));
/// # // this is necessary for type inference.
/// # use tracing_subscriber::Subscribe as _;
/// # let _ = subscriber.with_collector(tracing_subscriber::registry::Registry::default());
/// ```
pub fn map_writer<W2>(self, f: impl FnOnce(W) -> W2) -> Subscriber<C, N, E, W2>
where
W2: for<'writer> MakeWriter<'writer> + 'static,
{
Subscriber {
fmt_fields: self.fmt_fields,
fmt_event: self.fmt_event,
fmt_span: self.fmt_span,
is_ansi: self.is_ansi,
make_writer: f(self.make_writer),
_inner: self._inner,
}
}
}

impl<C, N, L, T, W> Subscriber<C, N, format::Format<L, T>, W>
Expand Down Expand Up @@ -531,6 +593,36 @@ impl<C, N, E, W> Subscriber<C, N, E, W> {
_inner: self._inner,
}
}

/// Updates the field formatter by applying a function to the existing field formatter.
///
/// This sets the field formatter that the subscriber being built will use to record fields.
///
/// # Examples
///
/// Updating a field formatter:
///
/// ```rust
/// use tracing_subscriber::field::MakeExt;
/// let subscriber = tracing_subscriber::fmt::subscriber()
/// .map_fmt_fields(|f| f.debug_alt());
/// # // this is necessary for type inference.
/// # use tracing_subscriber::Subscribe as _;
/// # let _ = subscriber.with_collector(tracing_subscriber::registry::Registry::default());
/// ```
pub fn map_fmt_fields<N2>(self, f: impl FnOnce(N) -> N2) -> Subscriber<C, N2, E, W>
where
N2: for<'writer> FormatFields<'writer> + 'static,
{
Subscriber {
fmt_event: self.fmt_event,
fmt_fields: f(self.fmt_fields),
fmt_span: self.fmt_span,
make_writer: self.make_writer,
is_ansi: self.is_ansi,
_inner: self._inner,
}
}
}

impl<C> Default for Subscriber<C> {
Expand Down
107 changes: 101 additions & 6 deletions tracing-subscriber/src/fmt/mod.rs
Expand Up @@ -892,8 +892,9 @@ impl<N, E, F, W> CollectorBuilder<N, E, F, W> {
/// Sets the maximum [verbosity level] that will be enabled by the
/// collector.
///
/// If the max level has already been set, this replaces that configuration
/// with the new maximum level.
/// If the max level has already been set, or a [`EnvFilter`] was added by
/// [`with_env_filter`], this replaces that configuration with the new
/// maximum level.
///
/// # Examples
///
Expand All @@ -915,7 +916,8 @@ impl<N, E, F, W> CollectorBuilder<N, E, F, W> {
/// .finish();
/// ```
/// [verbosity level]: tracing_core::Level
/// [`EnvFilter`]: super::filter::EnvFilter
/// [`EnvFilter`]: struct@crate::filter::EnvFilter
/// [`with_env_filter`]: fn@Self::with_env_filter
pub fn with_max_level(
self,
filter: impl Into<LevelFilter>,
Expand Down Expand Up @@ -972,8 +974,26 @@ impl<N, E, F, W> CollectorBuilder<N, E, F, W> {
}
}

/// Sets the function that the collector being built should use to format
/// events that occur.
/// Sets the [event formatter][`FormatEvent`] that the subscriber being built
/// will use to format events that occur.
///
/// The event formatter may be any type implementing the [`FormatEvent`]
/// trait, which is implemented for all functions taking a [`FmtContext`], a
/// [`Writer`], and an [`Event`].
///
/// # Examples
///
/// Setting a type implementing [`FormatEvent`] as the formatter:
///
/// ```rust
/// use tracing_subscriber::fmt::format;
///
/// let subscriber = tracing_subscriber::fmt()
/// .event_format(format().compact())
/// .finish();
/// ```
///
/// [`Writer`]: struct@self::format::Writer
pub fn event_format<E2>(self, fmt_event: E2) -> CollectorBuilder<N, E2, F, W>
where
E2: FormatEvent<Registry, N> + 'static,
Expand All @@ -1000,7 +1020,6 @@ impl<N, E, F, W> CollectorBuilder<N, E, F, W> {
/// .with_writer(io::stderr)
/// .init();
/// ```
///
pub fn with_writer<W2>(self, make_writer: W2) -> CollectorBuilder<N, E, F, W2>
where
W2: for<'writer> MakeWriter<'writer> + 'static,
Expand Down Expand Up @@ -1041,6 +1060,82 @@ impl<N, E, F, W> CollectorBuilder<N, E, F, W> {
inner: self.inner.with_writer(TestWriter::default()),
}
}

/// Updates the event formatter by applying a function to the existing event formatter.
///
/// This sets the event formatter that the collector being built will use to record fields.
///
/// # Examples
///
/// Updating an event formatter:
///
/// ```rust
/// let subscriber = tracing_subscriber::fmt()
/// .map_event_format(|e| e.compact())
/// .finish();
/// ```
pub fn map_event_format<E2>(self, f: impl FnOnce(E) -> E2) -> CollectorBuilder<N, E2, F, W>
where
E2: FormatEvent<Registry, N> + 'static,
N: for<'writer> FormatFields<'writer> + 'static,
W: for<'writer> MakeWriter<'writer> + 'static,
{
CollectorBuilder {
filter: self.filter,
inner: self.inner.map_event_format(f),
}
}

/// Updates the field formatter by applying a function to the existing field formatter.
///
/// This sets the field formatter that the subscriber being built will use to record fields.
///
/// # Examples
///
/// Updating a field formatter:
///
/// ```rust
/// use tracing_subscriber::field::MakeExt;
/// let subscriber = tracing_subscriber::fmt()
/// .map_fmt_fields(|f| f.debug_alt())
/// .finish();
/// ```
pub fn map_fmt_fields<N2>(self, f: impl FnOnce(N) -> N2) -> CollectorBuilder<N2, E, F, W>
where
N2: for<'writer> FormatFields<'writer> + 'static,
{
CollectorBuilder {
filter: self.filter,
inner: self.inner.map_fmt_fields(f),
}
}

/// Updates the [`MakeWriter`] by applying a function to the existing [`MakeWriter`].
///
/// This sets the [`MakeWriter`] that the subscriber being built will use to write events.
///
/// # Examples
///
/// Redirect output to stderr if level is <= WARN:
///
/// ```rust
/// use tracing::Level;
/// use tracing_subscriber::fmt::{self, writer::MakeWriterExt};
///
/// let stderr = std::io::stderr.with_max_level(Level::WARN);
/// let collector = tracing_subscriber::fmt()
/// .map_writer(move |w| stderr.or_else(w))
/// .finish();
/// ```
pub fn map_writer<W2>(self, f: impl FnOnce(W) -> W2) -> CollectorBuilder<N, E, F, W2>
where
W2: for<'writer> MakeWriter<'writer> + 'static,
{
CollectorBuilder {
filter: self.filter,
inner: self.inner.map_writer(f),
}
}
}

/// Install a global tracing collector that listens for events and
Expand Down

0 comments on commit 7a32d3b

Please sign in to comment.