use {
crate::{Diagnostics, DEFAULT_ENV},
compact_str::CompactString,
std::{borrow::Cow, cmp, env, ffi::OsStr, fmt},
tracing_core::{Interest, LevelFilter, Metadata},
tracing_subscriber::layer::Context,
};
mod parse;
#[derive(Debug, Default)]
pub struct Filter {
directives: Vec<Directive>,
regex: Option<regex::Regex>,
}
#[derive(Debug, PartialEq, Eq)]
struct Directive {
target: Option<CompactString>,
level: LevelFilter,
}
impl fmt::Display for Filter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for directive in &*self.directives {
if let Some(target) = &directive.target {
write!(f, "{}=", target)?;
}
write!(f, "{},", directive.level)?;
}
if let Some(regex) = &self.regex {
write!(f, "/{}", regex)?;
}
Ok(())
}
}
impl Filter {
pub fn new(spec: &str) -> Self {
spec.parse().unwrap_or_else(|_| Self::empty())
}
pub const fn empty() -> Self {
Self {
directives: Vec::new(),
regex: None,
}
}
pub fn from_default_env() -> Option<(Self, Option<Diagnostics<'static>>)> {
Self::from_env(DEFAULT_ENV)
}
pub fn from_env(key: impl AsRef<OsStr>) -> Option<(Self, Option<Diagnostics<'static>>)> {
let s = env::var(key).ok()?;
let (filter, err) = Self::parse(&s);
Some((
filter.unwrap_or_default(),
match err {
None => None,
Some(x) => Some({
Diagnostics {
error: x.error,
ignored: x.ignored,
disabled: x.disabled,
source: s.into(),
}
}),
},
))
}
pub fn layer(self) -> crate::FilterLayer<Self> {
crate::FilterLayer::new(self)
}
}
impl Filter {
pub fn add_directive<'a>(
&mut self,
target: Option<impl Into<Cow<'a, str>>>,
level: impl Into<LevelFilter>,
) {
let target = target.map(Into::into).map(Into::into);
let level = level.into();
let directive = Directive { target, level };
let ix = self.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) => self.directives[ix] = directive,
Err(ix) => self.directives.insert(ix, directive),
}
}
pub fn with_directive<'a>(
mut self,
target: Option<impl Into<Cow<'a, str>>>,
level: impl Into<LevelFilter>,
) -> Self {
self.add_directive(target, level);
self
}
pub fn add_level(&mut self, level: impl Into<LevelFilter>) {
self.add_directive(None::<&str>, level);
}
pub fn with_level(mut self, level: impl Into<LevelFilter>) -> Self {
self.add_level(level);
self
}
pub fn add_target<'a>(
&mut self,
target: impl Into<Cow<'a, str>>,
level: impl Into<LevelFilter>,
) {
self.add_directive(Some(target), level);
}
pub fn with_target<'a>(
mut self,
target: impl Into<Cow<'a, str>>,
level: impl Into<LevelFilter>,
) -> Self {
self.add_directive(Some(target), level);
self
}
pub fn add_regex(&mut self, regex: regex::Regex) {
match &self.regex {
Some(_) => panic!("set `tracing_filter::simple::Filter` regex that was already set"),
None => self.regex = Some(regex),
}
}
pub fn with_regex(mut self, regex: regex::Regex) -> Self {
self.add_regex(regex);
self
}
fn is_enabled(&self, metadata: &Metadata<'_>) -> bool {
let level = *metadata.level();
let target = metadata.target();
if self.directives.is_empty() {
return level <= LevelFilter::ERROR;
}
for directive in self.directives.iter().rev() {
match &directive.target {
Some(name) if !target.starts_with(&**name) => {},
Some(..) | None => return level <= directive.level,
}
}
false
}
}
impl<S> crate::Filter<S> for Filter {
fn callsite_enabled(&self, metadata: &Metadata<'_>) -> Interest {
if self.is_enabled(metadata) {
Interest::always()
} else {
Interest::never()
}
}
fn enabled(&self, metadata: &Metadata<'_>, _ctx: &Context<'_, S>) -> bool {
self.is_enabled(metadata)
}
}