Skip to content

Commit

Permalink
subscriber: add Targets::targets method to iterate directives (toki…
Browse files Browse the repository at this point in the history
…o-rs#1574)

There's currently no way to get the registered directives from a
`Targets` instance, which is a barrier to some use-cases such as
combining user-supplied directives with application-supplied defaults.

This commit adds a `Targets::targets` method, which returns an iterator
of `(&str, LevelFilter)` pairs, one for each directive (excluding the
default level, whose `target` is `None`).

Items are yielded as per the underlying `DirectiveSet::directives`,
which would yield itms in specifity order (as constructed by
`DirectiveSet::add`). However, the order has been left explicitly
undefined in the documentation for `Targets::targets` on the basis that
this is an implementation detail that may change.

## Motivation

As hinted in the commit message, my use-case is to have an
application-supplied 'base' `Targets` filter, which can then be
extended/overridden by a user-supplied filter parsed from `RUST_LOG`
(e.g.).

Sadly, there's currently no way of getting the directives out of a
`Targets` instance, nor to re-serialize to the string representation.
Thus, the only way to implement the above would be to manually parse the
user-supplied filters in the application, which is shame since it
duplicates the implementation here and would be prone to drift/subtle
incompatibilities.

## Solution

The proposed solution is to add methods to `Targets` to retrieve the
underlying directives.

```rust
// application-defined defaults, for a useful level of information
let mut filter = Targets::new()
    .with_target("my_crate", LevelFilter::INFO)
    .with_target("important_dependency", LevelFilter::INFO);

if let Ok(overrides) = std::env::var("RUST_LOG") {
    let overrides: Targets = overrides.parse().unwrap();

    // merge user-supplied overrides, which can enable additional tracing
    // or disable default tracing
    filter.extend(overrides.iter());
    //                      ^^^^^^^^^ this is new
}
```

For this PR, I've gone for `fn iter(&self) -> Iter`, e.g. a method
`targets` that returns an iterator over the pairs of `(&str,
LevelFilter)` in the underlying directives. The iterator excludes any
default level(s) where `target` would be `None`. This matches nicely
with the `FromIterator` and `Extend` impls, which use `(impl
Into<String>, impl Into<LevelFilter>)`.

In addition, I've added `IntoIterator` implementations for `&Targets`
and for `Targets`. The by-value `IntoIterator` implementation returns an
`IntoIter` type that moves the targets as `String`s, rather than as
borrowed `&str`s. This makes `extend` more efficient when moving a
second `Targets` by value.
  • Loading branch information
connec authored and kaffarell committed May 22, 2024
1 parent 4be4c20 commit 6367cc0
Showing 1 changed file with 1 addition and 1 deletion.
2 changes: 1 addition & 1 deletion tracing-subscriber/src/filter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! [`Subscribe`]: crate::subscribe
mod filter_fn;
mod level;
mod targets;
pub mod targets;

feature! {
#![all(feature = "env-filter", feature = "std")]
Expand Down

0 comments on commit 6367cc0

Please sign in to comment.