alto_logger 0.4.0

an opinionated logger that provides colorful and multi-line output
Documentation
use std::{borrow::Cow, collections::HashMap};

#[derive(Debug)]
pub(crate) enum FiltersKind {
    Default,
    Blanket,
    List(Vec<(Cow<'static, str>, log::LevelFilter)>),
    Map(HashMap<Cow<'static, str>, log::LevelFilter>),
}

#[derive(Debug)]
pub(crate) struct Filters {
    kind: FiltersKind,
    minimum: Option<log::LevelFilter>,
}

impl Default for Filters {
    fn default() -> Self {
        Self {
            kind: FiltersKind::Default,
            minimum: None,
        }
    }
}

impl Filters {
    pub(crate) fn from_str(input: &str) -> Self {
        let mut mapping = input.split(',').filter_map(parse).collect::<Vec<_>>();

        let minimum = input
            .split(',')
            .filter(|s| !s.contains('='))
            .flat_map(|s| s.parse().ok())
            .filter(|&l| l != log::LevelFilter::Off)
            .max();

        let kind = match mapping.len() {
            0 if minimum.is_none() => FiltersKind::Default,
            0 => FiltersKind::Blanket,
            d if d < 15 => {
                mapping.shrink_to_fit();
                FiltersKind::List(mapping)
            }
            _ => FiltersKind::Map(mapping.into_iter().collect()),
        };

        Self { kind, minimum }
    }

    pub(crate) fn from_env() -> Self {
        std::env::var("RUST_LOG")
            .map(|s| Self::from_str(&s))
            .unwrap_or_default()
    }

    #[inline]
    pub(crate) fn is_enabled(&self, metadata: &log::Metadata<'_>) -> bool {
        match self.find_module(metadata.target()) {
            Some(level) => metadata.level() <= level,
            None => false,
        }
    }

    #[inline]
    pub(crate) fn find_module(&self, module: &str) -> Option<log::LevelFilter> {
        match self.kind {
            FiltersKind::Default => return None,
            FiltersKind::Blanket => return self.minimum,
            _ => {}
        }

        if let Some(level) = self.find_exact(module) {
            return Some(level);
        }

        let mut last = false;
        for (i, ch) in module.char_indices().rev() {
            if last {
                last = false;
                if ch == ':' {
                    if let Some(level) = self.find_exact(&module[..i]) {
                        return Some(level);
                    }
                }
            } else if ch == ':' {
                last = true
            }
        }

        self.minimum
    }

    #[inline]
    pub(crate) fn find_exact(&self, module: &str) -> Option<log::LevelFilter> {
        match &self.kind {
            FiltersKind::Default => None,
            FiltersKind::Blanket => self.minimum,
            FiltersKind::List(levels) => levels
                .iter()
                .find_map(|(m, level)| Some(*level).filter(|_| m == module)),
            FiltersKind::Map(levels) => levels.get(module).copied(),
        }
    }
}

#[inline]
pub(crate) fn parse(input: &str) -> Option<(Cow<'static, str>, log::LevelFilter)> {
    let mut iter = input.split('=');
    Some((
        Cow::Owned(iter.next()?.to_string()),
        iter.next()?.to_ascii_uppercase().parse().ok()?,
    ))
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn filters() {
        let input = "debug,foo::bar=off,foo::baz=trace,foo=info,baz=off,quux=error";
        let filters = Filters::from_str(input);

        let modules = &[
            ("foo::bar", log::LevelFilter::Off),
            ("foo::baz", log::LevelFilter::Trace),
            ("foo", log::LevelFilter::Info),
            ("baz", log::LevelFilter::Off),
            ("quux", log::LevelFilter::Error),
            ("something", log::LevelFilter::Debug),
            ("another::thing", log::LevelFilter::Debug),
        ];

        for (module, expected) in modules {
            assert_eq!(filters.find_module(module).unwrap(), *expected);
        }
    }

    #[test]
    fn minimum() {
        let filters =
            Filters::from_str("debug,foo::bar=off,foo::baz=trace,foo=info,baz=off,quux=error");

        let modules = &[
            ("foo::bar", log::LevelFilter::Off),
            ("foo::baz", log::LevelFilter::Trace),
            ("foo", log::LevelFilter::Info),
            ("baz", log::LevelFilter::Off),
            ("quux", log::LevelFilter::Error),
            ("something", log::LevelFilter::Debug),
            ("another::thing", log::LevelFilter::Debug),
            ("this::is::unknown", log::LevelFilter::Debug),
        ];

        for (module, expected) in modules {
            assert_eq!(filters.find_module(module).unwrap(), *expected);
        }
    }
}