From 511927afea2af053b6c6cac7f307a05b7c6b89dd Mon Sep 17 00:00:00 2001 From: Jarrod Overson Date: Tue, 15 Aug 2023 14:35:49 -0400 Subject: [PATCH] feat: added better granularity to log filter rules --- Cargo.lock | 1 + crates/bins/wick/src/options.rs | 105 +++---- crates/wick/wick-logger/Cargo.toml | 4 + crates/wick/wick-logger/src/lib.rs | 2 +- crates/wick/wick-logger/src/options.rs | 366 ++++++++++++++++++++++--- 5 files changed, 371 insertions(+), 107 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3990a673..889e8666 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6648,6 +6648,7 @@ dependencies = [ "console-subscriber", "opentelemetry", "opentelemetry-otlp", + "rstest", "thiserror", "time 0.3.25", "tokio", diff --git a/crates/bins/wick/src/options.rs b/crates/bins/wick/src/options.rs index 7771e634..c856dae9 100644 --- a/crates/bins/wick/src/options.rs +++ b/crates/bins/wick/src/options.rs @@ -2,7 +2,8 @@ use std::path::PathBuf; use clap::Args; use once_cell::sync::Lazy; -use wick_logger::{FilterOptions, LogLevel, TargetLevel}; +use regex::Regex; +use wick_logger::{FilterOptions, LogLevel, LogModifier, TargetLevel}; use wick_oci_utils::{OciOptions, OnExisting}; use wick_settings::Credential; @@ -58,42 +59,36 @@ impl LoggingOptions { } } -static DEFAULT_EXCLUSION_FILTER: Lazy> = Lazy::new(|| { +static DEFAULT_FILTER: Lazy> = Lazy::new(|| { vec![ - TargetLevel::new("wasmrs", wick_logger::LogLevel::Error), - TargetLevel::new("wasmrs_runtime", wick_logger::LogLevel::Error), - TargetLevel::new("wasmrs_wasmtime", wick_logger::LogLevel::Error), + TargetLevel::lte("flow", wick_logger::LogLevel::Warn), + TargetLevel::lte("wasmrs", wick_logger::LogLevel::Error), + TargetLevel::lte("wasmrs_runtime", wick_logger::LogLevel::Error), + TargetLevel::lte("wasmrs_wasmtime", wick_logger::LogLevel::Error), ] }); -static DEFAULT_INCLUSION_FILTER: Lazy> = - Lazy::new(|| vec![TargetLevel::new("flow", wick_logger::LogLevel::Warn)]); +static RULE_REGEX: Lazy = Lazy::new(|| Regex::new("(\\w+)\\s*(<|<=|>|>=|!=|==|=)\\s*(\\w+)").unwrap()); -fn parse_logstr( - default_level: LogLevel, - default_inc: &[TargetLevel], - default_exc: &[TargetLevel], - value: &str, -) -> FilterOptions { - let parts: Vec<(bool, Option<&str>, LogLevel)> = value +fn parse_logstr(default_level: LogLevel, default_filter: &[TargetLevel], logstr: &str) -> FilterOptions { + let parts: Vec<(LogModifier, Option<&str>, LogLevel)> = logstr .split(',') .filter_map(|s| { let s = s.trim(); if s.is_empty() { return None; } - if !s.contains('=') { - return Some((false, None, s.parse().ok()?)); + + if !s.contains(['<', '>', '=', '!']) { + return Some((LogModifier::LessThanOrEqualTo, None, s.parse().ok()?)); } - let mut parts = s.split('='); - let target = parts.next()?.trim(); - let (negated, target) = if target.starts_with('!') { - (true, target.trim_start_matches('!').trim()) - } else { - (false, target) - }; - let level = parts.next().unwrap_or("info").trim(); - Some((negated, Some(target), level.parse().ok()?)) + let parts = RULE_REGEX.captures(s)?; + + let target = parts.get(1)?.as_str(); + let modifier: LogModifier = parts.get(2)?.as_str().parse().ok()?; + let level: LogLevel = parts.get(3)?.as_str().parse().ok()?; + + Some((modifier, Some(target), level)) }) .collect(); @@ -102,31 +97,15 @@ fn parse_logstr( .find(|(_, target, _)| target.is_none()) .map_or_else(|| default_level, |(_, _, level)| *level); - let exclude = parts + let filter = parts .iter() - .filter_map(|(negated, target, level)| match negated { - true => target.map(|target| TargetLevel::new(target, *level)), - false => None, - }) - .collect::>(); - let include = parts - .iter() - .filter_map(|(negated, target, level)| match negated { - true => None, - false => target.map(|target| TargetLevel::new(target, *level)), - }) + .filter_map(|(modifier, target, level)| target.map(|target| TargetLevel::new(target, *level, *modifier))) .collect::>(); FilterOptions { level: global_level, // If the filter had inclusion rules, use those. Otherwise, use the default. - include: if include.is_empty() { - default_inc.to_vec() - } else { - include - }, - // If the filter had exclusion rules, add them to our defaults. - exclude: [default_exc.to_vec(), exclude].concat(), + filter: [filter, default_filter.to_vec()].concat(), } } @@ -144,15 +123,13 @@ impl From<&LoggingOptions> for wick_logger::LoggingOptions { let stderr_opts = parse_logstr( global_level, - &DEFAULT_INCLUSION_FILTER, - &DEFAULT_EXCLUSION_FILTER, + &DEFAULT_FILTER, value.stderr_filter.as_deref().unwrap_or_default(), ); let otel_opts = parse_logstr( global_level, - &DEFAULT_INCLUSION_FILTER, - &DEFAULT_EXCLUSION_FILTER, + &DEFAULT_FILTER, value.tel_filter.as_deref().unwrap_or_default(), ); @@ -266,35 +243,29 @@ mod test { use super::*; - type ExpectedLogRule = (LogLevel, Vec, Vec); + type ExpectedLogRule = (LogLevel, Vec); #[rstest::rstest] - #[case(LogLevel::Info, "trace", (LogLevel::Trace, DEFAULT_INCLUSION_FILTER.to_vec(),vec![]))] - #[case(LogLevel::Info, "wick=trace", (LogLevel::Info, vec![TargetLevel::new("wick", LogLevel::Trace)],vec![]))] - #[case(LogLevel::Info, "debug,wick=trace", (LogLevel::Debug, vec![TargetLevel::new("wick", LogLevel::Trace)],vec![]))] - #[case(LogLevel::Info, "wick=trace,debug", (LogLevel::Debug, vec![TargetLevel::new("wick", LogLevel::Trace)],vec![]))] - #[case(LogLevel::Info, " wick=trace , debug ,,, ,", (LogLevel::Debug, vec![TargetLevel::new("wick", LogLevel::Trace)],vec![]))] - #[case(LogLevel::Info, "wick=trace,!flow=info", (LogLevel::Info, vec![TargetLevel::new("wick", LogLevel::Trace)],vec![TargetLevel::new("flow", LogLevel::Info)]))] - #[case(LogLevel::Info, "wick=trace,!flow=info,wasmrs=info", (LogLevel::Info, vec![TargetLevel::new("wick", LogLevel::Trace),TargetLevel::new("wasmrs", LogLevel::Info)],vec![TargetLevel::new("flow", LogLevel::Info)]))] - #[case(LogLevel::Info, "wick = trace, ! flow = info, wasmrs = info", (LogLevel::Info, vec![TargetLevel::new("wick", LogLevel::Trace),TargetLevel::new("wasmrs", LogLevel::Info)],vec![TargetLevel::new("flow", LogLevel::Info)]))] + #[case(LogLevel::Info, "trace", (LogLevel::Trace,vec![]))] + #[case(LogLevel::Info, "wick<=trace", (LogLevel::Info, vec![TargetLevel::lte("wick", LogLevel::Trace)]))] + #[case(LogLevel::Info, "debug,wick<=trace", (LogLevel::Debug, vec![TargetLevel::lte("wick", LogLevel::Trace)]))] + #[case(LogLevel::Info, "wick<=trace,debug", (LogLevel::Debug, vec![TargetLevel::lte("wick", LogLevel::Trace)]))] + #[case(LogLevel::Info, " wick<=trace , debug ,,, ,", (LogLevel::Debug, vec![TargetLevel::lte("wick", LogLevel::Trace)]))] + #[case(LogLevel::Info, "wick<=trace,flow!=info", (LogLevel::Info, vec![TargetLevel::lte("wick", LogLevel::Trace),TargetLevel::not("flow", LogLevel::Info)]))] + #[case(LogLevel::Info, "wick<=trace,flow!=info,wasmrs<=info", (LogLevel::Info, vec![TargetLevel::lte("wick", LogLevel::Trace),TargetLevel::not("flow", LogLevel::Info),TargetLevel::lte("wasmrs", LogLevel::Info)]))] + #[case(LogLevel::Info, "wick <= trace, flow != info, wasmrs <= info",(LogLevel::Info, vec![TargetLevel::lte("wick", LogLevel::Trace),TargetLevel::not("flow", LogLevel::Info),TargetLevel::lte("wasmrs", LogLevel::Info)]))] fn test_log_rules( #[case] default_loglevel: LogLevel, #[case] filter: &str, #[case] expected: ExpectedLogRule, ) -> Result<()> { - let filter = parse_logstr( - default_loglevel, - &DEFAULT_INCLUSION_FILTER, - &DEFAULT_EXCLUSION_FILTER, - filter, - ); + let filter = parse_logstr(default_loglevel, &DEFAULT_FILTER, filter); assert_eq!(filter.level, expected.0); - assert_eq!(filter.include, expected.1); - // prepend the default exclusion filter so we don't need to include it in test cases above. - let expected_exclude = [DEFAULT_EXCLUSION_FILTER.to_vec(), expected.2].concat(); + // append the default exclusion filter so we don't need to include it in test cases above. + let expected_filter = [expected.1, DEFAULT_FILTER.to_vec()].concat(); - assert_eq!(filter.exclude, expected_exclude); + assert_eq!(filter.filter, expected_filter); Ok(()) } diff --git a/crates/wick/wick-logger/Cargo.toml b/crates/wick/wick-logger/Cargo.toml index db701c05..cbd5de1f 100644 --- a/crates/wick/wick-logger/Cargo.toml +++ b/crates/wick/wick-logger/Cargo.toml @@ -34,3 +34,7 @@ opentelemetry-otlp = { workspace = true, features = [ "grpc-tonic", "trace", ] } + + +[dev-dependencies] +rstest = { workspace = true } diff --git a/crates/wick/wick-logger/src/lib.rs b/crates/wick/wick-logger/src/lib.rs index a942543c..570e4aa9 100644 --- a/crates/wick/wick-logger/src/lib.rs +++ b/crates/wick/wick-logger/src/lib.rs @@ -93,7 +93,7 @@ pub mod error; /// Logger options. mod options; -pub use options::{FilterOptions, LogFilters, LogLevel, LoggingOptions, TargetLevel}; +pub use options::{FilterOptions, LogFilters, LogLevel, LogModifier, LoggingOptions, TargetLevel}; /// The main Logger module. mod logger; diff --git a/crates/wick/wick-logger/src/options.rs b/crates/wick/wick-logger/src/options.rs index dbe8685f..27c7b01b 100644 --- a/crates/wick/wick-logger/src/options.rs +++ b/crates/wick/wick-logger/src/options.rs @@ -1,5 +1,6 @@ use std::cmp; use std::path::PathBuf; +use std::str::FromStr; use tracing::{Level, Metadata}; use tracing_subscriber::layer::Context; @@ -60,22 +61,46 @@ impl LogFilters { pub struct FilterOptions { /// The default log level for anything that does not match an include or exclude filter. pub level: LogLevel, - /// The targets and their log levels to include. - pub include: Vec, - /// The targets and their log levels to exclude. - pub exclude: Vec, + /// The targets and their log levels. + pub filter: Vec, } impl Default for FilterOptions { fn default() -> Self { Self { level: LogLevel::Info, - include: vec![], - exclude: vec![], + filter: vec![], } } } +impl FilterOptions { + fn test_enabled(&self, module: &str, level: Level) -> bool { + let matches = self.filter.iter().filter(|config| module.starts_with(&config.target)); + let match_hit = matches.fold(None, |acc, next| { + let enabled = next.modifier.compare(filter_as_usize(level), next.level as usize); + let next_len = next.target.len(); + acc.map_or(Some((next_len, enabled)), |(last_len, last_enabled)| { + match next_len.cmp(&last_len) { + cmp::Ordering::Greater => { + // if we're more specific, use the most recent match result. + Some((next_len, enabled)) + } + cmp::Ordering::Equal => { + // if we're the same specifity, keep testing + Some((last_len, enabled && last_enabled)) + } + cmp::Ordering::Less => { + // otherwise, keep the last match result + Some((last_len, last_enabled)) + } + } + }) + }); + match_hit.map_or(self.level >= level, |(_, enabled)| enabled) + } +} + impl tracing_subscriber::layer::Filter for FilterOptions where S: tracing::Subscriber + for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>, @@ -89,26 +114,7 @@ where fn event_enabled(&self, event: &tracing::Event<'_>, _cx: &Context<'_, S>) -> bool { let module = event.metadata().target().split("::").next().unwrap_or_default(); let level = event.metadata().level(); - - let excluded = self - .exclude - .iter() - .find(|config| module.starts_with(&config.target) || config.target == "*") - .map(|excluded| excluded.level < *level); - - let included = self - .include - .iter() - .find(|config| module.starts_with(&config.target) || config.target == "*") - .map(|included| included.level >= (*level)); - - if included == Some(true) { - true - } else if excluded == Some(true) { - return false; - } else { - self.level >= *level - } + self.test_enabled(module, *level) } } @@ -119,17 +125,55 @@ pub struct TargetLevel { pub target: String, /// The level to log at. pub level: LogLevel, + /// The modifier that controls how to use this log level. + pub modifier: LogModifier, } impl TargetLevel { - /// Create a new instance for the given target and log level. - #[must_use] - pub fn new(target: impl AsRef, level: LogLevel) -> Self { + /// Create a new instance for the given target, log level, and modifier. + pub fn new(target: impl AsRef, level: LogLevel, modifier: LogModifier) -> Self { Self { target: target.as_ref().to_owned(), level, + modifier, } } + + /// Create a new negated instance for the given target and log level. + #[must_use] + pub fn not(target: impl AsRef, level: LogLevel) -> Self { + Self::new(target, level, LogModifier::Not) + } + + /// Create a new instance that matches the given target and any log level greater than the one specified. + #[must_use] + pub fn gt(target: impl AsRef, level: LogLevel) -> Self { + Self::new(target, level, LogModifier::GreaterThan) + } + + /// Create a new instance that matches the given target and any log level greater than or equal to the one specified. + #[must_use] + pub fn gte(target: impl AsRef, level: LogLevel) -> Self { + Self::new(target, level, LogModifier::GreaterThanOrEqualTo) + } + + /// Create a new instance that matches the given target and any log level less than or equal to the one specified. + #[must_use] + pub fn lt(target: impl AsRef, level: LogLevel) -> Self { + Self::new(target, level, LogModifier::LessThan) + } + + /// Create a new instance that matches the given target and any log level less than or equal to the one specified. + #[must_use] + pub fn lte(target: impl AsRef, level: LogLevel) -> Self { + Self::new(target, level, LogModifier::LessThanOrEqualTo) + } + + /// Create a new instance that matches the given target and any log level equal to the one specified. + #[must_use] + pub fn is(target: impl AsRef, level: LogLevel) -> Self { + Self::new(target, level, LogModifier::Equal) + } } impl LoggingOptions { @@ -142,6 +186,72 @@ impl LoggingOptions { } } +#[derive(Debug, Clone, PartialEq, Copy)] +/// Whether to include logs higher, lower, equal, or to not include them at all. +pub enum LogModifier { + /// Do not log the associated level. + Not, + /// Only log events greater than the associated level. + GreaterThan, + /// Only log events greater than or equal to the associated level. + GreaterThanOrEqualTo, + /// Only log events less than the associated level. + LessThan, + /// Only log events less than or equal to the associated level. + LessThanOrEqualTo, + /// Only log events equal to the associated level. + Equal, +} + +impl Default for LogModifier { + fn default() -> Self { + Self::LessThanOrEqualTo + } +} + +impl std::fmt::Display for LogModifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LogModifier::Not => write!(f, "!="), + LogModifier::GreaterThan => write!(f, ">"), + LogModifier::GreaterThanOrEqualTo => write!(f, ">="), + LogModifier::LessThan => write!(f, "<"), + LogModifier::LessThanOrEqualTo => write!(f, "<="), + LogModifier::Equal => write!(f, "="), + } + } +} + +impl LogModifier { + fn compare(self, a: usize, b: usize) -> bool { + match self { + LogModifier::Not => a != b, + LogModifier::GreaterThan => a > b, + LogModifier::GreaterThanOrEqualTo => a >= b, + LogModifier::LessThan => a < b, + LogModifier::LessThanOrEqualTo => a <= b, + LogModifier::Equal => a == b, + } + } +} + +impl FromStr for LogModifier { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "!=" => Ok(LogModifier::Not), + ">" => Ok(LogModifier::GreaterThan), + ">=" => Ok(LogModifier::GreaterThanOrEqualTo), + "<" => Ok(LogModifier::LessThan), + "<=" => Ok(LogModifier::LessThanOrEqualTo), + "=" | "==" => Ok(LogModifier::Equal), + + _ => Err(()), + } + } +} + /// The log levels. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[repr(usize)] @@ -166,8 +276,8 @@ impl Default for LogLevel { } } -impl std::str::FromStr for LogLevel { - type Err = String; +impl FromStr for LogLevel { + type Err = (); fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { @@ -177,7 +287,7 @@ impl std::str::FromStr for LogLevel { "info" => Ok(LogLevel::Info), "debug" => Ok(LogLevel::Debug), "trace" => Ok(LogLevel::Trace), - _ => Err(format!("Unknown log level: {}", s)), + _ => Err(()), } } } @@ -212,33 +322,33 @@ impl PartialEq for LogLevel { impl PartialOrd for LogLevel { #[inline(always)] fn partial_cmp(&self, other: &Level) -> Option { - Some((*self as usize).cmp(&filter_as_usize(other))) + Some((*self as usize).cmp(&filter_as_usize(*other))) } #[inline(always)] fn lt(&self, other: &Level) -> bool { - (*self as usize) < filter_as_usize(other) + (*self as usize) < filter_as_usize(*other) } #[inline(always)] fn le(&self, other: &Level) -> bool { - (*self as usize) <= filter_as_usize(other) + (*self as usize) <= filter_as_usize(*other) } #[inline(always)] fn gt(&self, other: &Level) -> bool { - (*self as usize) > filter_as_usize(other) + (*self as usize) > filter_as_usize(*other) } #[inline(always)] fn ge(&self, other: &Level) -> bool { - (*self as usize) >= filter_as_usize(other) + (*self as usize) >= filter_as_usize(*other) } } #[inline(always)] -fn filter_as_usize(x: &Level) -> usize { - (match *x { +fn filter_as_usize(x: Level) -> usize { + (match x { Level::ERROR => 0, Level::WARN => 1, Level::INFO => 2, @@ -246,3 +356,181 @@ fn filter_as_usize(x: &Level) -> usize { Level::TRACE => 4, } + 1) } + +#[cfg(test)] +mod test { + + use super::*; + + #[test] + fn test_modifier_compare() { + assert!(LogModifier::Equal.compare(2, 2)); + assert!(LogModifier::GreaterThan.compare(4, 2)); + assert!(LogModifier::GreaterThanOrEqualTo.compare(4, 2)); + assert!(LogModifier::GreaterThanOrEqualTo.compare(2, 2)); + assert!(LogModifier::Not.compare(4, 3)); + assert!(LogModifier::LessThan.compare(1, 2)); + assert!(LogModifier::LessThanOrEqualTo.compare(1, 2)); + assert!(LogModifier::LessThanOrEqualTo.compare(2, 2)); + } + + #[test] + fn test_modifier_compare_level() { + assert!(LogModifier::Equal.compare(filter_as_usize(Level::TRACE), LogLevel::Trace as usize)); + assert!(LogModifier::GreaterThan.compare(filter_as_usize(Level::TRACE), LogLevel::Warn as usize)); + assert!(LogModifier::GreaterThanOrEqualTo.compare(filter_as_usize(Level::INFO), LogLevel::Info as usize)); + assert!(LogModifier::GreaterThanOrEqualTo.compare(filter_as_usize(Level::TRACE), LogLevel::Debug as usize)); + assert!(LogModifier::Not.compare(filter_as_usize(Level::ERROR), LogLevel::Trace as usize)); + assert!(LogModifier::LessThan.compare(filter_as_usize(Level::INFO), LogLevel::Debug as usize)); + assert!(LogModifier::LessThanOrEqualTo.compare(filter_as_usize(Level::TRACE), LogLevel::Trace as usize)); + assert!(LogModifier::LessThanOrEqualTo.compare(filter_as_usize(Level::INFO), LogLevel::Trace as usize)); + } + + #[allow(clippy::needless_pass_by_value)] + fn opts(default: LogLevel, targets: [TargetLevel; K]) -> FilterOptions { + FilterOptions { + level: default, + filter: targets.to_vec(), + } + } + + #[test] + fn test_default_level() { + assert!(opts(LogLevel::Info, []).test_enabled("wick", Level::INFO)); + assert!(!opts(LogLevel::Info, []).test_enabled("wick", Level::TRACE)); + } + + #[rstest::rstest] + #[case(LogLevel::Info, [TargetLevel::lte("wick",LogLevel::Trace)], "wick", Level::TRACE, true)] + #[case(LogLevel::Info, [TargetLevel::lte("wick",LogLevel::Info),TargetLevel::lte("wick_packet",LogLevel::Trace)], "wick_packet", Level::TRACE, true)] + #[case(LogLevel::Info, [ + TargetLevel::lte("a",LogLevel::Info), + TargetLevel::not("ab",LogLevel::Trace), + TargetLevel::lte("abc",LogLevel::Trace) + ], "abcdef", Level::TRACE, true)] + #[case(LogLevel::Info, [ + TargetLevel::lte("a",LogLevel::Info), + TargetLevel::lte("abc",LogLevel::Trace), + TargetLevel::not("ab",LogLevel::Trace), + ], "abcdef", Level::TRACE, true)] + fn test_specificity( + #[case] default: LogLevel, + #[case] filter: [TargetLevel; K], + #[case] span_target: &str, + #[case] span_level: Level, + #[case] expect_enabled: bool, + ) { + assert_eq!( + opts(default, filter).test_enabled(span_target, span_level), + expect_enabled + ); + } + + #[rstest::rstest] + #[case(LogLevel::Info, LogLevel::Trace, Level::TRACE, false)] + #[case(LogLevel::Info, LogLevel::Trace, Level::DEBUG, true)] + #[case(LogLevel::Info, LogLevel::Trace, Level::INFO, true)] + #[case(LogLevel::Info, LogLevel::Trace, Level::WARN, true)] + #[case(LogLevel::Info, LogLevel::Trace, Level::ERROR, true)] + fn test_not( + #[case] default: LogLevel, + #[case] target_level: LogLevel, + #[case] span_level: Level, + #[case] expect_enabled: bool, + ) { + assert_eq!( + opts(default, [TargetLevel::not("wick", target_level)]).test_enabled("wick", span_level), + expect_enabled + ); + } + + #[rstest::rstest] + #[case(LogLevel::Info, LogLevel::Trace, Level::TRACE, false)] + #[case(LogLevel::Info, LogLevel::Trace, Level::DEBUG, true)] + #[case(LogLevel::Info, LogLevel::Trace, Level::INFO, true)] + #[case(LogLevel::Info, LogLevel::Trace, Level::WARN, true)] + #[case(LogLevel::Info, LogLevel::Trace, Level::ERROR, true)] + fn test_lt( + #[case] default: LogLevel, + #[case] target_level: LogLevel, + #[case] span_level: Level, + #[case] expect_enabled: bool, + ) { + assert_eq!( + opts(default, [TargetLevel::lt("wick", target_level)]).test_enabled("wick", span_level), + expect_enabled + ); + } + + #[rstest::rstest] + #[case(LogLevel::Info, LogLevel::Trace, Level::TRACE, true)] + #[case(LogLevel::Info, LogLevel::Trace, Level::DEBUG, true)] + #[case(LogLevel::Info, LogLevel::Trace, Level::INFO, true)] + #[case(LogLevel::Info, LogLevel::Trace, Level::WARN, true)] + #[case(LogLevel::Info, LogLevel::Trace, Level::ERROR, true)] + fn test_lte( + #[case] default: LogLevel, + #[case] target_level: LogLevel, + #[case] span_level: Level, + #[case] expect_enabled: bool, + ) { + assert_eq!( + opts(default, [TargetLevel::lte("wick", target_level)]).test_enabled("wick", span_level), + expect_enabled + ); + } + + #[rstest::rstest] + #[case(LogLevel::Info, LogLevel::Info, Level::TRACE, true)] + #[case(LogLevel::Info, LogLevel::Info, Level::DEBUG, true)] + #[case(LogLevel::Info, LogLevel::Info, Level::INFO, true)] + #[case(LogLevel::Info, LogLevel::Info, Level::WARN, false)] + #[case(LogLevel::Info, LogLevel::Info, Level::ERROR, false)] + fn test_gte( + #[case] default: LogLevel, + #[case] target_level: LogLevel, + #[case] span_level: Level, + #[case] expect_enabled: bool, + ) { + assert_eq!( + opts(default, [TargetLevel::gte("wick", target_level)]).test_enabled("wick", span_level), + expect_enabled + ); + } + + #[rstest::rstest] + #[case(LogLevel::Info, LogLevel::Info, Level::TRACE, true)] + #[case(LogLevel::Info, LogLevel::Info, Level::DEBUG, true)] + #[case(LogLevel::Info, LogLevel::Info, Level::INFO, false)] + #[case(LogLevel::Info, LogLevel::Info, Level::WARN, false)] + #[case(LogLevel::Info, LogLevel::Info, Level::ERROR, false)] + fn test_gt( + #[case] default: LogLevel, + #[case] target_level: LogLevel, + #[case] span_level: Level, + #[case] expect_enabled: bool, + ) { + assert_eq!( + opts(default, [TargetLevel::gt("wick", target_level)]).test_enabled("wick", span_level), + expect_enabled + ); + } + + #[rstest::rstest] + #[case(LogLevel::Info, LogLevel::Info, Level::TRACE, false)] + #[case(LogLevel::Info, LogLevel::Info, Level::DEBUG, false)] + #[case(LogLevel::Info, LogLevel::Info, Level::INFO, true)] + #[case(LogLevel::Info, LogLevel::Info, Level::WARN, false)] + #[case(LogLevel::Info, LogLevel::Info, Level::ERROR, false)] + fn test_eq( + #[case] default: LogLevel, + #[case] target_level: LogLevel, + #[case] span_level: Level, + #[case] expect_enabled: bool, + ) { + assert_eq!( + opts(default, [TargetLevel::is("wick", target_level)]).test_enabled("wick", span_level), + expect_enabled + ); + } +}