use {
super::{Directive, Filter},
crate::Diagnostics,
miette::{Diagnostic, SourceSpan},
std::{cmp, str::FromStr},
thiserror::Error,
tracing_core::LevelFilter,
};
impl Filter {
pub fn parse(spec: &str) -> (Option<Filter>, Option<Diagnostics<'_>>) {
let recover_span = |substr: &str| {
let offset = substr.as_ptr() as usize - spec.as_ptr() as usize;
offset..offset + substr.len()
};
let mut directives = Vec::new();
let mut parts = spec.split('/');
let dirs = parts.next();
let regex = parts.next();
if let Some(after) = parts.next() {
let regex = recover_span(regex.unwrap());
let after = recover_span(after);
let error = Error::MultipleSlash {
slash: (after.start - 1..after.start).into(),
regex: (regex.start..spec.len()).into(),
};
return (
None,
Some(Diagnostics {
error: Some(Box::new(error)),
ignored: Vec::new(),
disabled: None,
source: spec.into(),
}),
);
}
let mut warnings = Vec::new();
if let Some(dirs) = dirs {
for dir in dirs.split(',').map(|dir| dir.trim()) {
if dir.is_empty() {
continue;
}
let mut parts = dir.split('=');
let (log_level, name) =
match (parts.next(), parts.next().map(str::trim), parts.next()) {
(Some(part0), None, None) => {
match part0.parse() {
Ok(num) => (num, None),
Err(_) => (LevelFilter::TRACE, Some(part0)),
}
},
(Some(part0), Some(""), None) => (LevelFilter::TRACE, Some(part0)),
(Some(part0), Some(part1), None) => match part1.parse() {
Ok(num) => (num, Some(part0)),
_ => {
warnings.push(Warning::InvalidLevel {
span: recover_span(part1).into(),
});
continue;
},
},
(Some(_part0), Some(part1), Some(_part2)) => {
let part1 = recover_span(part1);
let dir = recover_span(dir);
warnings.push(Warning::InvalidLevel {
span: (part1.start..dir.end).into(),
});
continue;
},
_ => unreachable!(),
};
let directive = Directive {
target: name.map(Into::into),
level: log_level,
};
let ix = directives.binary_search_by(|x: &Directive| {
let a = x.target.as_ref().map(|x| x.len()).unwrap_or(0);
let b = directive.target.as_ref().map(|x| x.len()).unwrap_or(0);
match a.cmp(&b) {
cmp::Ordering::Equal => x.target.cmp(&directive.target),
ordering => ordering,
}
});
match ix {
Ok(ix) => directives[ix] = directive,
Err(ix) => directives.insert(ix, directive),
}
}
}
let regex = regex.and_then(|regex| match regex::Regex::new(regex) {
Ok(regex) => Some(regex),
Err(error) => {
warnings.push(Warning::InvalidRegex {
error,
span: recover_span(regex).into(),
});
None
},
});
let _ = regex; let filter = Some(Filter { directives, regex });
let report = if warnings.is_empty() {
None
} else {
Some(Diagnostics {
error: None,
ignored: warnings
.into_iter()
.map(|x| Box::new(x) as Box<dyn Diagnostic + Send + Sync + 'static>)
.collect(),
disabled: None,
source: spec.into(),
})
};
(filter, report)
}
}
impl FromStr for Filter {
type Err = Diagnostics<'static>;
fn from_str(spec: &str) -> Result<Self, Diagnostics<'static>> {
let (filter, errs) = Self::parse(spec);
filter.ok_or_else(|| {
errs.expect("filter compilation failed without any diagnostics")
.into_owned()
})
}
}
#[derive(Debug, Error, Diagnostic)]
#[diagnostic(severity(warning))]
enum Warning {
#[error("invalid level filter specified")]
#[diagnostic(help("valid level filters are OFF, ERROR, WARN, INFO, DEBUG, or TRACE"))]
InvalidLevel {
#[label]
span: SourceSpan,
},
#[error("invalid regex specified")]
#[diagnostic(code(tracing_filter::simple::InvalidRegex))]
InvalidRegex {
error: regex::Error,
#[label("{}", .error)]
span: SourceSpan,
},
}
#[derive(Debug, Error, Diagnostic)]
#[diagnostic(severity(error))]
enum Error {
#[error("logging spec has too many `/`s")]
#[diagnostic(help("regex filters may not contain `/`"))]
MultipleSlash {
#[label("this `/` is not allowed ...")]
slash: SourceSpan,
#[label("... in this regex filter")]
regex: SourceSpan,
},
}