use emit_core::{
event::ToEvent,
filter::Filter,
props::Props,
runtime::InternalFilter,
value::FromValue,
well_known::{KEY_LVL, LVL_DEBUG, LVL_ERROR, LVL_INFO, LVL_WARN},
};
use crate::value::{ToValue, Value};
use core::{fmt, str::FromStr};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Level {
Debug,
Info,
Warn,
Error,
}
impl Level {
pub fn try_from_str(s: &str) -> Result<Self, ParseLevelError> {
s.parse()
}
pub fn as_str(&self) -> &'static str {
match self {
Level::Info => LVL_INFO,
Level::Error => LVL_ERROR,
Level::Warn => LVL_WARN,
Level::Debug => LVL_DEBUG,
}
}
}
impl Default for Level {
fn default() -> Self {
Level::Info
}
}
impl fmt::Debug for Level {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self)
}
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(feature = "sval")]
impl sval::Value for Level {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
stream.value(self.as_str())
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Level {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.as_str().serialize(serializer)
}
}
impl FromStr for Level {
type Err = ParseLevelError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
let lvl = s.as_bytes();
match lvl.get(0) {
Some(b'I') | Some(b'i') => parse(lvl, b"INFORMATION", Level::Info),
Some(b'D') | Some(b'd') => {
parse(lvl, b"DEBUG", Level::Debug).or_else(|_| parse(lvl, b"DBG", Level::Debug))
}
Some(b'E') | Some(b'e') => parse(lvl, b"ERROR", Level::Error),
Some(b'W') | Some(b'w') => {
parse(lvl, b"WARNING", Level::Warn).or_else(|_| parse(lvl, b"WRN", Level::Warn))
}
Some(_) => Err(ParseLevelError {}),
None => Err(ParseLevelError {}),
}
}
}
fn parse(
mut input: &[u8],
mut expected_uppercase: &[u8],
ok: Level,
) -> Result<Level, ParseLevelError> {
input = &input[1..];
expected_uppercase = &expected_uppercase[1..];
while let Some(b) = input.get(0) {
match b {
b if b.is_ascii_alphabetic() => {
let Some(e) = expected_uppercase.get(0) else {
return Err(ParseLevelError {});
};
if b.to_ascii_uppercase() != *e {
return Err(ParseLevelError {});
}
expected_uppercase = &expected_uppercase[1..];
input = &input[1..];
}
b if b.is_ascii() && !b.is_ascii_control() => break,
_ => {
return Err(ParseLevelError {});
}
}
}
Ok(ok)
}
#[derive(Debug)]
pub struct ParseLevelError {}
impl fmt::Display for ParseLevelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "the input was not a valid level")
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseLevelError {}
impl ToValue for Level {
fn to_value(&self) -> Value<'_> {
Value::capture_display(self)
}
}
impl<'v> FromValue<'v> for Level {
fn from_value(value: Value<'v>) -> Option<Self> {
value
.downcast_ref::<Level>()
.copied()
.or_else(|| value.parse())
}
}
pub fn min_filter(min: Level) -> MinLevelFilter {
MinLevelFilter::new(min)
}
#[derive(Debug)]
pub struct MinLevelFilter<L = Level> {
min: L,
default: Option<L>,
}
impl From<Level> for MinLevelFilter {
fn from(min: Level) -> Self {
MinLevelFilter::new(min)
}
}
impl<L> MinLevelFilter<L> {
pub const fn new(min: L) -> MinLevelFilter<L> {
MinLevelFilter { min, default: None }
}
pub fn treat_unleveled_as(mut self, default: L) -> Self {
self.default = Some(default);
self
}
}
impl<L: for<'a> FromValue<'a> + Ord + Default> Filter for MinLevelFilter<L> {
fn matches<E: ToEvent>(&self, evt: E) -> bool {
evt.to_event()
.props()
.pull::<L, _>(KEY_LVL)
.as_ref()
.or_else(|| self.default.as_ref())
.unwrap_or(&L::default())
>= &self.min
}
}
impl InternalFilter for MinLevelFilter {}
#[cfg(feature = "alloc")]
mod alloc_support {
use super::*;
use alloc::vec::Vec;
use emit_core::path::Path;
use emit_core::str::Str;
pub fn min_by_path_filter<P: Into<Path<'static>>, L: Into<MinLevelFilter>>(
levels: impl IntoIterator<Item = (P, L)>,
) -> MinLevelPathMap {
MinLevelPathMap::from_iter(levels)
}
#[derive(Debug)]
pub struct MinLevelPathMap<L = Level> {
root: PathNode<L>,
}
#[derive(Debug)]
struct PathNode<L> {
min_level: Option<MinLevelFilter<L>>,
children: Vec<(Str<'static>, PathNode<L>)>,
}
impl<L> MinLevelPathMap<L> {
pub const fn new() -> Self {
MinLevelPathMap {
root: PathNode {
min_level: None,
children: Vec::new(),
},
}
}
pub fn default_min_level(&mut self, min_level: impl Into<MinLevelFilter<L>>) -> &mut Self {
self.root.min_level = Some(min_level.into());
self
}
pub fn min_level(
&mut self,
path: impl Into<Path<'static>>,
min_level: impl Into<MinLevelFilter<L>>,
) -> &mut Self {
let path = path.into();
let mut node = &mut self.root;
for segment in path.segments() {
node = match node
.children
.binary_search_by_key(&segment, |(key, _)| key.by_ref())
{
Ok(idx) => &mut node.children[idx].1,
Err(idx) => {
node.children.insert(
idx,
(
segment.to_owned(),
PathNode {
min_level: None,
children: Vec::new(),
},
),
);
&mut node.children[idx].1
}
};
}
node.min_level = Some(min_level.into());
self
}
}
impl<L: for<'a> FromValue<'a> + Ord + Default> Filter for MinLevelPathMap<L> {
fn matches<E: ToEvent>(&self, evt: E) -> bool {
let evt = evt.to_event();
let path = evt.mdl();
let mut node = &self.root;
let mut filter = self.root.min_level.as_ref();
for segment in path.segments() {
let Ok(idx) = node
.children
.binary_search_by_key(&segment, |(key, _)| key.by_ref())
else {
break;
};
node = &node.children[idx].1;
filter = node.min_level.as_ref().or(filter);
}
filter.matches(evt)
}
}
impl InternalFilter for MinLevelPathMap {}
impl<L, P: Into<Path<'static>>, T: Into<MinLevelFilter<L>>> FromIterator<(P, T)>
for MinLevelPathMap<L>
{
fn from_iter<I: IntoIterator<Item = (P, T)>>(iter: I) -> Self {
let mut map = MinLevelPathMap::new();
for (path, min_level) in iter {
map.min_level(path, min_level);
}
map
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn min_level() {
let mut filter = MinLevelPathMap::new();
filter.min_level(Path::new_raw("a"), Level::Error);
assert!(!filter.matches(crate::Event::new(
Path::new_raw("a"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, Level::Warn),
)));
assert!(filter.matches(crate::Event::new(
Path::new_raw("a"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, Level::Error),
)));
}
#[test]
fn min_level_default() {
let mut filter = MinLevelPathMap::new();
filter.default_min_level(Level::Error);
assert!(!filter.matches(crate::Event::new(
Path::new_raw("a"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, Level::Warn),
)));
assert!(filter.matches(crate::Event::new(
Path::new_raw("a"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, Level::Error),
)));
}
#[test]
fn min_level_child() {
let mut filter = MinLevelPathMap::new();
filter
.min_level(Path::new_raw("a"), Level::Error)
.min_level(Path::new_raw("a::b::c"), Level::Warn);
assert!(!filter.matches(crate::Event::new(
Path::new_raw("a"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, Level::Warn),
)));
assert!(!filter.matches(crate::Event::new(
Path::new_raw("a::b"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, Level::Warn),
)));
assert!(filter.matches(crate::Event::new(
Path::new_raw("a::b::c"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, Level::Warn),
)));
}
#[test]
fn min_level_unmatched() {
let mut filter = MinLevelPathMap::new();
filter.min_level(Path::new_raw("a"), Level::Error);
assert!(filter.matches(crate::Event::new(
Path::new_raw("b"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, Level::Warn),
)));
}
#[test]
fn min_level_default_unmatched() {
let mut filter = MinLevelPathMap::new();
filter
.default_min_level(Level::Error)
.min_level(Path::new_raw("a"), Level::Error);
assert!(!filter.matches(crate::Event::new(
Path::new_raw("b"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, Level::Warn),
)));
}
#[test]
fn min_level_integer() {
let mut filter = MinLevelPathMap::<u8>::new();
filter
.default_min_level(MinLevelFilter::new(3))
.min_level(Path::new_raw("a"), MinLevelFilter::new(1));
assert!(filter.matches(crate::Event::new(
Path::new_raw("a"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, 2),
)));
assert!(filter.matches(crate::Event::new(
Path::new_raw("b"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, 6),
)));
assert!(!filter.matches(crate::Event::new(
Path::new_raw("a"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, 0),
)));
assert!(!filter.matches(crate::Event::new(
Path::new_raw("b"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, 2),
)));
}
}
}
#[cfg(feature = "alloc")]
pub use self::alloc_support::*;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse() {
for (case, expected) in [
("d", Ok(Level::Debug)),
("dbg", Ok(Level::Debug)),
("debug", Ok::<Level, ParseLevelError>(Level::Debug)),
("i", Ok(Level::Info)),
("inf", Ok(Level::Info)),
("info", Ok(Level::Info)),
("information", Ok(Level::Info)),
("w", Ok(Level::Warn)),
("wrn", Ok(Level::Warn)),
("warn", Ok(Level::Warn)),
("warning", Ok(Level::Warn)),
("e", Ok(Level::Error)),
("err", Ok(Level::Error)),
("error", Ok(Level::Error)),
("INFO3", Ok(Level::Info)),
("WRN(x)", Ok(Level::Warn)),
("info warn", Ok(Level::Info)),
("", Err(ParseLevelError {})),
("ifo", Err(ParseLevelError {})),
("trace", Err(ParseLevelError {})),
("erroneous", Err(ParseLevelError {})),
("infoℹ️", Err(ParseLevelError {})),
] {
match expected {
Ok(expected) => {
assert_eq!(expected, Level::from_str(case).unwrap());
assert_eq!(expected, Level::from_str(&case.to_uppercase()).unwrap());
assert_eq!(expected, Level::from_str(&format!(" {case} ")).unwrap());
}
Err(expected) => assert_eq!(
expected.to_string(),
Level::from_str(case).unwrap_err().to_string()
),
}
}
}
#[test]
fn roundtrip() {
for lvl in [Level::Info, Level::Debug, Level::Warn, Level::Error] {
for fmt in [lvl.to_string(), format!("{:?}", lvl)] {
let parsed: Level = fmt.parse().unwrap();
assert_eq!(lvl, parsed, "{}", fmt);
}
}
}
#[cfg(feature = "sval")]
#[test]
fn level_stream() {
sval_test::assert_tokens(
&Level::Warn,
&[
sval_test::Token::TextBegin(Some(4)),
sval_test::Token::TextFragment("warn"),
sval_test::Token::TextEnd,
],
);
}
#[cfg(feature = "serde")]
#[test]
fn level_serialize() {
serde_test::assert_ser_tokens(&Level::Warn, &[serde_test::Token::Str("warn")]);
}
#[test]
fn to_from_value() {
for case in [Level::Debug, Level::Info, Level::Warn, Level::Error] {
let value = case.to_value();
assert_eq!(case, value.cast::<Level>().unwrap());
let formatted = case.to_string();
let value = Value::from(&*formatted);
assert_eq!(case, value.cast::<Level>().unwrap());
}
}
#[test]
fn min_level_filter() {
let filter = MinLevelFilter::new(Level::Warn);
assert!(filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, LVL_ERROR),
)));
assert!(filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, LVL_WARN),
)));
assert!(!filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
crate::Empty,
)));
assert!(!filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, LVL_DEBUG),
)));
assert!(!filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, LVL_INFO),
)));
}
#[test]
fn min_level_filter_with_default() {
let filter = MinLevelFilter::new(Level::Info).treat_unleveled_as(Level::Info);
assert!(filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, LVL_ERROR),
)));
assert!(filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, LVL_WARN),
)));
assert!(filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, LVL_INFO),
)));
assert!(filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
crate::Empty,
)));
assert!(!filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, LVL_DEBUG),
)));
}
#[test]
fn min_level_filter_integer() {
let filter = MinLevelFilter::new(3).treat_unleveled_as(1);
assert!(filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, 3),
)));
assert!(filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, 4),
)));
assert!(!filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
(KEY_LVL, 2),
)));
assert!(!filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
crate::Empty,
)));
}
}