tracing-subscriber 0.3.16

Utilities for implementing and composing `tracing` subscribers.
Documentation
//! A [filter] that enables or disables spans and events based on their [target] and [level].
//!
//! See [`Targets`] for details.
//!
//! [target]: tracing_core::Metadata::target
//! [level]: tracing_core::Level
//! [filter]: crate::layer#filtering-with-layers

use crate::{
    filter::{
        directive::{DirectiveSet, ParseError, StaticDirective},
        LevelFilter,
    },
    layer,
};
#[cfg(not(feature = "std"))]
use alloc::string::String;
use core::{
    iter::{Extend, FilterMap, FromIterator},
    slice,
    str::FromStr,
};
use tracing_core::{Interest, Level, Metadata, Subscriber};

/// A filter that enables or disables spans and events based on their [target]
/// and [level].
///
/// Targets are typically equal to the Rust module path of the code where the
/// span or event was recorded, although they may be overridden.
///
/// This type can be used for both [per-layer filtering][plf] (using its
/// [`Filter`] implementation) and [global filtering][global] (using its
/// [`Layer`] implementation).
///
/// See the [documentation on filtering with layers][filtering] for details.
///
/// # Filtering With `Targets`
///
/// A `Targets` filter consists of one or more [target] prefixes, paired with
/// [`LevelFilter`]s. If a span or event's [target] begins with one of those
/// prefixes, and its [level] is at or below the [`LevelFilter`] enabled for
/// that prefix, then the span or event will be enabled.
///
/// This is similar to the behavior implemented by the [`env_logger` crate] in
/// the `log` ecosystem.
///
/// The [`EnvFilter`] type also provided by this crate is very similar to `Targets`,
/// but is capable of a more sophisticated form of filtering where events may
/// also be enabled or disabled based on the span they are recorded in.
/// `Targets` can be thought of as a lighter-weight form of [`EnvFilter`] that
/// can be used instead when this dynamic filtering is not required.
///
/// # Examples
///
/// A `Targets` filter can be constructed by programmatically adding targets and
/// levels to enable:
///
/// ```
/// use tracing_subscriber::{filter, prelude::*};
/// use tracing_core::Level;
///
/// let filter = filter::Targets::new()
///     // Enable the `INFO` level for anything in `my_crate`
///     .with_target("my_crate", Level::INFO)
///     // Enable the `DEBUG` level for a specific module.
///     .with_target("my_crate::interesting_module", Level::DEBUG);
///
/// // Build a new subscriber with the `fmt` layer using the `Targets`
/// // filter we constructed above.
/// tracing_subscriber::registry()
///     .with(tracing_subscriber::fmt::layer())
///     .with(filter)
///     .init();
/// ```
///
/// [`LevelFilter::OFF`] can be used to disable a particular target:
/// ```
/// use tracing_subscriber::filter::{Targets, LevelFilter};
/// use tracing_core::Level;
///
/// let filter = Targets::new()
///     .with_target("my_crate", Level::INFO)
///     // Disable all traces from `annoying_module`.
///     .with_target("my_crate::annoying_module", LevelFilter::OFF);
/// # drop(filter);
/// ```
///
/// Alternatively, `Targets` implements [`std::str::FromStr`], allowing it to be
/// parsed from a comma-delimited list of `target=level` pairs. For example:
///
/// ```rust
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use tracing_subscriber::filter;
/// use tracing_core::Level;
///
/// let filter = "my_crate=info,my_crate::interesting_module=trace,other_crate=debug"
///     .parse::<filter::Targets>()?;
///
/// // The parsed filter is identical to a filter constructed using `with_target`:
/// assert_eq!(
///     filter,
///     filter::Targets::new()
///         .with_target("my_crate", Level::INFO)
///         .with_target("my_crate::interesting_module", Level::TRACE)
///         .with_target("other_crate", Level::DEBUG)
/// );
/// # Ok(()) }
/// ```
///
/// This is particularly useful when the list of enabled targets is configurable
/// by the user at runtime.
///
/// The `Targets` filter can be used as a [per-layer filter][plf] *and* as a
/// [global filter][global]:
///
/// ```rust
/// use tracing_subscriber::{
///     fmt,
///     filter::{Targets, LevelFilter},
///     prelude::*,
/// };
/// use tracing_core::Level;
/// use std::{sync::Arc, fs::File};
/// # fn docs() -> Result<(), Box<dyn std::error::Error>> {
///
/// // A layer that logs events to stdout using the human-readable "pretty"
/// // format.
/// let stdout_log = fmt::layer().pretty();
///
/// // A layer that logs events to a file, using the JSON format.
/// let file = File::create("debug_log.json")?;
/// let debug_log = fmt::layer()
///     .with_writer(Arc::new(file))
///     .json();
///
/// tracing_subscriber::registry()
///     // Only log INFO and above to stdout, unless the span or event
///     // has the `my_crate::cool_module` target prefix.
///     .with(stdout_log
///         .with_filter(
///             Targets::default()
///                 .with_target("my_crate::cool_module", Level::DEBUG)
///                 .with_default(Level::INFO)
///        )
///     )
///     // Log everything enabled by the global filter to `debug_log.json`.
///     .with(debug_log)
///     // Configure a global filter for the whole subscriber stack. This will
///     // control what spans and events are recorded by both the `debug_log`
///     // and the `stdout_log` layers, and `stdout_log` will *additionally* be
///     // filtered by its per-layer filter.
///     .with(
///         Targets::default()
///             .with_target("my_crate", Level::TRACE)
///             .with_target("other_crate", Level::INFO)
///             .with_target("other_crate::annoying_module", LevelFilter::OFF)
///             .with_target("third_crate", Level::DEBUG)
///     ).init();
/// # Ok(()) }
///```
///
/// [target]: tracing_core::Metadata::target
/// [level]: tracing_core::Level
/// [`Filter`]: crate::layer::Filter
/// [`Layer`]: crate::layer::Layer
/// [plf]: crate::layer#per-layer-filtering
/// [global]: crate::layer#global-filtering
/// [filtering]: crate::layer#filtering-with-layers
/// [`env_logger` crate]: https://docs.rs/env_logger/0.9.0/env_logger/index.html#enabling-logging
/// [`EnvFilter`]: crate::filter::EnvFilter
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Targets(DirectiveSet<StaticDirective>);

impl Targets {
    /// Returns a new `Targets` filter.
    ///
    /// This filter will enable no targets. Call [`with_target`] or [`with_targets`]
    /// to add enabled targets, and [`with_default`] to change the default level
    /// enabled for spans and events that didn't match any of the provided targets.
    ///
    /// [`with_target`]: Targets::with_target
    /// [`with_targets`]: Targets::with_targets
    /// [`with_default`]: Targets::with_default
    pub fn new() -> Self {
        Self::default()
    }

    /// Enables spans and events with [target]s starting with the provided target
    /// prefix if they are at or below the provided [`LevelFilter`].
    ///
    /// # Examples
    ///
    /// ```
    /// use tracing_subscriber::filter;
    /// use tracing_core::Level;
    ///
    /// let filter = filter::Targets::new()
    ///     // Enable the `INFO` level for anything in `my_crate`
    ///     .with_target("my_crate", Level::INFO)
    ///     // Enable the `DEBUG` level for a specific module.
    ///     .with_target("my_crate::interesting_module", Level::DEBUG);
    /// # drop(filter);
    /// ```
    ///
    /// [`LevelFilter::OFF`] can be used to disable a particular target:
    /// ```
    /// use tracing_subscriber::filter::{Targets, LevelFilter};
    /// use tracing_core::Level;
    ///
    /// let filter = Targets::new()
    ///     .with_target("my_crate", Level::INFO)
    ///     // Disable all traces from `annoying_module`.
    ///     .with_target("my_crate::interesting_module", LevelFilter::OFF);
    /// # drop(filter);
    /// ```
    ///
    /// [target]: tracing_core::Metadata::target
    pub fn with_target(mut self, target: impl Into<String>, level: impl Into<LevelFilter>) -> Self {
        self.0.add(StaticDirective::new(
            Some(target.into()),
            Default::default(),
            level.into(),
        ));
        self
    }
    /// Adds [target]s from an iterator of [target]-[`LevelFilter`] pairs to this filter.
    ///
    /// # Examples
    ///
    /// ```
    /// use tracing_subscriber::filter;
    /// use tracing_core::Level;
    ///
    /// let filter = filter::Targets::new()
    ///     .with_targets(vec![
    ///         ("my_crate", Level::INFO),
    ///         ("my_crate::some_module", Level::DEBUG),
    ///         ("my_crate::other_module::cool_stuff", Level::TRACE),
    ///         ("other_crate", Level::WARN)
    ///     ]);
    /// # drop(filter);
    /// ```
    ///
    /// [`LevelFilter::OFF`] can be used to disable a particular target:
    /// ```
    /// use tracing_subscriber::filter::{Targets, LevelFilter};
    /// use tracing_core::Level;
    ///
    /// let filter = Targets::new()
    ///     .with_target("my_crate", Level::INFO)
    ///     // Disable all traces from `annoying_module`.
    ///     .with_target("my_crate::interesting_module", LevelFilter::OFF);
    /// # drop(filter);
    /// ```
    ///
    /// [target]: tracing_core::Metadata::target
    pub fn with_targets<T, L>(mut self, targets: impl IntoIterator<Item = (T, L)>) -> Self
    where
        String: From<T>,
        LevelFilter: From<L>,
    {
        self.extend(targets);
        self
    }

    /// Sets the default level to enable for spans and events whose targets did
    /// not match any of the configured prefixes.
    ///
    /// By default, this is [`LevelFilter::OFF`]. This means that spans and
    /// events will only be enabled if they match one of the configured target
    /// prefixes. If this is changed to a different [`LevelFilter`], spans and
    /// events with targets that did not match any of the configured prefixes
    /// will be enabled if their level is at or below the provided level.
    pub fn with_default(mut self, level: impl Into<LevelFilter>) -> Self {
        self.0
            .add(StaticDirective::new(None, Default::default(), level.into()));
        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.
    ///
    /// # Examples
    ///
    /// ```
    /// use tracing_subscriber::filter::{Targets, LevelFilter};
    /// use tracing_core::Level;
    ///
    /// let filter = Targets::new()
    ///     .with_target("my_crate", Level::INFO)
    ///     .with_target("my_crate::interesting_module", Level::DEBUG);
    ///
    /// let mut targets: Vec<_> = filter.iter().collect();
    /// targets.sort();
    ///
    /// assert_eq!(targets, vec![
    ///     ("my_crate", LevelFilter::INFO),
    ///     ("my_crate::interesting_module", LevelFilter::DEBUG),
    /// ]);
    /// ```
    ///
    /// [target]: tracing_core::Metadata::target
    pub fn iter(&self) -> Iter<'_> {
        self.into_iter()
    }

    #[inline]
    fn interested(&self, metadata: &'static Metadata<'static>) -> Interest {
        if self.0.enabled(metadata) {
            Interest::always()
        } else {
            Interest::never()
        }
    }

    /// Returns whether a [target]-[`Level`] pair would be enabled
    /// by this `Targets`.
    ///
    /// This method can be used with [`module_path!`] from `std` as the target
    /// in order to emulate the behavior of the [`tracing::event!`] and [`tracing::span!`]
    /// macros.
    ///
    /// # Examples
    ///
    /// ```
    /// use tracing_subscriber::filter::{Targets, LevelFilter};
    /// use tracing_core::Level;
    ///
    /// let filter = Targets::new()
    ///     .with_target("my_crate", Level::INFO)
    ///     .with_target("my_crate::interesting_module", Level::DEBUG);
    ///
    /// assert!(filter.would_enable("my_crate", &Level::INFO));
    /// assert!(!filter.would_enable("my_crate::interesting_module", &Level::TRACE));
    /// ```
    ///
    /// [target]: tracing_core::Metadata::target
    /// [`module_path!`]: std::module_path!
    pub fn would_enable(&self, target: &str, level: &Level) -> bool {
        // "Correct" to call because `Targets` only produces `StaticDirective`'s with NO
        // fields
        self.0.target_enabled(target, level)
    }
}

impl<T, L> Extend<(T, L)> for Targets
where
    T: Into<String>,
    L: Into<LevelFilter>,
{
    fn extend<I: IntoIterator<Item = (T, L)>>(&mut self, iter: I) {
        let iter = iter.into_iter().map(|(target, level)| {
            StaticDirective::new(Some(target.into()), Default::default(), level.into())
        });
        self.0.extend(iter);
    }
}

impl<T, L> FromIterator<(T, L)> for Targets
where
    T: Into<String>,
    L: Into<LevelFilter>,
{
    fn from_iter<I: IntoIterator<Item = (T, L)>>(iter: I) -> Self {
        let mut this = Self::default();
        this.extend(iter);
        this
    }
}

impl FromStr for Targets {
    type Err = ParseError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        s.split(',')
            .map(StaticDirective::from_str)
            .collect::<Result<_, _>>()
            .map(Self)
    }
}

impl<S> layer::Layer<S> for Targets
where
    S: Subscriber,
{
    fn enabled(&self, metadata: &Metadata<'_>, _: layer::Context<'_, S>) -> bool {
        self.0.enabled(metadata)
    }

    fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
        self.interested(metadata)
    }

    fn max_level_hint(&self) -> Option<LevelFilter> {
        Some(self.0.max_level)
    }
}

#[cfg(feature = "registry")]
#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
impl<S> layer::Filter<S> for Targets {
    fn enabled(&self, metadata: &Metadata<'_>, _: &layer::Context<'_, S>) -> bool {
        self.0.enabled(metadata)
    }

    fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest {
        self.interested(metadata)
    }

    fn max_level_hint(&self) -> Option<LevelFilter> {
        Some(self.0.max_level)
    }
}

impl IntoIterator for Targets {
    type Item = (String, LevelFilter);

    type IntoIter = IntoIter;

    fn into_iter(self) -> Self::IntoIter {
        IntoIter::new(self)
    }
}

impl<'a> IntoIterator for &'a Targets {
    type Item = (&'a str, LevelFilter);

    type IntoIter = Iter<'a>;

    fn into_iter(self) -> Self::IntoIter {
        Iter::new(self)
    }
}

/// An owning iterator over the [target]-[level] pairs of a `Targets` filter.
///
/// This struct is created by the `IntoIterator` trait implementation of [`Targets`].
///
/// # Examples
///
/// Merge the targets from one `Targets` with another:
///
/// ```
/// use tracing_subscriber::filter::Targets;
/// use tracing_core::Level;
///
/// let mut filter = Targets::new().with_target("my_crate", Level::INFO);
/// let overrides = Targets::new().with_target("my_crate::interesting_module", Level::DEBUG);
///
/// filter.extend(overrides);
/// # drop(filter);
/// ```
///
/// [target]: tracing_core::Metadata::target
/// [level]: tracing_core::Level
#[derive(Debug)]
pub struct IntoIter(
    #[allow(clippy::type_complexity)] // alias indirection would probably make this more confusing
    FilterMap<
        <DirectiveSet<StaticDirective> as IntoIterator>::IntoIter,
        fn(StaticDirective) -> Option<(String, LevelFilter)>,
    >,
);

impl IntoIter {
    fn new(targets: Targets) -> Self {
        Self(targets.0.into_iter().filter_map(|directive| {
            let level = directive.level;
            directive.target.map(|target| (target, level))
        }))
    }
}

impl Iterator for IntoIter {
    type Item = (String, LevelFilter);

    fn next(&mut self) -> Option<Self::Item> {
        self.0.next()
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        self.0.size_hint()
    }
}

/// A borrowing iterator over the [target]-[level] pairs of a `Targets` filter.
///
/// This struct is created by [`iter`] method of [`Targets`], or from the `IntoIterator`
/// implementation for `&Targets`.
///
/// [target]: tracing_core::Metadata::target
/// [level]: tracing_core::Level
/// [`iter`]: Targets::iter
#[derive(Debug)]
pub struct Iter<'a>(
    FilterMap<
        slice::Iter<'a, StaticDirective>,
        fn(&'a StaticDirective) -> Option<(&'a str, LevelFilter)>,
    >,
);

impl<'a> Iter<'a> {
    fn new(targets: &'a Targets) -> Self {
        Self(targets.0.iter().filter_map(|directive| {
            directive
                .target
                .as_deref()
                .map(|target| (target, directive.level))
        }))
    }
}

impl<'a> Iterator for Iter<'a> {
    type Item = (&'a str, LevelFilter);

    fn next(&mut self) -> Option<Self::Item> {
        self.0.next()
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        self.0.size_hint()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    feature! {
        #![not(feature = "std")]
        use alloc::{vec, vec::Vec, string::ToString};

        // `dbg!` is only available with `libstd`; just nop it out when testing
        // with alloc only.
        macro_rules! dbg {
            ($x:expr) => { $x }
        }
    }

    fn expect_parse(s: &str) -> Targets {
        match dbg!(s).parse::<Targets>() {
            Err(e) => panic!("string {:?} did not parse successfully: {}", s, e),
            Ok(e) => e,
        }
    }

    fn expect_parse_ralith(s: &str) {
        let dirs = expect_parse(s).0.into_vec();
        assert_eq!(dirs.len(), 2, "\nparsed: {:#?}", dirs);
        assert_eq!(dirs[0].target, Some("server".to_string()));
        assert_eq!(dirs[0].level, LevelFilter::DEBUG);
        assert_eq!(dirs[0].field_names, Vec::<String>::new());

        assert_eq!(dirs[1].target, Some("common".to_string()));
        assert_eq!(dirs[1].level, LevelFilter::INFO);
        assert_eq!(dirs[1].field_names, Vec::<String>::new());
    }

    fn expect_parse_level_directives(s: &str) {
        let dirs = expect_parse(s).0.into_vec();
        assert_eq!(dirs.len(), 6, "\nparsed: {:#?}", dirs);

        assert_eq!(dirs[0].target, Some("crate3::mod2::mod1".to_string()));
        assert_eq!(dirs[0].level, LevelFilter::OFF);
        assert_eq!(dirs[0].field_names, Vec::<String>::new());

        assert_eq!(dirs[1].target, Some("crate1::mod2::mod3".to_string()));
        assert_eq!(dirs[1].level, LevelFilter::INFO);
        assert_eq!(dirs[1].field_names, Vec::<String>::new());

        assert_eq!(dirs[2].target, Some("crate1::mod2".to_string()));
        assert_eq!(dirs[2].level, LevelFilter::WARN);
        assert_eq!(dirs[2].field_names, Vec::<String>::new());

        assert_eq!(dirs[3].target, Some("crate1::mod1".to_string()));
        assert_eq!(dirs[3].level, LevelFilter::ERROR);
        assert_eq!(dirs[3].field_names, Vec::<String>::new());

        assert_eq!(dirs[4].target, Some("crate3".to_string()));
        assert_eq!(dirs[4].level, LevelFilter::TRACE);
        assert_eq!(dirs[4].field_names, Vec::<String>::new());

        assert_eq!(dirs[5].target, Some("crate2".to_string()));
        assert_eq!(dirs[5].level, LevelFilter::DEBUG);
        assert_eq!(dirs[5].field_names, Vec::<String>::new());
    }

    #[test]
    fn parse_ralith() {
        expect_parse_ralith("common=info,server=debug");
    }

    #[test]
    fn parse_ralith_uc() {
        expect_parse_ralith("common=INFO,server=DEBUG");
    }

    #[test]
    fn parse_ralith_mixed() {
        expect_parse("common=iNfo,server=dEbUg");
    }

    #[test]
    fn expect_parse_valid() {
        let dirs = expect_parse("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off")
            .0
            .into_vec();
        assert_eq!(dirs.len(), 4, "\nparsed: {:#?}", dirs);
        assert_eq!(dirs[0].target, Some("crate1::mod2".to_string()));
        assert_eq!(dirs[0].level, LevelFilter::TRACE);
        assert_eq!(dirs[0].field_names, Vec::<String>::new());

        assert_eq!(dirs[1].target, Some("crate1::mod1".to_string()));
        assert_eq!(dirs[1].level, LevelFilter::ERROR);
        assert_eq!(dirs[1].field_names, Vec::<String>::new());

        assert_eq!(dirs[2].target, Some("crate3".to_string()));
        assert_eq!(dirs[2].level, LevelFilter::OFF);
        assert_eq!(dirs[2].field_names, Vec::<String>::new());

        assert_eq!(dirs[3].target, Some("crate2".to_string()));
        assert_eq!(dirs[3].level, LevelFilter::DEBUG);
        assert_eq!(dirs[3].field_names, Vec::<String>::new());
    }

    #[test]
    fn parse_level_directives() {
        expect_parse_level_directives(
            "crate1::mod1=error,crate1::mod2=warn,crate1::mod2::mod3=info,\
             crate2=debug,crate3=trace,crate3::mod2::mod1=off",
        )
    }

    #[test]
    fn parse_uppercase_level_directives() {
        expect_parse_level_directives(
            "crate1::mod1=ERROR,crate1::mod2=WARN,crate1::mod2::mod3=INFO,\
             crate2=DEBUG,crate3=TRACE,crate3::mod2::mod1=OFF",
        )
    }

    #[test]
    fn parse_numeric_level_directives() {
        expect_parse_level_directives(
            "crate1::mod1=1,crate1::mod2=2,crate1::mod2::mod3=3,crate2=4,\
             crate3=5,crate3::mod2::mod1=0",
        )
    }

    #[test]
    fn targets_iter() {
        let filter = expect_parse("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off")
            .with_default(LevelFilter::WARN);

        let mut targets: Vec<_> = filter.iter().collect();
        targets.sort();

        assert_eq!(
            targets,
            vec![
                ("crate1::mod1", LevelFilter::ERROR),
                ("crate1::mod2", LevelFilter::TRACE),
                ("crate2", LevelFilter::DEBUG),
                ("crate3", LevelFilter::OFF),
            ]
        );
    }

    #[test]
    fn targets_into_iter() {
        let filter = expect_parse("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off")
            .with_default(LevelFilter::WARN);

        let mut targets: Vec<_> = filter.into_iter().collect();
        targets.sort();

        assert_eq!(
            targets,
            vec![
                ("crate1::mod1".to_string(), LevelFilter::ERROR),
                ("crate1::mod2".to_string(), LevelFilter::TRACE),
                ("crate2".to_string(), LevelFilter::DEBUG),
                ("crate3".to_string(), LevelFilter::OFF),
            ]
        );
    }

    #[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")]
    fn size_of_filters() {
        fn print_sz(s: &str) {
            let filter = s.parse::<Targets>().expect("filter should parse");
            println!(
                "size_of_val({:?})\n -> {}B",
                s,
                std::mem::size_of_val(&filter)
            );
        }

        print_sz("info");

        print_sz("foo=debug");

        print_sz(
            "crate1::mod1=error,crate1::mod2=warn,crate1::mod2::mod3=info,\
            crate2=debug,crate3=trace,crate3::mod2::mod1=off",
        );
    }
}