use std::collections::HashMap;
use std::time::Instant;
use parking_lot::Mutex;
use tracing::level_filters::LevelFilter;
use tracing::{Level, Subscriber};
use tracing_subscriber::Layer;
use tracing_subscriber::layer::Context;
use tracing_subscriber::registry::LookupSpan;
#[derive(Debug, Clone)]
pub struct SamplingConfig {
pub enabled: bool,
pub initial_rate: u32,
pub thereafter_rate: u32,
}
impl Default for SamplingConfig {
fn default() -> Self {
Self {
enabled: false,
initial_rate: 100,
thereafter_rate: 100,
}
}
}
#[derive(Debug)]
struct LevelCounter {
window_start: Instant,
count: u32,
}
impl LevelCounter {
fn new() -> Self {
Self {
window_start: Instant::now(),
count: 0,
}
}
}
pub struct SamplingLayer {
initial_rate: u32,
thereafter_rate: u32,
counters: Mutex<HashMap<Level, LevelCounter>>,
}
impl SamplingLayer {
pub fn new(cfg: &SamplingConfig) -> Self {
Self {
initial_rate: cfg.initial_rate,
thereafter_rate: cfg.thereafter_rate.max(1),
counters: Mutex::new(HashMap::new()),
}
}
fn should_keep(&self, level: Level) -> bool {
let mut counters = self.counters.lock();
let counter = counters.entry(level).or_insert_with(LevelCounter::new);
let elapsed = counter.window_start.elapsed();
if elapsed.as_secs() >= 1 {
counter.window_start = Instant::now();
counter.count = 1;
return true;
}
counter.count += 1;
if counter.count <= self.initial_rate {
return true;
}
let excess = counter.count - self.initial_rate;
excess % self.thereafter_rate == 1
}
}
impl<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for SamplingLayer {
fn event_enabled(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) -> bool {
self.should_keep(*event.metadata().level())
}
fn max_level_hint(&self) -> Option<LevelFilter> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_is_disabled() {
let cfg = SamplingConfig::default();
assert!(!cfg.enabled);
assert_eq!(cfg.initial_rate, 100);
assert_eq!(cfg.thereafter_rate, 100);
}
#[test]
fn allows_initial_burst() {
let layer = SamplingLayer::new(&SamplingConfig {
enabled: true,
initial_rate: 5,
thereafter_rate: 2,
});
for _ in 0..5 {
assert!(layer.should_keep(Level::INFO));
}
}
#[test]
fn drops_after_burst() {
let layer = SamplingLayer::new(&SamplingConfig {
enabled: true,
initial_rate: 2,
thereafter_rate: 3,
});
assert!(layer.should_keep(Level::INFO));
assert!(layer.should_keep(Level::INFO));
assert!(layer.should_keep(Level::INFO)); assert!(!layer.should_keep(Level::INFO)); assert!(!layer.should_keep(Level::INFO)); assert!(layer.should_keep(Level::INFO)); }
#[test]
fn per_level_independent_counting() {
let layer = SamplingLayer::new(&SamplingConfig {
enabled: true,
initial_rate: 1,
thereafter_rate: 2,
});
assert!(layer.should_keep(Level::INFO));
assert!(layer.should_keep(Level::WARN));
assert!(layer.should_keep(Level::INFO));
assert!(layer.should_keep(Level::WARN));
assert!(!layer.should_keep(Level::INFO));
assert!(!layer.should_keep(Level::WARN));
}
#[test]
fn thereafter_rate_zero_treated_as_one() {
let layer = SamplingLayer::new(&SamplingConfig {
enabled: true,
initial_rate: 1,
thereafter_rate: 0, });
assert!(layer.should_keep(Level::ERROR));
assert!(!layer.should_keep(Level::ERROR));
}
}