Skip to content

Commit afdad8f

Browse files
authored
subscriber: make boxed subscribers not useless (#2023)
## Motivation Currently, there is an `impl<C: Collect> Subscribe for Box<dyn Subscribe<C>>` intended to allow type-erasing a subscriber. Unfortunately, however, this impl is...completely and utterly useless. In order to be set as the default, a collector must be `Send + Sync + 'static`. Because the boxed trait object does not bound the subscriber with these traits, adding a boxed subscriber to a collector will make that collector impossible to use. This means that a `Box<dyn Subscribe<C>>` has no practical purpose whatsoever. ## Solution This branch adds an `impl<C: Collect> for Box<dyn Subscribe<C> + Send + Sync + 'static>`, and removes the useless impl. In addition, I've improved the documentation on runtime configuration of subscribers to include an example of using `Box` to type-erase a subscriber. This is, incidentally, how I found out that using `Box`ed subscribers is currently useless. ## Notes * When this change is backported to v0.1.x, we'll have to un-delete the useless `impl` for `Box<dyn Layer<S>>`, as removing it is technically a breaking change. But, no one is actually _using_ that impl for any practical purpose, since they...can't. * We may want to just add a `Self: Send + Sync + 'static` bound to the `Subscribe` trait, too; this would make it much easier to avoid this kind of issue in the future. But, that's a breaking change that can't easily be backported to `v0.1.x` Signed-off-by: Eliza Weisman <eliza@buoyant.io>
1 parent 770ab84 commit afdad8f

File tree

1 file changed

+136
-85
lines changed
  • tracing-subscriber/src/subscribe

1 file changed

+136
-85
lines changed

tracing-subscriber/src/subscribe/mod.rs

Lines changed: 136 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
//! [`Collect`] behavior; it can _observe_ events and spans, but does not
2020
//! assign IDs.
2121
//!
22-
//! ## Composing Subscribers
22+
//! # Composing Subscribers
2323
//!
2424
//! Since a [subscriber] does not implement a complete strategy for collecting
2525
//! traces, it must be composed with a [collector] in order to be used. The
@@ -137,9 +137,139 @@
137137
//! [`Subscribe::with_collector`] as an implementation detail, as `with_collector`
138138
//! calls must be nested, leading to less clear code for the reader.
139139
//!
140+
//! ## Runtime Configuration With Subscribers
141+
//!
142+
//! In some cases, a particular [subscriber] may be enabled or disabled based on
143+
//! runtime configuration. This can introduce challenges, because the type of a
144+
//! layered [collector] depends on which subscribers are added to it: if an `if`
145+
//! or `match` expression adds some [`Subscribe`] implementation in one branch,
146+
//! and other subscribers in another, the [collector] values returned by those
147+
//! branches will have different types. For example, the following _will not_
148+
//! work:
149+
//!
150+
//! ```compile_fail
151+
//! # fn docs() -> Result<(), Box<dyn std::error::Error + 'static>> {
152+
//! # struct Config {
153+
//! # is_prod: bool,
154+
//! # path: &'static str,
155+
//! # }
156+
//! # let cfg = Config { is_prod: false, path: "debug.log" };
157+
//! use std::fs::File;
158+
//! use tracing_subscriber::{Registry, prelude::*};
159+
//!
160+
//! let stdout_log = tracing_subscriber::fmt::subscriber().pretty();
161+
//! let collector = Registry::default().with(stdout_log);
162+
//!
163+
//! // The compile error will occur here because the if and else
164+
//! // branches have different (and therefore incompatible) types.
165+
//! let collector = if cfg.is_prod {
166+
//! let file = File::create(cfg.path)?;
167+
//! let collector = tracing_subscriber::fmt::subscriber()
168+
//! .json()
169+
//! .with_writer(Arc::new(file));
170+
//! collector.with(subscriber)
171+
//! } else {
172+
//! collector
173+
//! };
174+
//!
175+
//! tracing::collect::set_global_default(collector)
176+
//! .expect("Unable to set global collector");
177+
//! # Ok(()) }
178+
//! ```
179+
//!
180+
//! However, a [`Subscribe`] wrapped in an [`Option`] [also implements the `Subscribe`
181+
//! trait][option-impl]. This allows individual layers to be enabled or disabled at
182+
//! runtime while always producing a [`Collect`] of the same type. For
183+
//! example:
184+
//!
185+
//! ```
186+
//! # fn docs() -> Result<(), Box<dyn std::error::Error + 'static>> {
187+
//! # struct Config {
188+
//! # is_prod: bool,
189+
//! # path: &'static str,
190+
//! # }
191+
//! # let cfg = Config { is_prod: false, path: "debug.log" };
192+
//! use std::fs::File;
193+
//! use tracing_subscriber::{Registry, prelude::*};
194+
//!
195+
//! let stdout_log = tracing_subscriber::fmt::subscriber().pretty();
196+
//! let collector = Registry::default().with(stdout_log);
197+
//!
198+
//! // if `cfg.is_prod` is true, also log JSON-formatted logs to a file.
199+
//! let json_log = if cfg.is_prod {
200+
//! let file = File::create(cfg.path)?;
201+
//! let json_log = tracing_subscriber::fmt::subscriber()
202+
//! .json()
203+
//! .with_writer(file);
204+
//! Some(json_log)
205+
//! } else {
206+
//! None
207+
//! };
208+
//!
209+
//! // If `cfg.is_prod` is false, then `json` will be `None`, and this subscriber
210+
//! // will do nothing. However, the collector will still have the same type
211+
//! // regardless of whether the `Option`'s value is `None` or `Some`.
212+
//! let collector = collector.with(json_log);
213+
//!
214+
//! tracing::collect::set_global_default(collector)
215+
//! .expect("Unable to set global collector");
216+
//! # Ok(()) }
217+
//! ```
218+
//!
219+
//! If a subscriber may be one of several different types, note that [`Box<dyn
220+
//! Subscribe<C> + Send + Sync + 'static>` implements `Subscribe`][box-impl].
221+
//! This may be used to erase the type of a subscriber.
222+
//!
223+
//! For example, a function that configures a subscriber to log to one of
224+
//! several outputs might return a `Box<dyn Subscribe<C> + Send + Sync + 'static>`:
225+
//! ```
226+
//! use tracing_subscriber::{
227+
//! Subscribe,
228+
//! registry::LookupSpan,
229+
//! prelude::*,
230+
//! };
231+
//! use std::{path::PathBuf, fs::File, io};
232+
//!
233+
//! /// Configures whether logs are emitted to a file, to stdout, or to stderr.
234+
//! pub enum LogConfig {
235+
//! File(PathBuf),
236+
//! Stdout,
237+
//! Stderr,
238+
//! }
239+
//!
240+
//! impl LogConfig {
241+
//! pub fn subscriber<C>(self) -> Box<dyn Subscribe<C> + Send + Sync + 'static>
242+
//! where
243+
//! C: tracing_core::Collect + Send + Sync,
244+
//! for<'a> C: LookupSpan<'a>,
245+
//! {
246+
//! // Shared configuration regardless of where logs are output to.
247+
//! let fmt = tracing_subscriber::fmt::subscriber()
248+
//! .with_target(true)
249+
//! .with_thread_names(true);
250+
//!
251+
//! // Configure the writer based on the desired log target:
252+
//! match self {
253+
//! LogConfig::File(path) => {
254+
//! let file = File::create(path).expect("failed to create log file");
255+
//! Box::new(fmt.with_writer(file))
256+
//! },
257+
//! LogConfig::Stdout => Box::new(fmt.with_writer(io::stdout)),
258+
//! LogConfig::Stderr => Box::new(fmt.with_writer(io::stderr)),
259+
//! }
260+
//! }
261+
//! }
262+
//!
263+
//! let config = LogConfig::Stdout;
264+
//! tracing_subscriber::registry()
265+
//! .with(config.subscriber())
266+
//! .init();
267+
//! ```
268+
//!
140269
//! [prelude]: crate::prelude
270+
//! [box-impl]: #impl-Subscribe<C>-for-Box<dyn Subscribe<C> + Send + Sync + 'static>
141271
//!
142-
//! ## Recording Traces
272+
//! # Recording Traces
143273
//!
144274
//! The [`Subscribe`] trait defines a set of methods for consuming notifications from
145275
//! tracing instrumentation, which are generally equivalent to the similarly
@@ -148,7 +278,7 @@
148278
//! information provided by the wrapped subscriber (such as [the current span])
149279
//! to the subscriber.
150280
//!
151-
//! ## Filtering with `Subscribers`s
281+
//! # Filtering with `Subscriber`s
152282
//!
153283
//! As well as strategies for handling trace events, the `Subscribe` trait may also
154284
//! be used to represent composable _filters_. This allows the determination of
@@ -160,7 +290,7 @@
160290
//! combined with _per-subscriber filters_ that control what spans and events are
161291
//! recorded by those subscribers.
162292
//!
163-
//! ### Global Filtering
293+
//! ## Global Filtering
164294
//!
165295
//! A `Subscribe` that implements a filtering strategy should override the
166296
//! [`register_callsite`] and/or [`enabled`] methods. It may also choose to implement
@@ -181,7 +311,7 @@
181311
//! [`Interest::never()`] from its [`register_callsite`] method, filter
182312
//! evaluation will short-circuit and the span or event will be disabled.
183313
//!
184-
//! ### Per-Subscriber Filtering
314+
//! ## Per-Subscriber Filtering
185315
//!
186316
//! **Note**: per-subscriber filtering APIs currently require the [`"registry"` crate
187317
//! feature flag][feat] to be enabled.
@@ -396,85 +526,6 @@
396526
//! # Ok(()) }
397527
//! ```
398528
//!
399-
//!
400-
//! ## Runtime Configuration With Subscribers
401-
//!
402-
//! In some cases, a particular [subscriber] may be enabled or disabled based on
403-
//! runtime configuration. This can introduce challenges, because the type of a
404-
//! layered [collector] depends on which subscribers are added to it: if an `if`
405-
//! or `match` expression adds some [`Subscribe`] implementation in one branch,
406-
//! and other subscribers in another, the [collector] values returned by those
407-
//! branches will have different types. For example, the following _will not_
408-
//! work:
409-
//!
410-
//! ```compile_fail
411-
//! # fn docs() -> Result<(), Box<dyn std::error::Error + 'static>> {
412-
//! # struct Config {
413-
//! # is_prod: bool,
414-
//! # path: &'static str,
415-
//! # }
416-
//! # let cfg = Config { is_prod: false, path: "debug.log" };
417-
//! use std::fs::File;
418-
//! use tracing_subscriber::{Registry, prelude::*};
419-
//!
420-
//! let stdout_log = tracing_subscriber::fmt::subscriber().pretty();
421-
//! let collector = Registry::default().with(stdout_log);
422-
//!
423-
//! // The compile error will occur here because the if and else
424-
//! // branches have different (and therefore incompatible) types.
425-
//! let collector = if cfg.is_prod {
426-
//! let file = File::create(cfg.path)?;
427-
//! let collector = tracing_subscriber::fmt::subscriber()
428-
//! .json()
429-
//! .with_writer(Arc::new(file));
430-
//! collector.with(subscriber)
431-
//! } else {
432-
//! collector
433-
//! };
434-
//!
435-
//! tracing::collect::set_global_default(collector)
436-
//! .expect("Unable to set global collector");
437-
//! # Ok(()) }
438-
//! ```
439-
//!
440-
//! However, a [`Subscribe`] wrapped in an [`Option`] [also implements the `Subscribe`
441-
//! trait][option-impl]. This allows individual layers to be enabled or disabled at
442-
//! runtime while always producing a [`Collect`] of the same type. For
443-
//! example:
444-
//!
445-
//! ```
446-
//! # fn docs() -> Result<(), Box<dyn std::error::Error + 'static>> {
447-
//! # struct Config {
448-
//! # is_prod: bool,
449-
//! # path: &'static str,
450-
//! # }
451-
//! # let cfg = Config { is_prod: false, path: "debug.log" };
452-
//! use std::fs::File;
453-
//! use tracing_subscriber::{Registry, prelude::*};
454-
//!
455-
//! let stdout_log = tracing_subscriber::fmt::subscriber().pretty();
456-
//! let collector = Registry::default().with(stdout_log);
457-
//!
458-
//! // if `cfg.is_prod` is true, also log JSON-formatted logs to a file.
459-
//! let json_log = if cfg.is_prod {
460-
//! let file = File::create(cfg.path)?;
461-
//! let json_log = tracing_subscriber::fmt::subscriber()
462-
//! .json()
463-
//! .with_writer(file);
464-
//! Some(json_log)
465-
//! } else {
466-
//! None
467-
//! };
468-
//!
469-
//! // If `cfg.is_prod` is false, then `json` will be `None`, and this subscriber
470-
//! // will do nothing. However, the collector will still have the same type
471-
//! // regardless of whether the `Option`'s value is `None` or `Some`.
472-
//! let collector = collector.with(json_log);
473-
//!
474-
//! tracing::collect::set_global_default(collector)
475-
//! .expect("Unable to set global collector");
476-
//! # Ok(()) }
477-
//! ```
478529
//! [subscriber]: Subscribe
479530
//! [`Collect`]:tracing_core::Collect
480531
//! [collector]: tracing_core::Collect
@@ -1282,7 +1333,7 @@ feature! {
12821333
subscriber_impl_body! {}
12831334
}
12841335

1285-
impl<C> Subscribe<C> for Box<dyn Subscribe<C>>
1336+
impl<C> Subscribe<C> for Box<dyn Subscribe<C> + Send + Sync + 'static>
12861337
where
12871338
C: Collect,
12881339
{

0 commit comments

Comments
 (0)