Skip to content

Commit 0852ab3

Browse files
committed
subscriber: add Layer impl for Vec<L: Layer> (#2027)
Depends on #2028 ## Motivation In many cases, it is desirable to have a variable number of `Layer`s at runtime (such as when reading a logging configuration from a config file). The current approach, where types implementing `Layer` are composed at the type level into `Layered` layers, doesn't work well when the number of layers varies at runtime. ## Solution To solve this, this branch adds a `Layer` implementation for `Vec<L> where L: Layer`. This allows variable-length lists of layers to be added to a subscriber. Although the impl for `Vec<L>` requires all the layers to be the same type, it can also be used in onjunction with `Box<dyn Layer<S> + ...>` trait objects to implement a variable-length list of layers of multiple types. I also wrote a bunch of docs examples. ## Notes Alternatively, we could have a separate type defined in `tracing-subscriber` for a variable-length list of type-erased `Layer`s. This would have one primary usability benefit, which is that we could have a `push` operation that takes an `impl Layer` and boxes it automatically. However, I thought the approach used here is nicer, since it's based on composing together existing primitives such as `Vec` and `Box`, rather than adding a whole new API. Additionally, it allows avoiding the additional `Box`ing in the case where the list consists of layers that are all the same type. Closes #1708 Signed-off-by: Eliza Weisman <[email protected]>
1 parent 30204cb commit 0852ab3

File tree

4 files changed

+451
-3
lines changed

4 files changed

+451
-3
lines changed

tracing-subscriber/src/layer/mod.rs

Lines changed: 219 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,11 +215,11 @@
215215
//! ```
216216
//!
217217
//! If a [`Layer`] may be one of several different types, note that [`Box<dyn
218-
//! Layer<C> + Send + Sync>` implements `Layer`][box-impl].
218+
//! Layer<S> + Send + Sync>` implements `Layer`][box-impl].
219219
//! This may be used to erase the type of a [`Layer`].
220220
//!
221221
//! 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>`:
222+
//! several outputs might return a `Box<dyn Layer<S> + Send + Sync + 'static>`:
223223
//! ```
224224
//! use tracing_subscriber::{
225225
//! Layer,
@@ -267,6 +267,107 @@
267267
//! The [`Layer::boxed`] method is provided to make boxing a `Layer`
268268
//! more convenient, but [`Box::new`] may be used as well.
269269
//!
270+
//! When the number of `Layer`s varies at runtime, note that a
271+
//! [`Vec<L> where L: `Layer`` also implements `Layer`][vec-impl]. This
272+
//! can be used to add a variable number of `Layer`s to a `Subscriber`:
273+
//!
274+
//! ```
275+
//! use tracing_subscriber::{Layer, prelude::*};
276+
//! struct MyLayer {
277+
//! // ...
278+
//! }
279+
//! # impl MyLayer { fn new() -> Self { Self {} }}
280+
//!
281+
//! impl<S: tracing_core::Subscriber> Layer<S> for MyLayer {
282+
//! // ...
283+
//! }
284+
//!
285+
//! /// Returns how many layers we need
286+
//! fn how_many_layers() -> usize {
287+
//! // ...
288+
//! # 3
289+
//! }
290+
//!
291+
//! // Create a variable-length `Vec` of layers
292+
//! let mut layers = Vec::new();
293+
//! for _ in 0..how_many_layers() {
294+
//! layers.push(MyLayer::new());
295+
//! }
296+
//!
297+
//! tracing_subscriber::registry()
298+
//! .with(layers)
299+
//! .init();
300+
//! ```
301+
//!
302+
//! If a variable number of `Layer` is needed and those `Layer`s have
303+
//! different types, a `Vec` of [boxed `Layer` trait objects][box-impl] may
304+
//! be used. For example:
305+
//!
306+
//! ```
307+
//! use tracing_subscriber::{filter::LevelFilter, Layer, prelude::*};
308+
//! use std::fs::File;
309+
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
310+
//! struct Config {
311+
//! enable_log_file: bool,
312+
//! enable_stdout: bool,
313+
//! enable_stderr: bool,
314+
//! // ...
315+
//! }
316+
//! # impl Config {
317+
//! # fn from_config_file()-> Result<Self, Box<dyn std::error::Error>> {
318+
//! # // don't enable the log file so that the example doesn't actually create it
319+
//! # Ok(Self { enable_log_file: false, enable_stdout: true, enable_stderr: true })
320+
//! # }
321+
//! # }
322+
//!
323+
//! let cfg = Config::from_config_file()?;
324+
//!
325+
//! // Based on our dynamically loaded config file, create any number of layers:
326+
//! let mut layers = Vec::new();
327+
//!
328+
//! if cfg.enable_log_file {
329+
//! let file = File::create("myapp.log")?;
330+
//! let layer = tracing_subscriber::fmt::layer()
331+
//! .with_thread_names(true)
332+
//! .with_target(true)
333+
//! .json()
334+
//! .with_writer(file)
335+
//! // Box the layer as a type-erased trait object, so that it can
336+
//! // be pushed to the `Vec`.
337+
//! .boxed();
338+
//! layers.push(layer);
339+
//! }
340+
//!
341+
//! if cfg.enable_stdout {
342+
//! let layer = tracing_subscriber::fmt::layer()
343+
//! .pretty()
344+
//! .with_filter(LevelFilter::INFO)
345+
//! // Box the layer as a type-erased trait object, so that it can
346+
//! // be pushed to the `Vec`.
347+
//! .boxed();
348+
//! layers.push(layer);
349+
//! }
350+
//!
351+
//! if cfg.enable_stdout {
352+
//! let layer = tracing_subscriber::fmt::layer()
353+
//! .with_target(false)
354+
//! .with_filter(LevelFilter::WARN)
355+
//! // Box the layer as a type-erased trait object, so that it can
356+
//! // be pushed to the `Vec`.
357+
//! .boxed();
358+
//! layers.push(layer);
359+
//! }
360+
//!
361+
//! tracing_subscriber::registry()
362+
//! .with(layers)
363+
//! .init();
364+
//!# Ok(()) }
365+
//! ```
366+
//!
367+
//! Finally, if the number of layers _changes_ at runtime, a `Vec` of
368+
//! subscribers can be used alongside the [`reload`](crate::reload) module to
369+
//! add or remove subscribers dynamically at runtime.
370+
//!
270371
//! [option-impl]: Layer#impl-Layer<S>-for-Option<L>
271372
//! [box-impl]: Layer#impl-Layer%3CS%3E-for-Box%3Cdyn%20Layer%3CS%3E%20+%20Send%20+%20Sync%3E
272373
//! [prelude]: crate::prelude
@@ -1060,6 +1161,8 @@ where
10601161

10611162
feature! {
10621163
#![all(feature = "registry", feature = "std")]
1164+
use alloc::vec::Vec;
1165+
10631166
/// A per-[`Layer`] filter that determines whether a span or event is enabled
10641167
/// for an individual layer.
10651168
///
@@ -1491,6 +1594,120 @@ feature! {
14911594
{
14921595
layer_impl_body! {}
14931596
}
1597+
1598+
1599+
1600+
impl<S, L> Layer<S> for Vec<L>
1601+
where
1602+
L: Layer<S>,
1603+
S: Subscriber,
1604+
{
1605+
1606+
fn on_layer(&mut self, subscriber: &mut S) {
1607+
for l in self {
1608+
l.on_layer(subscriber);
1609+
}
1610+
}
1611+
1612+
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
1613+
// Return highest level of interest.
1614+
let mut interest = Interest::never();
1615+
for l in self {
1616+
let new_interest = l.register_callsite(metadata);
1617+
if (interest.is_sometimes() && new_interest.is_always())
1618+
|| (interest.is_never() && !new_interest.is_never())
1619+
{
1620+
interest = new_interest;
1621+
}
1622+
}
1623+
1624+
interest
1625+
}
1626+
1627+
fn enabled(&self, metadata: &Metadata<'_>, ctx: Context<'_, S>) -> bool {
1628+
self.iter().all(|l| l.enabled(metadata, ctx.clone()))
1629+
}
1630+
1631+
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
1632+
for l in self {
1633+
l.on_new_span(attrs, id, ctx.clone());
1634+
}
1635+
}
1636+
1637+
fn max_level_hint(&self) -> Option<LevelFilter> {
1638+
let mut max_level = LevelFilter::ERROR;
1639+
for l in self {
1640+
// NOTE(eliza): this is slightly subtle: if *any* layer
1641+
// returns `None`, we have to return `None`, assuming there is
1642+
// no max level hint, since that particular layer cannot
1643+
// provide a hint.
1644+
let hint = l.max_level_hint()?;
1645+
max_level = core::cmp::max(hint, max_level);
1646+
}
1647+
Some(max_level)
1648+
}
1649+
1650+
fn on_record(&self, span: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
1651+
for l in self {
1652+
l.on_record(span, values, ctx.clone())
1653+
}
1654+
}
1655+
1656+
fn on_follows_from(&self, span: &span::Id, follows: &span::Id, ctx: Context<'_, S>) {
1657+
for l in self {
1658+
l.on_follows_from(span, follows, ctx.clone());
1659+
}
1660+
}
1661+
1662+
fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
1663+
for l in self {
1664+
l.on_event(event, ctx.clone());
1665+
}
1666+
}
1667+
1668+
fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
1669+
for l in self {
1670+
l.on_enter(id, ctx.clone());
1671+
}
1672+
}
1673+
1674+
fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
1675+
for l in self {
1676+
l.on_exit(id, ctx.clone());
1677+
}
1678+
}
1679+
1680+
fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
1681+
for l in self {
1682+
l.on_close(id.clone(), ctx.clone());
1683+
}
1684+
}
1685+
1686+
#[doc(hidden)]
1687+
unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> {
1688+
// If downcasting to `Self`, return a pointer to `self`.
1689+
if id == TypeId::of::<Self>() {
1690+
return Some(self as *const _ as *const ());
1691+
}
1692+
1693+
// Someone is looking for per-layer filters. But, this `Vec`
1694+
// might contain layers with per-layer filters *and*
1695+
// layers without filters. It should only be treated as a
1696+
// per-layer-filtered layer if *all* its layers have
1697+
// per-layer filters.
1698+
// XXX(eliza): it's a bummer we have to do this linear search every
1699+
// time. It would be nice if this could be cached, but that would
1700+
// require replacing the `Vec` impl with an impl for a newtype...
1701+
if filter::is_plf_downcast_marker(id) && self.iter().any(|s| s.downcast_raw(id).is_none()) {
1702+
return None;
1703+
}
1704+
1705+
// Otherwise, return the first child of `self` that downcaasts to
1706+
// the selected type, if any.
1707+
// XXX(eliza): hope this is reasonable lol
1708+
self.iter().find_map(|l| l.downcast_raw(id))
1709+
}
1710+
}
14941711
}
14951712

14961713
// === impl SubscriberExt ===

tracing-subscriber/tests/layer_filters/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ mod downcast_raw;
77
mod filter_scopes;
88
mod targets;
99
mod trees;
10+
mod vec;
1011

1112
use tracing::{level_filters::LevelFilter, Level};
12-
use tracing_subscriber::{filter, prelude::*};
13+
use tracing_subscriber::{filter, prelude::*, Layer};
1314

1415
#[test]
1516
fn basic_layer_filters() {
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
use super::*;
2+
use tracing::Subscriber;
3+
4+
#[test]
5+
fn with_filters_unboxed() {
6+
let (trace_layer, trace_handle) = layer::named("trace")
7+
.event(event::mock().at_level(Level::TRACE))
8+
.event(event::mock().at_level(Level::DEBUG))
9+
.event(event::mock().at_level(Level::INFO))
10+
.done()
11+
.run_with_handle();
12+
let trace_layer = trace_layer.with_filter(LevelFilter::TRACE);
13+
14+
let (debug_layer, debug_handle) = layer::named("debug")
15+
.event(event::mock().at_level(Level::DEBUG))
16+
.event(event::mock().at_level(Level::INFO))
17+
.done()
18+
.run_with_handle();
19+
let debug_layer = debug_layer.with_filter(LevelFilter::DEBUG);
20+
21+
let (info_layer, info_handle) = layer::named("info")
22+
.event(event::mock().at_level(Level::INFO))
23+
.done()
24+
.run_with_handle();
25+
let info_layer = info_layer.with_filter(LevelFilter::INFO);
26+
27+
let _subscriber = tracing_subscriber::registry()
28+
.with(vec![trace_layer, debug_layer, info_layer])
29+
.set_default();
30+
31+
tracing::trace!("hello trace");
32+
tracing::debug!("hello debug");
33+
tracing::info!("hello info");
34+
35+
trace_handle.assert_finished();
36+
debug_handle.assert_finished();
37+
info_handle.assert_finished();
38+
}
39+
40+
#[test]
41+
fn with_filters_boxed() {
42+
let (unfiltered_layer, unfiltered_handle) = layer::named("unfiltered")
43+
.event(event::mock().at_level(Level::TRACE))
44+
.event(event::mock().at_level(Level::DEBUG))
45+
.event(event::mock().at_level(Level::INFO))
46+
.done()
47+
.run_with_handle();
48+
let unfiltered_layer = unfiltered_layer.boxed();
49+
50+
let (debug_layer, debug_handle) = layer::named("debug")
51+
.event(event::mock().at_level(Level::DEBUG))
52+
.event(event::mock().at_level(Level::INFO))
53+
.done()
54+
.run_with_handle();
55+
let debug_layer = debug_layer.with_filter(LevelFilter::DEBUG).boxed();
56+
57+
let (target_layer, target_handle) = layer::named("target")
58+
.event(event::mock().at_level(Level::INFO))
59+
.done()
60+
.run_with_handle();
61+
let target_layer = target_layer
62+
.with_filter(filter::filter_fn(|meta| meta.target() == "my_target"))
63+
.boxed();
64+
65+
let _subscriber = tracing_subscriber::registry()
66+
.with(vec![unfiltered_layer, debug_layer, target_layer])
67+
.set_default();
68+
69+
tracing::trace!("hello trace");
70+
tracing::debug!("hello debug");
71+
tracing::info!(target: "my_target", "hello my target");
72+
73+
unfiltered_handle.assert_finished();
74+
debug_handle.assert_finished();
75+
target_handle.assert_finished();
76+
}
77+
78+
#[test]
79+
fn mixed_max_level_hint() {
80+
let unfiltered = layer::named("unfiltered").run().boxed();
81+
let info = layer::named("info")
82+
.run()
83+
.with_filter(LevelFilter::INFO)
84+
.boxed();
85+
let debug = layer::named("debug")
86+
.run()
87+
.with_filter(LevelFilter::DEBUG)
88+
.boxed();
89+
90+
let subscriber = tracing_subscriber::registry().with(vec![unfiltered, info, debug]);
91+
92+
assert_eq!(subscriber.max_level_hint(), None);
93+
}
94+
95+
#[test]
96+
fn all_filtered_max_level_hint() {
97+
let warn = layer::named("warn")
98+
.run()
99+
.with_filter(LevelFilter::WARN)
100+
.boxed();
101+
let info = layer::named("info")
102+
.run()
103+
.with_filter(LevelFilter::INFO)
104+
.boxed();
105+
let debug = layer::named("debug")
106+
.run()
107+
.with_filter(LevelFilter::DEBUG)
108+
.boxed();
109+
110+
let subscriber = tracing_subscriber::registry().with(vec![warn, info, debug]);
111+
112+
assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::DEBUG));
113+
}

0 commit comments

Comments
 (0)