Skip to content

Commit

Permalink
subscriber: add Targets::default_level method (#2242)
Browse files Browse the repository at this point in the history
## Motivation

This makes it possible to fully "override" some base `Targets` filter
with another (e.g. user-supplied) filter. Without some way to obtain the
default, only explicit targets can be overridden (via `IntoIter` and
friends). 

See also #1790 (comment)

## Solution

We can add a method to `Targets` that filters the underlying
`DirectiveSet` for the default directive. This works because
`DirectiveSet::add` will replace directives with the same
`target`/`field_names`, which is always `None`/`vec![]` for the
directive added by `with_default` (and in fact we are only concerned
with `target`, since no other `Targets` API allows adding directives
with a `None` target).

Ideally the method would be named `default`, corresponding to
`with_default`, however this conflicts with `Default::default` and so
would be a breaking change (and harm ergonomics). `default_level` seemed
a better name than `get_default`, since "getters" of this style are
generally considered unidiomatic<sup>[citation needed]</sup>.

Example usage:

```rust
let mut filter = Targets::new().with_target("some_module", LevelFilter::INFO);

// imagine this came from `RUST_LOG` or similar
let override: Targets = "trace".parse().unwrap();

// merge the override
if let Some(default) = override.default_level() {
    filter = filter.with_default(default);
}
for (target, level) in override.iter() {
    filter = filter.with_target(target, level);
}
```

Closes #1790
  • Loading branch information
connec authored and hawkw committed Jul 28, 2022
1 parent 63914b6 commit dc6c181
Showing 1 changed file with 71 additions and 0 deletions.
71 changes: 71 additions & 0 deletions tracing-subscriber/src/filter/targets.rs
Expand Up @@ -277,6 +277,62 @@ impl Targets {
self
}

/// Returns the default level for this filter, if one is set.
///
/// The default level is used to filter any spans or events with targets
/// that do not match any of the configured set of prefixes.
///
/// The default level can be set for a filter either by using
/// [`with_default`](Self::with_default) or when parsing from a filter string that includes a
/// level without a target (e.g. `"trace"`).
///
/// # Examples
///
/// ```
/// use tracing_subscriber::filter::{LevelFilter, Targets};
///
/// let filter = Targets::new().with_default(LevelFilter::INFO);
/// assert_eq!(filter.default_level(), Some(LevelFilter::INFO));
///
/// let filter: Targets = "info".parse().unwrap();
/// assert_eq!(filter.default_level(), Some(LevelFilter::INFO));
/// ```
///
/// The default level is `None` if no default is set:
///
/// ```
/// use tracing_subscriber::filter::Targets;
///
/// let filter = Targets::new();
/// assert_eq!(filter.default_level(), None);
///
/// let filter: Targets = "my_crate=info".parse().unwrap();
/// assert_eq!(filter.default_level(), None);
/// ```
///
/// Note that an unset default level (`None`) behaves like [`LevelFilter::OFF`] when the filter is
/// used, but it could also be set explicitly which may be useful to distinguish (such as when
/// merging multiple `Targets`).
///
/// ```
/// use tracing_subscriber::filter::{LevelFilter, Targets};
///
/// let filter = Targets::new().with_default(LevelFilter::OFF);
/// assert_eq!(filter.default_level(), Some(LevelFilter::OFF));
///
/// let filter: Targets = "off".parse().unwrap();
/// assert_eq!(filter.default_level(), Some(LevelFilter::OFF));
/// ```
pub fn default_level(&self) -> Option<LevelFilter> {
self.0.directives().into_iter().find_map(|d| {
if d.target.is_none() {
Some(d.level)
} else {
None
}
})
}

/// Returns an iterator over the [target]-[`LevelFilter`] pairs in this filter.
///
/// The order of iteration is undefined.
Expand Down Expand Up @@ -685,6 +741,21 @@ mod tests {
);
}

#[test]
fn targets_default_level() {
let filter = expect_parse("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off");
assert_eq!(filter.default_level(), None);

let filter = expect_parse("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off")
.with_default(LevelFilter::OFF);
assert_eq!(filter.default_level(), Some(LevelFilter::OFF));

let filter = expect_parse("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off")
.with_default(LevelFilter::OFF)
.with_default(LevelFilter::INFO);
assert_eq!(filter.default_level(), Some(LevelFilter::INFO));
}

#[test]
// `println!` is only available with `libstd`.
#[cfg(feature = "std")]
Expand Down

0 comments on commit dc6c181

Please sign in to comment.