use std::sync::Arc;
use std::sync::atomic::{AtomicU8, AtomicU64, Ordering};
use tracing::metadata::LevelFilter;
use tracing::{Level, Metadata};
pub enum Interest {
Never,
Sometimes,
Always,
}
pub trait EnabledPredicate: Send + Sync + 'static {
fn max_level_hint(&self) -> Option<LevelFilter>;
fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest;
fn enabled(&self, metadata: &Metadata<'_>) -> bool;
fn new_span_enabled(&self, span: &tracing::span::Attributes<'_>) -> bool;
}
pub struct LevelPredicate {
level: Arc<AtomicU8>,
}
impl LevelPredicate {
pub fn new(level: Level) -> Self {
Self::with_filter(LevelFilter::from_level(level))
}
pub fn with_filter(filter: LevelFilter) -> Self {
Self {
level: Arc::new(AtomicU8::new(filter_to_u8(filter))),
}
}
pub fn handle(&self) -> LevelHandle {
LevelHandle {
level: Arc::clone(&self.level),
}
}
}
#[derive(Clone)]
pub struct LevelHandle {
level: Arc<AtomicU8>,
}
impl LevelHandle {
pub fn set(&self, filter: LevelFilter) {
self.level.store(filter_to_u8(filter), Ordering::Release);
tracing::callsite::rebuild_interest_cache();
}
pub fn get(&self) -> LevelFilter {
u8_to_filter(self.level.load(Ordering::Acquire))
}
}
impl EnabledPredicate for LevelPredicate {
fn max_level_hint(&self) -> Option<LevelFilter> {
Some(u8_to_filter(self.level.load(Ordering::Acquire)))
}
fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest {
let filter = u8_to_filter(self.level.load(Ordering::Acquire));
if filter == LevelFilter::OFF {
return Interest::Never;
}
if LevelFilter::from_level(*metadata.level()) <= filter {
Interest::Always
} else {
Interest::Never
}
}
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
let filter = u8_to_filter(self.level.load(Ordering::Relaxed));
if filter == LevelFilter::OFF {
return false;
}
LevelFilter::from_level(*metadata.level()) <= filter
}
fn new_span_enabled(&self, span: &tracing::span::Attributes<'_>) -> bool {
self.enabled(span.metadata())
}
}
fn filter_to_u8(f: LevelFilter) -> u8 {
if f == LevelFilter::OFF {
0
} else if f == LevelFilter::ERROR {
1
} else if f == LevelFilter::WARN {
2
} else if f == LevelFilter::INFO {
3
} else if f == LevelFilter::DEBUG {
4
} else {
5 }
}
fn u8_to_filter(n: u8) -> LevelFilter {
match n {
0 => LevelFilter::OFF,
1 => LevelFilter::ERROR,
2 => LevelFilter::WARN,
3 => LevelFilter::INFO,
4 => LevelFilter::DEBUG,
_ => LevelFilter::TRACE,
}
}
pub struct ChancePredicate<P: EnabledPredicate> {
chance_pct_bits: Arc<AtomicU64>,
inner: P,
}
impl<P: EnabledPredicate> ChancePredicate<P> {
pub fn new(inner: P, chance_pct: f64) -> Self {
let pct = clamp_pct(chance_pct);
Self {
chance_pct_bits: Arc::new(AtomicU64::new(pct.to_bits())),
inner,
}
}
pub fn handle(&self) -> ChanceHandle {
ChanceHandle {
bits: Arc::clone(&self.chance_pct_bits),
}
}
}
#[derive(Clone)]
pub struct ChanceHandle {
bits: Arc<AtomicU64>,
}
impl ChanceHandle {
pub fn set(&self, pct: f64) {
let pct = clamp_pct(pct);
self.bits.store(pct.to_bits(), Ordering::Relaxed);
}
pub fn get(&self) -> f64 {
f64::from_bits(self.bits.load(Ordering::Relaxed))
}
}
fn clamp_pct(pct: f64) -> f64 {
if pct.is_nan() {
0.0
} else {
pct.clamp(0.0, 100.0)
}
}
impl<P: EnabledPredicate> EnabledPredicate for ChancePredicate<P> {
fn max_level_hint(&self) -> Option<LevelFilter> {
self.inner.max_level_hint()
}
fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest {
self.inner.callsite_enabled(metadata)
}
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
self.inner.enabled(metadata)
}
fn new_span_enabled(&self, span: &tracing::span::Attributes<'_>) -> bool {
if span.is_root() {
let pct = f64::from_bits(self.chance_pct_bits.load(Ordering::Relaxed));
if pct <= 0.0 {
return false;
}
if pct < 100.0 {
let roll = rand::random::<u64>() as f64 / (u64::MAX as f64 + 1.0) * 100.0;
if roll >= pct {
return false;
}
}
}
self.inner.new_span_enabled(span)
}
}