Skip to main content

synapse_pingora/waf/
trace.rs

1//! WAF evaluation tracing utilities.
2
3use serde::Serialize;
4
5/// Trace events emitted during WAF evaluation.
6#[derive(Debug, Clone, Serialize)]
7#[serde(tag = "type", rename_all = "snake_case")]
8pub enum TraceEvent {
9    EvaluationStarted {
10        method: String,
11        uri: String,
12        candidate_rules: usize,
13    },
14    RuleStart {
15        rule_id: u32,
16    },
17    ConditionEvaluated {
18        rule_id: u32,
19        kind: String,
20        field: Option<String>,
21        op: Option<String>,
22        name: Option<String>,
23        matched: bool,
24    },
25    RuleEnd {
26        rule_id: u32,
27        matched: bool,
28        risk: f64,
29        blocking: bool,
30    },
31    EvaluationFinished {
32        verdict: String,
33        risk_score: u16,
34        matched_rules: Vec<u32>,
35        timed_out: bool,
36        rules_evaluated: Option<u32>,
37        detection_time_us: u64,
38    },
39    Truncated {
40        limit: usize,
41    },
42}
43
44/// Sink for WAF trace events.
45pub trait TraceSink: Send {
46    fn record(&mut self, event: TraceEvent);
47}
48
49/// Trace state helper to avoid allocations when tracing is disabled.
50pub struct TraceState<'a> {
51    sink: Option<&'a mut dyn TraceSink>,
52}
53
54impl<'a> TraceState<'a> {
55    pub fn enabled(sink: &'a mut dyn TraceSink) -> Self {
56        Self { sink: Some(sink) }
57    }
58
59    pub fn disabled() -> Self {
60        Self { sink: None }
61    }
62
63    pub fn is_enabled(&self) -> bool {
64        self.sink.is_some()
65    }
66
67    pub fn emit(&mut self, event: TraceEvent) {
68        if let Some(sink) = self.sink.as_mut() {
69            sink.record(event);
70        }
71    }
72}