Skip to content

Commit 7043420

Browse files
committed
subscriber: document boxed Layers (#2023)
Currently, there is an `impl<S: Subscriber> Layer for Box<dyn Layer<S> + Send + Sync>` intended to allow type-erasing a subscriber. This change improves the documentation on runtime configuration of layers to include an example of using `Box` to type-erase a layer. Signed-off-by: Eliza Weisman <eliza@buoyant.io>
1 parent c539f34 commit 7043420

File tree

1 file changed

+136
-83
lines changed
  • tracing-subscriber/src/layer

1 file changed

+136
-83
lines changed

tracing-subscriber/src/layer/mod.rs

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

0 commit comments

Comments
 (0)