rskit_logging/
sampling.rs1use std::collections::HashMap;
17use std::time::Instant;
18
19use parking_lot::Mutex;
20use tracing::level_filters::LevelFilter;
21use tracing::{Level, Subscriber};
22use tracing_subscriber::Layer;
23use tracing_subscriber::layer::Context;
24use tracing_subscriber::registry::LookupSpan;
25
26#[derive(Debug, Clone)]
30pub struct SamplingConfig {
31 pub enabled: bool,
33 pub initial_rate: u32,
35 pub thereafter_rate: u32,
37}
38
39impl Default for SamplingConfig {
40 fn default() -> Self {
41 Self {
42 enabled: false,
43 initial_rate: 100,
44 thereafter_rate: 100,
45 }
46 }
47}
48
49#[derive(Debug)]
53struct LevelCounter {
54 window_start: Instant,
55 count: u32,
56}
57
58impl LevelCounter {
59 fn new() -> Self {
60 Self {
61 window_start: Instant::now(),
62 count: 0,
63 }
64 }
65}
66
67pub struct SamplingLayer {
74 initial_rate: u32,
75 thereafter_rate: u32,
76 counters: Mutex<HashMap<Level, LevelCounter>>,
77}
78
79impl SamplingLayer {
80 pub fn new(cfg: &SamplingConfig) -> Self {
82 Self {
83 initial_rate: cfg.initial_rate,
84 thereafter_rate: cfg.thereafter_rate.max(1),
85 counters: Mutex::new(HashMap::new()),
86 }
87 }
88
89 fn should_keep(&self, level: Level) -> bool {
91 let mut counters = self.counters.lock();
92 let counter = counters.entry(level).or_insert_with(LevelCounter::new);
93
94 let elapsed = counter.window_start.elapsed();
95 if elapsed.as_secs() >= 1 {
96 counter.window_start = Instant::now();
98 counter.count = 1;
99 return true;
100 }
101
102 counter.count += 1;
103
104 if counter.count <= self.initial_rate {
105 return true;
106 }
107
108 let excess = counter.count - self.initial_rate;
110 excess % self.thereafter_rate == 1
111 }
112}
113
114impl<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for SamplingLayer {
115 fn event_enabled(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) -> bool {
116 self.should_keep(*event.metadata().level())
117 }
118
119 fn max_level_hint(&self) -> Option<LevelFilter> {
120 None
122 }
123}
124
125#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn default_config_is_disabled() {
133 let cfg = SamplingConfig::default();
134 assert!(!cfg.enabled);
135 assert_eq!(cfg.initial_rate, 100);
136 assert_eq!(cfg.thereafter_rate, 100);
137 }
138
139 #[test]
140 fn allows_initial_burst() {
141 let layer = SamplingLayer::new(&SamplingConfig {
142 enabled: true,
143 initial_rate: 5,
144 thereafter_rate: 2,
145 });
146
147 for _ in 0..5 {
148 assert!(layer.should_keep(Level::INFO));
149 }
150 }
151
152 #[test]
153 fn drops_after_burst() {
154 let layer = SamplingLayer::new(&SamplingConfig {
155 enabled: true,
156 initial_rate: 2,
157 thereafter_rate: 3,
158 });
159
160 assert!(layer.should_keep(Level::INFO));
162 assert!(layer.should_keep(Level::INFO));
163
164 assert!(layer.should_keep(Level::INFO)); assert!(!layer.should_keep(Level::INFO)); assert!(!layer.should_keep(Level::INFO)); assert!(layer.should_keep(Level::INFO)); }
170
171 #[test]
172 fn per_level_independent_counting() {
173 let layer = SamplingLayer::new(&SamplingConfig {
174 enabled: true,
175 initial_rate: 1,
176 thereafter_rate: 2,
177 });
178
179 assert!(layer.should_keep(Level::INFO));
181 assert!(layer.should_keep(Level::WARN));
182
183 assert!(layer.should_keep(Level::INFO));
185 assert!(layer.should_keep(Level::WARN));
186
187 assert!(!layer.should_keep(Level::INFO));
189 assert!(!layer.should_keep(Level::WARN));
190 }
191
192 #[test]
193 fn thereafter_rate_zero_treated_as_one() {
194 let layer = SamplingLayer::new(&SamplingConfig {
195 enabled: true,
196 initial_rate: 1,
197 thereafter_rate: 0, });
199
200 assert!(layer.should_keep(Level::ERROR));
201 assert!(!layer.should_keep(Level::ERROR));
207 }
208}