Skip to main content

conjure_cp_cli/utils/
rule_trace.rs

1//! Shared in-memory rule application counts for CLI and exhaustive traces.
2use std::collections::BTreeMap;
3use std::sync::{Arc, Mutex};
4
5use tracing::field::{Field, Visit};
6use tracing::{Event, Subscriber};
7use tracing_subscriber::{Layer, layer::Context};
8
9/// A clonable trace layer whose counts can be read without file I/O.
10#[derive(Clone, Default)]
11pub struct RuleApplicationCounter {
12    counts: Arc<Mutex<BTreeMap<String, u64>>>,
13}
14
15impl RuleApplicationCounter {
16    /// Snapshot the counts collected by every clone of this layer.
17    pub fn snapshot(&self) -> BTreeMap<String, u64> {
18        self.counts
19            .lock()
20            .expect("rule trace aggregate state lock poisoned")
21            .clone()
22    }
23}
24
25impl<S: Subscriber> Layer<S> for RuleApplicationCounter {
26    fn on_event(&self, event: &Event<'_>, _: Context<'_, S>) {
27        let mut visitor = RuleNameVisitor::default();
28        event.record(&mut visitor);
29        if let Some(rule) = visitor.rule_name {
30            *self
31                .counts
32                .lock()
33                .expect("rule trace aggregate state lock poisoned")
34                .entry(rule)
35                .or_default() += 1;
36        }
37    }
38}
39
40#[derive(Default)]
41struct RuleNameVisitor {
42    rule_name: Option<String>,
43}
44
45impl Visit for RuleNameVisitor {
46    fn record_str(&mut self, field: &Field, value: &str) {
47        if field.name() == "rule_name" {
48            self.rule_name = Some(value.to_owned());
49        }
50    }
51
52    fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
53        if field.name() == "rule_name" && self.rule_name.is_none() {
54            self.rule_name = Some(format!("{value:?}").trim_matches('"').to_owned());
55        }
56    }
57}