Skip to content

Commit

Permalink
subscriber: add Layer::boxed method (tokio-rs#2026)
Browse files Browse the repository at this point in the history
## Motivation

PR tokio-rs#2023 made the `impl Layer` for boxed `dyn Layer` trait
objects actually be useful. One minor annoyance when working with boxed
trait objects is that, in some cases, in order to use `Box::new` to
erase a `Layer`'s type, it's necessary to add a type annotation like
this:
```rust
let boxed_layer: Box<dyn Layer<_> + Send + Sync + 'static>
    = Box::new(some_layer);
```

Because the trait object includes `Send + Sync + 'static`, this is a bit
wordy to type out.

## Solution

This commit adds a `Layer::boxed` method that takes `Self` and boxes
it as a type-erased `dyn Layer` trait object. This can then be
chained after a a chain of layer builder methods. Because the
`boxed` method _explicitly_ returns a `dyn Layer<_> + ...` trait
object, it's not necessary to use a wordy type annotation when
using this method.
  • Loading branch information
hawkw authored and kaffarell committed May 22, 2024
1 parent 8cbe3c5 commit beb4e56
Showing 1 changed file with 128 additions and 1 deletion.
129 changes: 128 additions & 1 deletion tracing-subscriber/src/layer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,9 @@
//! .init();
//! ```
//!
//! [prelude]: crate::prelude
//! The [`Layer::boxed`] method is provided to make boxing a `Layer`
//! more convenient, but [`Box::new`] may be used as well.
//!
//! [option-impl]: Layer#impl-Layer<S>-for-Option<L>
//! [box-impl]: Layer#impl-Layer%3CS%3E-for-Box%3Cdyn%20Layer%3CS%3E%20+%20Send%20+%20Sync%3E
//! [prelude]: crate::prelude
Expand Down Expand Up @@ -921,6 +923,131 @@ where
filter::Filtered::new(self, filter)
}

/// Erases the type of this [`Layer`], returning a [`Box`]ed `dyn
/// Layer` trait object.
///
/// This can be used when a function returns a `Layer` which may be of
/// one of several types, or when a `Layer` subscriber has a very long type
/// signature.
///
/// # Examples
///
/// The following example will *not* compile, because the value assigned to
/// `log_layer` may have one of several different types:
///
/// ```compile_fail
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use tracing_subscriber::{Layer, filter::LevelFilter, prelude::*};
/// use std::{path::PathBuf, fs::File, io};
///
/// /// Configures whether logs are emitted to a file, to stdout, or to stderr.
/// pub enum LogConfig {
/// File(PathBuf),
/// Stdout,
/// Stderr,
/// }
///
/// let config = // ...
/// # LogConfig::Stdout;
///
/// // Depending on the config, construct a layer of one of several types.
/// let log_layer = match config {
/// // If logging to a file, use a maximally-verbose configuration.
/// LogConfig::File(path) => {
/// let file = File::create(path)?;
/// tracing_subscriber::fmt::layer()
/// .with_thread_ids(true)
/// .with_thread_names(true)
/// // Selecting the JSON logging format changes the layer's
/// // type.
/// .json()
/// .with_span_list(true)
/// // Setting the writer to use our log file changes the
/// // layer's type again.
/// .with_writer(file)
/// },
///
/// // If logging to stdout, use a pretty, human-readable configuration.
/// LogConfig::Stdout => tracing_subscriber::fmt::layer()
/// // Selecting the "pretty" logging format changes the
/// // layer's type!
/// .pretty()
/// .with_writer(io::stdout)
/// // Add a filter based on the RUST_LOG environment variable;
/// // this changes the type too!
/// .and_then(tracing_subscriber::EnvFilter::from_default_env()),
///
/// // If logging to stdout, only log errors and warnings.
/// LogConfig::Stderr => tracing_subscriber::fmt::layer()
/// // Changing the writer changes the layer's type
/// .with_writer(io::stderr)
/// // Only log the `WARN` and `ERROR` levels. Adding a filter
/// // changes the layer's type to `Filtered<LevelFilter, ...>`.
/// .with_filter(LevelFilter::WARN),
/// };
///
/// tracing_subscriber::registry()
/// .with(log_layer)
/// .init();
/// # Ok(()) }
/// ```
///
/// However, adding a call to `.boxed()` after each match arm erases the
/// layer's type, so this code *does* compile:
///
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # use tracing_subscriber::{Layer, filter::LevelFilter, prelude::*};
/// # use std::{path::PathBuf, fs::File, io};
/// # pub enum LogConfig {
/// # File(PathBuf),
/// # Stdout,
/// # Stderr,
/// # }
/// # let config = LogConfig::Stdout;
/// let log_layer = match config {
/// LogConfig::File(path) => {
/// let file = File::create(path)?;
/// tracing_subscriber::fmt::layer()
/// .with_thread_ids(true)
/// .with_thread_names(true)
/// .json()
/// .with_span_list(true)
/// .with_writer(file)
/// // Erase the type by boxing the layer
/// .boxed()
/// },
///
/// LogConfig::Stdout => tracing_subscriber::fmt::layer()
/// .pretty()
/// .with_writer(io::stdout)
/// .and_then(tracing_subscriber::EnvFilter::from_default_env())
/// // Erase the type by boxing the layer
/// .boxed(),
///
/// LogConfig::Stderr => tracing_subscriber::fmt::layer()
/// .with_writer(io::stderr)
/// .with_filter(LevelFilter::WARN)
/// // Erase the type by boxing the layer
/// .boxed(),
/// };
///
/// tracing_subscriber::registry()
/// .with(log_layer)
/// .init();
/// # Ok(()) }
/// ```
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
fn boxed(self) -> Box<dyn Layer<S> + Send + Sync + 'static>
where
Self: Sized,
Self: Layer<S> + Send + Sync + 'static,
S: Subscriber,
{
Box::new(self)
}

#[doc(hidden)]
unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> {
if id == TypeId::of::<Self>() {
Expand Down

0 comments on commit beb4e56

Please sign in to comment.