#![allow(clippy::mutable_key_type)]
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{LazyLock, Mutex};
use tracing::Level as TracingLevel;
use crate::Event;
static REGEX_CACHE: LazyLock<Mutex<HashMap<String, Option<Regex>>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
#[derive(Debug, Clone)]
pub enum PatternMatcher {
Exact(String),
Wildcard, Prefix(String), Suffix(String), Contains(String), Regex(Regex),
}
impl PatternMatcher {
pub fn new(pattern: &str) -> Self {
match pattern {
"*" => Self::Wildcard,
p if p.starts_with('*')
&& p.ends_with('*')
&& p.len() > 2
&& !p[1..p.len() - 1].contains('*') =>
{
Self::Contains(p[1..p.len() - 1].to_string())
}
p if p.starts_with('*') && p.len() > 1 && !p[1..].contains('*') => {
Self::Suffix(p[1..].to_string())
}
p if p.ends_with('*') && p.len() > 1 && !p[..p.len() - 1].contains('*') => {
Self::Prefix(p[..p.len() - 1].to_string())
}
p if !p.contains('*') => Self::Exact(p.to_string()),
p => {
let regex_pattern = p.replace("*", ".*");
let full_pattern = format!("^{regex_pattern}$");
let mut cache = REGEX_CACHE.lock().unwrap();
if let Some(cached_regex) = cache.get(&full_pattern) {
if let Some(regex) = cached_regex {
Self::Regex(regex.clone())
} else {
Self::Exact(p.to_string())
}
} else {
match Regex::new(&full_pattern) {
Ok(regex) => {
let result = Self::Regex(regex.clone());
cache.insert(full_pattern, Some(regex));
result
}
Err(_) => {
cache.insert(full_pattern, None);
Self::Exact(p.to_string())
}
}
}
}
}
}
pub fn matches(&self, value: &str) -> bool {
match self {
Self::Exact(pattern) => value == pattern,
Self::Wildcard => true,
Self::Prefix(prefix) => value.starts_with(prefix),
Self::Suffix(suffix) => value.ends_with(suffix),
Self::Contains(substring) => value.contains(substring),
Self::Regex(regex) => regex.is_match(value),
}
}
}
impl PartialEq for PatternMatcher {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Exact(a), Self::Exact(b)) => a == b,
(Self::Wildcard, Self::Wildcard) => true,
(Self::Prefix(a), Self::Prefix(b)) => a == b,
(Self::Suffix(a), Self::Suffix(b)) => a == b,
(Self::Contains(a), Self::Contains(b)) => a == b,
(Self::Regex(a), Self::Regex(b)) => a.as_str() == b.as_str(),
_ => false,
}
}
}
impl std::hash::Hash for PatternMatcher {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
Self::Exact(s) => {
0u8.hash(state);
s.hash(state);
}
Self::Wildcard => 1u8.hash(state),
Self::Prefix(s) => {
2u8.hash(state);
s.hash(state);
}
Self::Suffix(s) => {
3u8.hash(state);
s.hash(state);
}
Self::Contains(s) => {
4u8.hash(state);
s.hash(state);
}
Self::Regex(r) => {
5u8.hash(state);
r.as_str().hash(state);
}
}
}
}
pub fn matches(pattern: &str, value: &str) -> bool {
PatternMatcher::new(pattern).matches(value)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Level(pub TracingLevel);
impl Level {
pub const ERROR: Level = Level(TracingLevel::ERROR);
pub const WARN: Level = Level(TracingLevel::WARN);
pub const INFO: Level = Level(TracingLevel::INFO);
pub const DEBUG: Level = Level(TracingLevel::DEBUG);
pub const TRACE: Level = Level(TracingLevel::TRACE);
pub fn is_trace(&self) -> bool {
self.0 == TracingLevel::TRACE
}
pub fn is_error(&self) -> bool {
self.0 == TracingLevel::ERROR
}
pub fn is_warn(&self) -> bool {
self.0 == TracingLevel::WARN
}
pub fn is_info(&self) -> bool {
self.0 == TracingLevel::INFO
}
pub fn is_debug(&self) -> bool {
self.0 == TracingLevel::DEBUG
}
}
impl Serialize for Level {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s = match self.0 {
TracingLevel::ERROR => "ERROR",
TracingLevel::WARN => "WARN",
TracingLevel::INFO => "INFO",
TracingLevel::DEBUG => "DEBUG",
TracingLevel::TRACE => "TRACE",
};
serializer.serialize_str(s)
}
}
impl<'de> Deserialize<'de> for Level {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let level = match s.to_uppercase().as_str() {
"ERROR" => TracingLevel::ERROR,
"WARN" => TracingLevel::WARN,
"INFO" => TracingLevel::INFO,
"DEBUG" => TracingLevel::DEBUG,
"TRACE" => TracingLevel::TRACE,
_ => {
return Err(serde::de::Error::custom(format!(
"invalid level filter: {s}"
)));
}
};
Ok(Level(level))
}
}
impl std::hash::Hash for Level {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let value = match self.0 {
TracingLevel::ERROR => 1,
TracingLevel::WARN => 2,
TracingLevel::INFO => 3,
TracingLevel::DEBUG => 4,
TracingLevel::TRACE => 5,
};
value.hash(state);
}
}
impl Eq for Level {}
impl std::fmt::Display for Level {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 {
TracingLevel::ERROR => write!(f, "ERROR"),
TracingLevel::WARN => write!(f, "WARN"),
TracingLevel::INFO => write!(f, "INFO"),
TracingLevel::DEBUG => write!(f, "DEBUG"),
TracingLevel::TRACE => write!(f, "TRACE"),
}
}
}
impl From<TracingLevel> for Level {
fn from(filter: TracingLevel) -> Self {
Level(filter)
}
}
impl From<Level> for TracingLevel {
fn from(level: Level) -> Self {
level.0
}
}
#[derive(Debug, Clone)]
pub struct Match {
pub level: Level,
pub include: bool,
pub module_patterns: Vec<String>,
pub file_patterns: Vec<String>,
pub span_patterns: Vec<String>,
pub target_patterns: Vec<String>,
module_matchers: Vec<PatternMatcher>,
file_matchers: Vec<PatternMatcher>,
span_matchers: Vec<PatternMatcher>,
target_matchers: Vec<PatternMatcher>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableMatch {
pub level: Level,
pub include: bool,
pub module_patterns: Vec<String>,
pub file_patterns: Vec<String>,
pub span_patterns: Vec<String>,
pub target_patterns: Vec<String>,
}
impl Serialize for Match {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let serializable = SerializableMatch {
level: self.level,
include: self.include,
module_patterns: self.module_patterns.clone(),
file_patterns: self.file_patterns.clone(),
span_patterns: self.span_patterns.clone(),
target_patterns: self.target_patterns.clone(),
};
serializable.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Match {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let serializable = SerializableMatch::deserialize(deserializer)?;
Ok(Match {
level: serializable.level,
include: serializable.include,
module_patterns: serializable.module_patterns.clone(),
file_patterns: serializable.file_patterns.clone(),
span_patterns: serializable.span_patterns.clone(),
target_patterns: serializable.target_patterns.clone(),
module_matchers: serializable
.module_patterns
.iter()
.map(|p| PatternMatcher::new(p))
.collect(),
file_matchers: serializable
.file_patterns
.iter()
.map(|p| PatternMatcher::new(p))
.collect(),
span_matchers: serializable
.span_patterns
.iter()
.map(|p| PatternMatcher::new(p))
.collect(),
target_matchers: serializable
.target_patterns
.iter()
.map(|p| PatternMatcher::new(p))
.collect(),
})
}
}
impl std::hash::Hash for Match {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.include.hash(state);
self.level.hash(state);
self.module_patterns.hash(state);
self.file_patterns.hash(state);
self.span_patterns.hash(state);
self.target_patterns.hash(state);
}
}
impl Default for Match {
fn default() -> Self {
let module_patterns = vec!["*".to_string()];
Self {
level: Level(TracingLevel::DEBUG),
include: true,
module_matchers: module_patterns
.iter()
.map(|p| PatternMatcher::new(p))
.collect(),
file_matchers: vec![],
span_matchers: vec![],
target_matchers: vec![],
module_patterns,
file_patterns: vec![],
span_patterns: vec![],
target_patterns: vec![],
}
}
}
impl Eq for Match {}
impl PartialEq for Match {
fn eq(&self, other: &Self) -> bool {
self.level == other.level
&& self.include == other.include
&& self.module_patterns == other.module_patterns
&& self.file_patterns == other.file_patterns
&& self.span_patterns == other.span_patterns
&& self.target_patterns == other.target_patterns
}
}
impl Match {
pub fn new(level: impl Into<Level>) -> Self {
Self {
level: level.into(),
include: true,
module_patterns: vec![],
file_patterns: vec![],
span_patterns: vec![],
target_patterns: vec![],
module_matchers: vec![],
file_matchers: vec![],
span_matchers: vec![],
target_matchers: vec![],
}
}
pub fn trace() -> Self {
Self::new(TracingLevel::TRACE)
}
pub fn debug() -> Self {
Self::new(TracingLevel::DEBUG)
}
pub fn info() -> Self {
Self::new(TracingLevel::INFO)
}
pub fn warn() -> Self {
Self::new(TracingLevel::WARN)
}
pub fn error() -> Self {
Self::new(TracingLevel::ERROR)
}
pub fn include(mut self) -> Self {
self.include = true;
self
}
pub fn exclude(mut self) -> Self {
self.include = false;
self
}
pub fn module_patterns(
mut self,
patterns: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
self.module_patterns = patterns.into_iter().map(Into::<String>::into).collect();
self.module_matchers = self
.module_patterns
.iter()
.map(|p| PatternMatcher::new(p))
.collect();
self
}
pub fn module_pattern(mut self, pattern: impl Into<String>) -> Self {
let pattern_str = pattern.into();
self.module_matchers.push(PatternMatcher::new(&pattern_str));
self.module_patterns.push(pattern_str);
self
}
pub fn extend_module_patterns(
mut self,
patterns: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
let new_patterns: Vec<String> = patterns.into_iter().map(Into::<String>::into).collect();
for pattern in &new_patterns {
self.module_matchers.push(PatternMatcher::new(pattern));
}
self.module_patterns.extend(new_patterns);
self
}
pub fn file_patterns(mut self, patterns: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.file_patterns = patterns.into_iter().map(Into::<String>::into).collect();
self.file_matchers = self
.file_patterns
.iter()
.map(|p| PatternMatcher::new(p))
.collect();
self
}
pub fn file_pattern(mut self, pattern: impl Into<String>) -> Self {
let pattern_str = pattern.into();
self.file_matchers.push(PatternMatcher::new(&pattern_str));
self.file_patterns.push(pattern_str);
self
}
pub fn extend_file_patterns(
mut self,
patterns: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
let new_patterns: Vec<String> = patterns.into_iter().map(Into::<String>::into).collect();
for pattern in &new_patterns {
self.file_matchers.push(PatternMatcher::new(pattern));
}
self.file_patterns.extend(new_patterns);
self
}
pub fn span_patterns(mut self, patterns: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.span_patterns = patterns.into_iter().map(Into::<String>::into).collect();
self.span_matchers = self
.span_patterns
.iter()
.map(|p| PatternMatcher::new(p))
.collect();
self
}
pub fn span_pattern(mut self, pattern: impl Into<String>) -> Self {
let pattern_str = pattern.into();
self.span_matchers.push(PatternMatcher::new(&pattern_str));
self.span_patterns.push(pattern_str);
self
}
pub fn extend_span_patterns(
mut self,
patterns: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
let new_patterns: Vec<String> = patterns.into_iter().map(Into::<String>::into).collect();
for pattern in &new_patterns {
self.span_matchers.push(PatternMatcher::new(pattern));
}
self.span_patterns.extend(new_patterns);
self
}
pub fn target_patterns(
mut self,
patterns: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
self.target_patterns = patterns.into_iter().map(Into::<String>::into).collect();
self.target_matchers = self
.target_patterns
.iter()
.map(|p| PatternMatcher::new(p))
.collect();
self
}
pub fn target_pattern(mut self, pattern: impl Into<String>) -> Self {
let pattern_str = pattern.into();
self.target_matchers.push(PatternMatcher::new(&pattern_str));
self.target_patterns.push(pattern_str);
self
}
pub fn extend_target_patterns(
mut self,
patterns: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
let new_patterns: Vec<String> = patterns.into_iter().map(Into::<String>::into).collect();
for pattern in &new_patterns {
self.target_matchers.push(PatternMatcher::new(pattern));
}
self.target_patterns.extend(new_patterns);
self
}
pub fn all_modules(mut self) -> Self {
let pattern = "*".to_string();
self.module_matchers.push(PatternMatcher::new(&pattern));
self.module_patterns.push(pattern);
self
}
pub fn into_matcher_set(self) -> MatcherSet {
MatcherSet::from_matcher(self)
}
pub fn matches(&self, event: &Event) -> bool {
match self.level.0 {
TracingLevel::ERROR => {
if event.level.0 != TracingLevel::ERROR {
return false;
}
}
TracingLevel::WARN => {
if !matches!(event.level.0, TracingLevel::ERROR | TracingLevel::WARN) {
return false;
}
}
TracingLevel::INFO => {
if !matches!(
event.level.0,
TracingLevel::ERROR | TracingLevel::WARN | TracingLevel::INFO
) {
return false;
}
}
TracingLevel::DEBUG => {
if !matches!(
event.level.0,
TracingLevel::ERROR
| TracingLevel::WARN
| TracingLevel::INFO
| TracingLevel::DEBUG
) {
return false;
}
}
TracingLevel::TRACE => {} }
if let Some(module_path) = &event.module_path {
if !self.module_matchers.is_empty() {
let mut module_matched = false;
for matcher in &self.module_matchers {
if matcher.matches(module_path) {
module_matched = true;
break;
}
}
if !module_matched {
return false;
}
}
} else if !self.module_matchers.is_empty() {
let has_wildcard = self
.module_matchers
.iter()
.any(|m| matches!(m, PatternMatcher::Wildcard));
if !has_wildcard {
return false;
}
}
if !self.file_matchers.is_empty() {
let mut file_matched = false;
if let Some(file) = &event.file {
for matcher in &self.file_matchers {
if matcher.matches(file) {
file_matched = true;
break;
}
}
}
if !file_matched {
return false;
}
}
if !self.span_matchers.is_empty() {
let mut span_matched = false;
if let Some(span_name) = &event.span_name {
for matcher in &self.span_matchers {
if matcher.matches(span_name) {
span_matched = true;
break;
}
}
}
if !span_matched {
return false;
}
}
if !self.target_matchers.is_empty() {
let mut target_matched = false;
for matcher in &self.target_matchers {
if matcher.matches(&event.target) {
target_matched = true;
break;
}
}
if !target_matched {
return false;
}
}
true
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MatcherSet {
matchers: std::collections::HashSet<Match>,
}
pub trait IntoMatcherSet {
fn into_matcher_set(self) -> MatcherSet;
}
impl IntoMatcherSet for Match {
fn into_matcher_set(self) -> MatcherSet {
MatcherSet::from_matcher(self)
}
}
impl IntoMatcherSet for MatcherSet {
fn into_matcher_set(self) -> MatcherSet {
self
}
}
impl<const N: usize> IntoMatcherSet for [Match; N] {
fn into_matcher_set(self) -> MatcherSet {
MatcherSet::from_matchers(self)
}
}
impl IntoMatcherSet for Vec<Match> {
fn into_matcher_set(self) -> MatcherSet {
MatcherSet::from_matchers(self)
}
}
impl IntoMatcherSet for &[Match] {
fn into_matcher_set(self) -> MatcherSet {
MatcherSet::from_matchers(self.iter().cloned())
}
}
impl MatcherSet {
pub fn empty() -> Self {
Self {
matchers: std::collections::HashSet::new(),
}
}
pub fn from_matcher(matcher: Match) -> Self {
let mut filter = Self::empty();
filter.matchers.insert(matcher);
filter
}
pub fn from_matchers(matchers: impl IntoIterator<Item = Match>) -> Self {
let mut filter = Self::empty();
for matcher in matchers {
filter.matchers.insert(matcher);
}
filter
}
pub fn with_matcher(mut self, filter: Match) -> Self {
self.matchers.replace(filter);
self
}
pub fn add_matcher(&mut self, filter: Match) {
self.matchers.replace(filter);
}
pub fn remove_matcher(&mut self, filter: &Match) -> bool {
self.matchers.remove(filter)
}
pub fn clear_matchers(&mut self) {
self.matchers.clear();
}
pub fn is_empty(&self) -> bool {
self.matchers.is_empty()
}
pub fn iter_matchers(&self) -> Vec<&Match> {
self.matchers.iter().collect()
}
}
impl From<Match> for MatcherSet {
fn from(val: Match) -> Self {
val.into_matcher_set()
}
}