blob: 1394fd04a6229003fda7777e837f1bc11d4b3860 [file] [log] [blame]
use matchers::Pattern;
use std::{
cmp::Ordering,
error::Error,
fmt::{self, Write},
str::FromStr,
sync::{
atomic::{AtomicBool, Ordering::*},
Arc,
},
};
use super::{FieldMap, LevelFilter};
use tracing_core::field::{Field, Visit};
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Match {
pub(crate) name: String, // TODO: allow match patterns for names?
pub(crate) value: Option<ValueMatch>,
}
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct CallsiteMatch {
pub(crate) fields: FieldMap<ValueMatch>,
pub(crate) level: LevelFilter,
}
#[derive(Debug)]
pub(crate) struct SpanMatch {
fields: FieldMap<(ValueMatch, AtomicBool)>,
level: LevelFilter,
has_matched: AtomicBool,
}
pub(crate) struct MatchVisitor<'a> {
inner: &'a SpanMatch,
}
#[derive(Debug, Clone)]
pub(crate) enum ValueMatch {
/// Matches a specific `bool` value.
Bool(bool),
/// Matches a specific `f64` value.
F64(f64),
/// Matches a specific `u64` value.
U64(u64),
/// Matches a specific `i64` value.
I64(i64),
/// Matches any `NaN` `f64` value.
NaN,
/// Matches any field whose `fmt::Debug` output is equal to a fixed string.
Debug(MatchDebug),
/// Matches any field whose `fmt::Debug` output matches a regular expression
/// pattern.
Pat(Box<MatchPattern>),
}
impl Eq for ValueMatch {}
impl PartialEq for ValueMatch {
fn eq(&self, other: &Self) -> bool {
use ValueMatch::*;
match (self, other) {
(Bool(a), Bool(b)) => a.eq(b),
(F64(a), F64(b)) => {
debug_assert!(!a.is_nan());
debug_assert!(!b.is_nan());
a.eq(b)
}
(U64(a), U64(b)) => a.eq(b),
(I64(a), I64(b)) => a.eq(b),
(NaN, NaN) => true,
(Pat(a), Pat(b)) => a.eq(b),
_ => false,
}
}
}
impl Ord for ValueMatch {
fn cmp(&self, other: &Self) -> Ordering {
use ValueMatch::*;
match (self, other) {
(Bool(this), Bool(that)) => this.cmp(that),
(Bool(_), _) => Ordering::Less,
(F64(this), F64(that)) => this
.partial_cmp(that)
.expect("`ValueMatch::F64` may not contain `NaN` values"),
(F64(_), Bool(_)) => Ordering::Greater,
(F64(_), _) => Ordering::Less,
(NaN, NaN) => Ordering::Equal,
(NaN, Bool(_)) | (NaN, F64(_)) => Ordering::Greater,
(NaN, _) => Ordering::Less,
(U64(this), U64(that)) => this.cmp(that),
(U64(_), Bool(_)) | (U64(_), F64(_)) | (U64(_), NaN) => Ordering::Greater,
(U64(_), _) => Ordering::Less,
(I64(this), I64(that)) => this.cmp(that),
(I64(_), Bool(_)) | (I64(_), F64(_)) | (I64(_), NaN) | (I64(_), U64(_)) => {
Ordering::Greater
}
(I64(_), _) => Ordering::Less,
(Pat(this), Pat(that)) => this.cmp(that),
(Pat(_), _) => Ordering::Greater,
(Debug(this), Debug(that)) => this.cmp(that),
(Debug(_), _) => Ordering::Greater,
}
}
}
impl PartialOrd for ValueMatch {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
/// Matches a field's `fmt::Debug` output against a regular expression pattern.
///
/// This is used for matching all non-literal field value filters when regular
/// expressions are enabled.
#[derive(Debug, Clone)]
pub(crate) struct MatchPattern {
pub(crate) matcher: Pattern,
pattern: Arc<str>,
}
/// Matches a field's `fmt::Debug` output against a fixed string pattern.
///
/// This is used for matching all non-literal field value filters when regular
/// expressions are disabled.
#[derive(Debug, Clone)]
pub(crate) struct MatchDebug {
pattern: Arc<str>,
}
/// Indicates that a field name specified in a filter directive was invalid.
#[derive(Clone, Debug)]
#[cfg_attr(docsrs, doc(cfg(feature = "env-filter")))]
pub struct BadName {
name: String,
}
// === impl Match ===
impl Match {
pub(crate) fn has_value(&self) -> bool {
self.value.is_some()
}
// TODO: reference count these strings?
pub(crate) fn name(&self) -> String {
self.name.clone()
}
pub(crate) fn parse(s: &str, regex: bool) -> Result<Self, Box<dyn Error + Send + Sync>> {
let mut parts = s.split('=');
let name = parts
.next()
.ok_or_else(|| BadName {
name: "".to_string(),
})?
// TODO: validate field name
.to_string();
let value = parts
.next()
.map(|part| match regex {
true => ValueMatch::parse_regex(part),
false => Ok(ValueMatch::parse_non_regex(part)),
})
.transpose()?;
Ok(Match { name, value })
}
}
impl fmt::Display for Match {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.name, f)?;
if let Some(ref value) = self.value {
write!(f, "={}", value)?;
}
Ok(())
}
}
impl Ord for Match {
fn cmp(&self, other: &Self) -> Ordering {
// Ordering for `Match` directives is based first on _whether_ a value
// is matched or not. This is semantically meaningful --- we would
// prefer to check directives that match values first as they are more
// specific.
let has_value = match (self.value.as_ref(), other.value.as_ref()) {
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
_ => Ordering::Equal,
};
// If both directives match a value, we fall back to the field names in
// length + lexicographic ordering, and if these are equal as well, we
// compare the match directives.
//
// This ordering is no longer semantically meaningful but is necessary
// so that the directives can be stored in the `BTreeMap` in a defined
// order.
has_value
.then_with(|| self.name.cmp(&other.name))
.then_with(|| self.value.cmp(&other.value))
}
}
impl PartialOrd for Match {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
// === impl ValueMatch ===
fn value_match_f64(v: f64) -> ValueMatch {
if v.is_nan() {
ValueMatch::NaN
} else {
ValueMatch::F64(v)
}
}
impl ValueMatch {
/// Parse a `ValueMatch` that will match `fmt::Debug` fields using regular
/// expressions.
///
/// This returns an error if the string didn't contain a valid `bool`,
/// `u64`, `i64`, or `f64` literal, and couldn't be parsed as a regular
/// expression.
fn parse_regex(s: &str) -> Result<Self, matchers::Error> {
s.parse::<bool>()
.map(ValueMatch::Bool)
.or_else(|_| s.parse::<u64>().map(ValueMatch::U64))
.or_else(|_| s.parse::<i64>().map(ValueMatch::I64))
.or_else(|_| s.parse::<f64>().map(value_match_f64))
.or_else(|_| {
s.parse::<MatchPattern>()
.map(|p| ValueMatch::Pat(Box::new(p)))
})
}
/// Parse a `ValueMatch` that will match `fmt::Debug` against a fixed
/// string.
///
/// This does *not* return an error, because any string that isn't a valid
/// `bool`, `u64`, `i64`, or `f64` literal is treated as expected
/// `fmt::Debug` output.
fn parse_non_regex(s: &str) -> Self {
s.parse::<bool>()
.map(ValueMatch::Bool)
.or_else(|_| s.parse::<u64>().map(ValueMatch::U64))
.or_else(|_| s.parse::<i64>().map(ValueMatch::I64))
.or_else(|_| s.parse::<f64>().map(value_match_f64))
.unwrap_or_else(|_| ValueMatch::Debug(MatchDebug::new(s)))
}
}
impl fmt::Display for ValueMatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ValueMatch::Bool(ref inner) => fmt::Display::fmt(inner, f),
ValueMatch::F64(ref inner) => fmt::Display::fmt(inner, f),
ValueMatch::NaN => fmt::Display::fmt(&std::f64::NAN, f),
ValueMatch::I64(ref inner) => fmt::Display::fmt(inner, f),
ValueMatch::U64(ref inner) => fmt::Display::fmt(inner, f),
ValueMatch::Debug(ref inner) => fmt::Display::fmt(inner, f),
ValueMatch::Pat(ref inner) => fmt::Display::fmt(inner, f),
}
}
}
// === impl MatchPattern ===
impl FromStr for MatchPattern {
type Err = matchers::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let matcher = s.parse::<Pattern>()?;
Ok(Self {
matcher,
pattern: s.to_owned().into(),
})
}
}
impl fmt::Display for MatchPattern {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&*self.pattern, f)
}
}
impl AsRef<str> for MatchPattern {
#[inline]
fn as_ref(&self) -> &str {
self.pattern.as_ref()
}
}
impl MatchPattern {
#[inline]
fn str_matches(&self, s: &impl AsRef<str>) -> bool {
self.matcher.matches(s)
}
#[inline]
fn debug_matches(&self, d: &impl fmt::Debug) -> bool {
self.matcher.debug_matches(d)
}
pub(super) fn into_debug_match(self) -> MatchDebug {
MatchDebug {
pattern: self.pattern,
}
}
}
impl PartialEq for MatchPattern {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.pattern == other.pattern
}
}
impl Eq for MatchPattern {}
impl PartialOrd for MatchPattern {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.pattern.cmp(&other.pattern))
}
}
impl Ord for MatchPattern {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.pattern.cmp(&other.pattern)
}
}
// === impl MatchDebug ===
impl MatchDebug {
fn new(s: &str) -> Self {
Self {
pattern: s.to_owned().into(),
}
}
#[inline]
fn debug_matches(&self, d: &impl fmt::Debug) -> bool {
// Naively, we would probably match a value's `fmt::Debug` output by
// formatting it to a string, and then checking if the string is equal
// to the expected pattern. However, this would require allocating every
// time we want to match a field value against a `Debug` matcher, which
// can be avoided.
//
// Instead, we implement `fmt::Write` for a type that, rather than
// actually _writing_ the strings to something, matches them against the
// expected pattern, and returns an error if the pattern does not match.
struct Matcher<'a> {
pattern: &'a str,
}
impl fmt::Write for Matcher<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
// If the string is longer than the remaining expected string,
// we know it won't match, so bail.
if s.len() > self.pattern.len() {
return Err(fmt::Error);
}
// If the expected string begins with the string that was
// written, we are still potentially a match. Advance the
// position in the expected pattern to chop off the matched
// output, and continue.
if self.pattern.starts_with(s) {
self.pattern = &self.pattern[s.len()..];
return Ok(());
}
// Otherwise, the expected string doesn't include the string
// that was written at the current position, so the `fmt::Debug`
// output doesn't match! Return an error signalling that this
// doesn't match.
Err(fmt::Error)
}
}
let mut matcher = Matcher {
pattern: &self.pattern,
};
// Try to "write" the value's `fmt::Debug` output to a `Matcher`. This
// returns an error if the `fmt::Debug` implementation wrote any
// characters that did not match the expected pattern.
write!(matcher, "{:?}", d).is_ok()
}
}
impl fmt::Display for MatchDebug {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&*self.pattern, f)
}
}
impl AsRef<str> for MatchDebug {
#[inline]
fn as_ref(&self) -> &str {
self.pattern.as_ref()
}
}
impl PartialEq for MatchDebug {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.pattern == other.pattern
}
}
impl Eq for MatchDebug {}
impl PartialOrd for MatchDebug {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.pattern.cmp(&other.pattern))
}
}
impl Ord for MatchDebug {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.pattern.cmp(&other.pattern)
}
}
// === impl BadName ===
impl Error for BadName {}
impl fmt::Display for BadName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid field name `{}`", self.name)
}
}
impl CallsiteMatch {
pub(crate) fn to_span_match(&self) -> SpanMatch {
let fields = self
.fields
.iter()
.map(|(k, v)| (k.clone(), (v.clone(), AtomicBool::new(false))))
.collect();
SpanMatch {
fields,
level: self.level,
has_matched: AtomicBool::new(false),
}
}
}
impl SpanMatch {
pub(crate) fn visitor(&self) -> MatchVisitor<'_> {
MatchVisitor { inner: self }
}
#[inline]
pub(crate) fn is_matched(&self) -> bool {
if self.has_matched.load(Acquire) {
return true;
}
self.is_matched_slow()
}
#[inline(never)]
fn is_matched_slow(&self) -> bool {
let matched = self
.fields
.values()
.all(|(_, matched)| matched.load(Acquire));
if matched {
self.has_matched.store(true, Release);
}
matched
}
#[inline]
pub(crate) fn filter(&self) -> Option<LevelFilter> {
if self.is_matched() {
Some(self.level)
} else {
None
}
}
}
impl<'a> Visit for MatchVisitor<'a> {
fn record_f64(&mut self, field: &Field, value: f64) {
match self.inner.fields.get(field) {
Some((ValueMatch::NaN, ref matched)) if value.is_nan() => {
matched.store(true, Release);
}
Some((ValueMatch::F64(ref e), ref matched))
if (value - *e).abs() < std::f64::EPSILON =>
{
matched.store(true, Release);
}
_ => {}
}
}
fn record_i64(&mut self, field: &Field, value: i64) {
use std::convert::TryInto;
match self.inner.fields.get(field) {
Some((ValueMatch::I64(ref e), ref matched)) if value == *e => {
matched.store(true, Release);
}
Some((ValueMatch::U64(ref e), ref matched)) if Ok(value) == (*e).try_into() => {
matched.store(true, Release);
}
_ => {}
}
}
fn record_u64(&mut self, field: &Field, value: u64) {
match self.inner.fields.get(field) {
Some((ValueMatch::U64(ref e), ref matched)) if value == *e => {
matched.store(true, Release);
}
_ => {}
}
}
fn record_bool(&mut self, field: &Field, value: bool) {
match self.inner.fields.get(field) {
Some((ValueMatch::Bool(ref e), ref matched)) if value == *e => {
matched.store(true, Release);
}
_ => {}
}
}
fn record_str(&mut self, field: &Field, value: &str) {
match self.inner.fields.get(field) {
Some((ValueMatch::Pat(ref e), ref matched)) if e.str_matches(&value) => {
matched.store(true, Release);
}
Some((ValueMatch::Debug(ref e), ref matched)) if e.debug_matches(&value) => {
matched.store(true, Release)
}
_ => {}
}
}
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
match self.inner.fields.get(field) {
Some((ValueMatch::Pat(ref e), ref matched)) if e.debug_matches(&value) => {
matched.store(true, Release);
}
Some((ValueMatch::Debug(ref e), ref matched)) if e.debug_matches(&value) => {
matched.store(true, Release)
}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug)]
#[allow(dead_code)]
struct MyStruct {
answer: usize,
question: &'static str,
}
#[test]
fn debug_struct_match() {
let my_struct = MyStruct {
answer: 42,
question: "life, the universe, and everything",
};
let pattern = "MyStruct { answer: 42, question: \"life, the universe, and everything\" }";
assert_eq!(
format!("{:?}", my_struct),
pattern,
"`MyStruct`'s `Debug` impl doesn't output the expected string"
);
let matcher = MatchDebug {
pattern: pattern.into(),
};
assert!(matcher.debug_matches(&my_struct))
}
#[test]
fn debug_struct_not_match() {
let my_struct = MyStruct {
answer: 42,
question: "what shall we have for lunch?",
};
let pattern = "MyStruct { answer: 42, question: \"life, the universe, and everything\" }";
assert_eq!(
format!("{:?}", my_struct),
"MyStruct { answer: 42, question: \"what shall we have for lunch?\" }",
"`MyStruct`'s `Debug` impl doesn't output the expected string"
);
let matcher = MatchDebug {
pattern: pattern.into(),
};
assert!(!matcher.debug_matches(&my_struct))
}
}