1
//! Shared in-memory rule application counts for CLI and exhaustive traces.
2
use std::collections::BTreeMap;
3
use std::sync::{Arc, Mutex};
4

            
5
use tracing::field::{Field, Visit};
6
use tracing::{Event, Subscriber};
7
use tracing_subscriber::{Layer, layer::Context};
8

            
9
/// A clonable trace layer whose counts can be read without file I/O.
10
#[derive(Clone, Default)]
11
pub struct RuleApplicationCounter {
12
    counts: Arc<Mutex<BTreeMap<String, u64>>>,
13
}
14

            
15
impl RuleApplicationCounter {
16
    /// Snapshot the counts collected by every clone of this layer.
17
36
    pub fn snapshot(&self) -> BTreeMap<String, u64> {
18
36
        self.counts
19
36
            .lock()
20
36
            .expect("rule trace aggregate state lock poisoned")
21
36
            .clone()
22
36
    }
23
}
24

            
25
impl<S: Subscriber> Layer<S> for RuleApplicationCounter {
26
1931401
    fn on_event(&self, event: &Event<'_>, _: Context<'_, S>) {
27
1931401
        let mut visitor = RuleNameVisitor::default();
28
1931401
        event.record(&mut visitor);
29
1931401
        if let Some(rule) = visitor.rule_name {
30
1931401
            *self
31
1931401
                .counts
32
1931401
                .lock()
33
1931401
                .expect("rule trace aggregate state lock poisoned")
34
1931401
                .entry(rule)
35
1931401
                .or_default() += 1;
36
        }
37
1931401
    }
38
}
39

            
40
#[derive(Default)]
41
struct RuleNameVisitor {
42
    rule_name: Option<String>,
43
}
44

            
45
impl Visit for RuleNameVisitor {
46
7621744
    fn record_str(&mut self, field: &Field, value: &str) {
47
7621744
        if field.name() == "rule_name" {
48
7621744
            self.rule_name = Some(value.to_owned());
49
        }
50
7621744
    }
51

            
52
7621744
    fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
53
7621744
        if field.name() == "rule_name" && self.rule_name.is_none() {
54
            self.rule_name = Some(format!("{value:?}").trim_matches('"').to_owned());
55
7621744
        }
56
7621744
    }
57
}