Skip to main content

sz_rust_orm_facade/data_scope/
metrics.rs

1//! Data Scope 指标采集 — DataScopeMetrics
2//!
3//! 使用 tracing 日志记录指标(对齐 observability 模块的轻量级方案)。
4//! 生产环境可通过 tracing subscriber 导出至 Prometheus。
5
6use std::sync::atomic::{AtomicU64, Ordering};
7
8/// Data Scope 指标
9pub struct DataScopeMetrics {
10    hit_total: AtomicU64,
11    bypass_total: AtomicU64,
12    reject_total: AtomicU64,
13    eval_total_ms: AtomicU64,
14    eval_count: AtomicU64,
15}
16
17impl DataScopeMetrics {
18    /// 创建指标实例
19    pub fn new() -> Self {
20        Self {
21            hit_total: AtomicU64::new(0),
22            bypass_total: AtomicU64::new(0),
23            reject_total: AtomicU64::new(0),
24            eval_total_ms: AtomicU64::new(0),
25            eval_count: AtomicU64::new(0),
26        }
27    }
28
29    /// 记录命中(数据范围条件注入成功)
30    pub fn record_hit(&self, table: &str, mode: &str) {
31        self.hit_total.fetch_add(1, Ordering::Relaxed);
32        tracing::debug!(
33            target: "data_scope_metrics",
34            table = table,
35            mode = mode,
36            "data_scope_hit"
37        );
38    }
39
40    /// 记录绕过(超级管理员)
41    pub fn record_bypass(&self, user_id: i64, table: &str) {
42        self.bypass_total.fetch_add(1, Ordering::Relaxed);
43        tracing::info!(
44            target: "data_scope_audit",
45            user_id = user_id,
46            table = table,
47            "super bypass: user_id={}, table={}", user_id, table
48        );
49    }
50
51    /// 记录拒绝(错误发生)
52    pub fn record_reject(&self, error_code: &str, table: &str) {
53        self.reject_total.fetch_add(1, Ordering::Relaxed);
54        tracing::warn!(
55            target: "data_scope_metrics",
56            error_code = error_code,
57            table = table,
58            "data_scope_reject"
59        );
60    }
61
62    /// 记录评估耗时
63    pub fn record_eval(&self, elapsed_ms: u64) {
64        self.eval_total_ms.fetch_add(elapsed_ms, Ordering::Relaxed);
65        self.eval_count.fetch_add(1, Ordering::Relaxed);
66    }
67
68    /// 获取命中总数
69    pub fn hit_total(&self) -> u64 {
70        self.hit_total.load(Ordering::Relaxed)
71    }
72
73    /// 获取绕过总数
74    pub fn bypass_total(&self) -> u64 {
75        self.bypass_total.load(Ordering::Relaxed)
76    }
77
78    /// 获取拒绝总数
79    pub fn reject_total(&self) -> u64 {
80        self.reject_total.load(Ordering::Relaxed)
81    }
82
83    /// 获取平均评估耗时(毫秒)
84    pub fn avg_eval_ms(&self) -> f64 {
85        let count = self.eval_count.load(Ordering::Relaxed);
86        if count == 0 {
87            0.0
88        } else {
89            self.eval_total_ms.load(Ordering::Relaxed) as f64 / count as f64
90        }
91    }
92}
93
94impl Default for DataScopeMetrics {
95    fn default() -> Self {
96        Self::new()
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_metrics_record() {
106        let metrics = DataScopeMetrics::new();
107        metrics.record_hit("order", "dept");
108        metrics.record_hit("order", "dept");
109        metrics.record_bypass(1, "order");
110        metrics.record_reject("DATA_SCOPE_NO_USER_CONTEXT", "order");
111        metrics.record_eval(5);
112        metrics.record_eval(15);
113        assert_eq!(metrics.hit_total(), 2);
114        assert_eq!(metrics.bypass_total(), 1);
115        assert_eq!(metrics.reject_total(), 1);
116        assert_eq!(metrics.avg_eval_ms(), 10.0);
117    }
118
119    #[test]
120    fn test_avg_eval_ms_zero_count() {
121        let metrics = DataScopeMetrics::new();
122        assert_eq!(metrics.avg_eval_ms(), 0.0);
123    }
124
125    #[test]
126    fn test_default() {
127        let metrics = DataScopeMetrics::default();
128        assert_eq!(metrics.hit_total(), 0);
129        assert_eq!(metrics.avg_eval_ms(), 0.0);
130    }
131}