use {
super::matcher::{CallsiteMatch, CallsiteMatcher, FieldMatch, Match},
crate::SmallVec,
compact_str::CompactString,
std::{cmp::Ordering, collections::HashMap, fmt},
tracing_core::{LevelFilter, Metadata},
};
#[derive(Debug, PartialEq, Eq)]
pub(super) struct DirectiveSet<T: Ord> {
pub(super) directives: Vec<T>,
pub(super) level: LevelFilter,
}
pub(super) type Dynamics = DirectiveSet<DynamicDirective>;
#[derive(Debug, PartialEq, Eq)]
pub(super) struct DynamicDirective {
pub(super) span: Option<CompactString>,
pub(super) fields: SmallVec<FieldMatch>,
pub(super) target: Option<CompactString>,
pub(super) level: LevelFilter,
}
pub(super) type Statics = DirectiveSet<StaticDirective>;
#[derive(Debug, PartialEq, Eq)]
pub(super) struct StaticDirective {
pub(super) target: Option<CompactString>,
pub(super) fields: SmallVec<CompactString>,
pub(super) level: LevelFilter,
}
impl<T: Ord> DirectiveSet<T> {
fn directives(&self) -> impl Iterator<Item = &T> {
self.directives.iter()
}
fn directives_for<'a>(&'a self, metadata: &'a Metadata<'a>) -> impl Iterator<Item = &'a T> + 'a
where
T: Match,
{
self.directives().filter(move |d| d.cares_about(metadata))
}
pub(super) fn add(&mut self, directive: T)
where
T: Match,
{
let level = *directive.level();
if level > self.level {
self.level = level;
}
let ix = self.directives.binary_search(&directive);
match ix {
Ok(ix) => self.directives[ix] = directive,
Err(ix) => self.directives.insert(ix, directive),
}
}
}
impl<T: Match + Ord> FromIterator<T> for DirectiveSet<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut this = Self::default();
for directive in iter.into_iter() {
this.add(directive)
}
this
}
}
impl<T: Ord> Default for DirectiveSet<T> {
fn default() -> Self {
Self {
directives: Vec::new(),
level: LevelFilter::OFF,
}
}
}
impl DynamicDirective {
fn field_matcher(&self, metadata: &Metadata<'_>) -> Option<CallsiteMatch> {
let fieldset = metadata.fields();
let fields = self
.fields
.iter()
.filter_map(
|FieldMatch {
ref name,
ref value,
}| {
if let Some(field) = fieldset.field(name) {
let value = value.as_ref().cloned()?;
Some(Ok((field, value)))
} else {
Some(Err(()))
}
},
)
.collect::<Result<HashMap<_, _>, ()>>()
.ok()?;
Some(CallsiteMatch {
fields,
level: self.level,
})
}
}
impl Match for DynamicDirective {
fn cares_about(&self, metadata: &Metadata<'_>) -> bool {
if let Some(ref target) = self.target {
if !metadata.target().starts_with(&target[..]) {
return false;
}
}
if let Some(ref name) = self.span {
if &**name != metadata.name() {
return false;
}
}
let fields = metadata.fields();
for field in &self.fields {
if fields.field(&field.name).is_none() {
return false;
}
}
true
}
fn level(&self) -> &LevelFilter {
&self.level
}
}
impl DynamicDirective {
fn is_dynamic(&self) -> bool {
self.span.is_some() || !self.fields.is_empty()
}
fn is_static(&self) -> bool {
self.span.is_none() && self.fields.iter().all(|field| field.value.is_none())
}
fn to_static(&self) -> Option<StaticDirective> {
if self.is_static() {
Some(StaticDirective {
target: self.target.clone(),
fields: self.fields.iter().map(|field| field.name.clone()).collect(),
level: self.level,
})
} else {
None
}
}
pub(super) fn make_tables(directives: Vec<DynamicDirective>) -> (Dynamics, Statics) {
let (dynamics, statics): (Vec<DynamicDirective>, Vec<DynamicDirective>) = directives
.into_iter()
.partition(DynamicDirective::is_dynamic);
let statics = statics
.into_iter()
.filter_map(|d| d.to_static())
.chain(dynamics.iter().filter_map(DynamicDirective::to_static))
.collect();
(Dynamics::from_iter(dynamics), statics)
}
}
impl Match for StaticDirective {
fn cares_about(&self, metadata: &Metadata<'_>) -> bool {
if let Some(ref target) = self.target {
if !metadata.target().starts_with(&target[..]) {
return false;
}
}
if metadata.is_event() && !self.fields.is_empty() {
let fields = metadata.fields();
for name in &self.fields {
if fields.field(name).is_none() {
return false;
}
}
}
true
}
fn level(&self) -> &LevelFilter {
&self.level
}
}
impl Statics {
pub(super) fn enabled(&self, metadata: &Metadata<'_>) -> bool {
let level = metadata.level();
if self.level < *level {
return false;
}
match self.directives_for(metadata).next() {
Some(d) => d.level >= *level,
None => false,
}
}
}
impl Dynamics {
pub(super) fn matcher(&self, metadata: &Metadata<'_>) -> Option<CallsiteMatcher> {
let mut level = None;
let fields = self
.directives_for(metadata)
.filter_map(|d| {
if let Some(f) = d.field_matcher(metadata) {
return Some(f);
}
match level {
Some(ref b) if d.level > *b => level = Some(d.level),
None => level = Some(d.level),
_ => {},
}
None
})
.collect();
if let Some(level) = level {
Some(CallsiteMatcher {
fields,
base_level: level,
})
} else if !fields.is_empty() {
Some(CallsiteMatcher {
fields,
base_level: level.unwrap_or(LevelFilter::OFF),
})
} else {
None
}
}
pub(super) fn has_value_filters(&self) -> bool {
self.directives()
.any(|d| d.fields.iter().any(|f| f.value.is_some()))
}
}
impl Default for StaticDirective {
fn default() -> Self {
Self {
target: None,
fields: SmallVec::new(),
level: LevelFilter::ERROR,
}
}
}
impl PartialOrd for DynamicDirective {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DynamicDirective {
fn cmp(&self, other: &DynamicDirective) -> Ordering {
let ordering = self
.target
.as_ref()
.map(CompactString::len)
.cmp(&other.target.as_ref().map(CompactString::len))
.then_with(|| self.span.is_some().cmp(&other.span.is_some()))
.then_with(|| self.fields.len().cmp(&other.fields.len()))
.then_with(|| {
self.target
.cmp(&other.target)
.then_with(|| self.span.cmp(&other.span))
.then_with(|| self.fields[..].cmp(&other.fields[..]))
})
.reverse();
#[cfg(debug_assertions)]
{
if ordering == Ordering::Equal {
debug_assert_eq!(
self.target, other.target,
"invariant violated: Ordering::Equal must imply a.target == b.target"
);
debug_assert_eq!(
self.span, other.span,
"invariant violated: Ordering::Equal must imply a.span == b.span"
);
debug_assert_eq!(
self.fields, other.fields,
"invariant violated: Ordering::Equal must imply a.fields == b.fields"
);
}
}
ordering
}
}
impl PartialOrd for StaticDirective {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for StaticDirective {
fn cmp(&self, other: &StaticDirective) -> Ordering {
let ordering = self
.target
.as_ref()
.map(CompactString::len)
.cmp(&other.target.as_ref().map(CompactString::len))
.then_with(|| self.fields.len().cmp(&other.fields.len()))
.then_with(|| {
self.target
.cmp(&other.target)
.then_with(|| self.fields[..].cmp(&other.fields[..]))
})
.reverse();
#[cfg(debug_assertions)]
{
if ordering == Ordering::Equal {
debug_assert_eq!(
self.target, other.target,
"invariant violated: Ordering::Equal must imply a.target == b.target"
);
debug_assert_eq!(
self.fields, other.fields,
"invariant violated: Ordering::Equal must imply a.fields == b.fields"
);
}
}
ordering
}
}
impl fmt::Display for DynamicDirective {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut wrote_any = false;
if let Some(ref target) = self.target {
fmt::Display::fmt(target, f)?;
wrote_any = true;
}
if self.span.is_some() || !self.fields.is_empty() {
f.write_str("[")?;
if let Some(ref span) = self.span {
fmt::Display::fmt(span, f)?;
}
let mut fields = self.fields.iter();
if let Some(field) = fields.next() {
write!(f, "{{{}", field)?;
for field in fields {
write!(f, ",{}", field)?;
}
f.write_str("}")?;
}
f.write_str("]")?;
wrote_any = true;
}
if wrote_any {
f.write_str("=")?;
}
fmt::Display::fmt(&self.level, f)
}
}
impl fmt::Display for StaticDirective {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut wrote_any = false;
if let Some(ref target) = self.target {
fmt::Display::fmt(target, f)?;
wrote_any = true;
}
if !self.fields.is_empty() {
f.write_str("[")?;
let mut fields = self.fields.iter();
if let Some(field) = fields.next() {
write!(f, "{{{}", field)?;
for field in fields {
write!(f, ",{}", field)?;
}
f.write_str("}")?;
}
f.write_str("]")?;
wrote_any = true;
}
if wrote_any {
f.write_str("=")?;
}
fmt::Display::fmt(&self.level, f)
}
}