use fastrace::collector::{Reporter, SpanRecord};
fn has_error_event(spans: &[SpanRecord]) -> bool {
spans.iter().any(|span| {
span.events
.iter()
.any(|event| event.name == crate::hook::ERROR_EVENT)
})
}
pub struct SamplingReporter<R: Reporter> {
inner: R,
sample_rate: u64,
}
impl<R: Reporter> SamplingReporter<R> {
#[must_use]
pub fn new(inner: R, sample_rate: u64) -> Self {
Self { inner, sample_rate }
}
}
impl<R: Reporter> Reporter for SamplingReporter<R> {
fn report(&mut self, spans: Vec<SpanRecord>) {
let trace_id = spans.first().map_or(0, |span| span.trace_id.0);
let keep = self.sample_rate <= 1
|| has_error_event(&spans)
|| trace_id.is_multiple_of(u128::from(self.sample_rate));
if keep {
self.inner.report(spans);
}
}
}
#[derive(Default)]
pub struct MultiReporter {
reporters: Vec<Box<dyn Reporter>>,
}
impl MultiReporter {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, reporter: impl Reporter + 'static) {
self.reporters.push(Box::new(reporter));
}
}
impl Reporter for MultiReporter {
fn report(&mut self, spans: Vec<SpanRecord>) {
let n = self.reporters.len();
if n <= 1 {
if let Some(reporter) = self.reporters.first_mut() {
reporter.report(spans);
}
return;
}
let Some((last, rest)) = self.reporters.split_last_mut() else {
return;
};
for reporter in rest {
reporter.report(spans.clone());
}
last.report(spans);
}
}
#[cfg(test)]
mod tests {
use fastrace::collector::{EventRecord, TestReporter, TraceId};
use super::*;
fn clean_trace(id: u128) -> Vec<SpanRecord> {
vec![SpanRecord {
trace_id: TraceId(id),
..SpanRecord::default()
}]
}
fn error_trace(id: u128) -> Vec<SpanRecord> {
vec![SpanRecord {
trace_id: TraceId(id),
events: vec![EventRecord {
name: "error".into(),
..EventRecord::default()
}],
..SpanRecord::default()
}]
}
#[test]
fn error_traces_always_kept() {
let (mock, spans) = TestReporter::new();
let mut reporter = SamplingReporter::new(mock, 3);
reporter.report(error_trace(1));
reporter.report(error_trace(2));
reporter.report(clean_trace(1));
assert_eq!(spans.lock().len(), 2);
}
#[test]
fn clean_traces_sampled_by_trace_id() {
let (mock, spans) = TestReporter::new();
let mut reporter = SamplingReporter::new(mock, 3);
for id in 1..=9 {
reporter.report(clean_trace(id));
}
let kept: Vec<u128> = spans.lock().iter().map(|span| span.trace_id.0).collect();
assert_eq!(kept, [3, 6, 9]);
}
#[test]
fn sampling_decision_is_stable_per_trace() {
let (mock, spans) = TestReporter::new();
let mut reporter = SamplingReporter::new(mock, 3);
reporter.report(clean_trace(3));
reporter.report(clean_trace(4));
reporter.report(clean_trace(3));
reporter.report(clean_trace(4));
let kept: Vec<u128> = spans.lock().iter().map(|span| span.trace_id.0).collect();
assert_eq!(kept, [3, 3]);
}
#[test]
fn sample_rate_zero_or_one_keeps_everything() {
for rate in [0, 1] {
let (mock, spans) = TestReporter::new();
let mut reporter = SamplingReporter::new(mock, rate);
for id in 0..5 {
reporter.report(clean_trace(id));
}
assert_eq!(spans.lock().len(), 5);
}
}
#[test]
fn multi_reporter_fans_out_to_all() {
let (a, a_spans) = TestReporter::new();
let (b, b_spans) = TestReporter::new();
let mut multi = MultiReporter::new();
multi.push(a);
multi.push(b);
multi.report(error_trace(1));
assert_eq!(a_spans.lock().len(), 1);
assert_eq!(b_spans.lock().len(), 1);
}
#[test]
fn multi_reporter_single_sink_moves_without_clone() {
let (a, a_spans) = TestReporter::new();
let mut multi = MultiReporter::new();
multi.push(a);
multi.report(clean_trace(1));
assert_eq!(a_spans.lock().len(), 1);
}
}