use crate::observability::traits::{Observer, ObserverEvent, ObserverMetric};
pub struct MultiObserver {
observers: Vec<Box<dyn Observer>>,
}
impl MultiObserver {
pub fn new(observers: Vec<Box<dyn Observer>>) -> Self {
Self { observers }
}
}
impl Observer for MultiObserver {
fn record_event(&self, event: &ObserverEvent) {
for obs in &self.observers {
obs.record_event(event);
}
}
fn record_metric(&self, metric: &ObserverMetric) {
for obs in &self.observers {
obs.record_metric(metric);
}
}
fn flush(&self) {
for obs in &self.observers {
obs.flush();
}
}
fn name(&self) -> &str {
"multi"
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use crate::observability::multi::MultiObserver;
use crate::observability::traits::*;
struct CountingObserver {
events: Arc<AtomicUsize>,
metrics: Arc<AtomicUsize>,
flushes: Arc<AtomicUsize>,
}
impl CountingObserver {
fn new() -> (Self, Arc<AtomicUsize>, Arc<AtomicUsize>, Arc<AtomicUsize>) {
let events = Arc::new(AtomicUsize::new(0));
let metrics = Arc::new(AtomicUsize::new(0));
let flushes = Arc::new(AtomicUsize::new(0));
(
Self {
events: Arc::clone(&events),
metrics: Arc::clone(&metrics),
flushes: Arc::clone(&flushes),
},
events,
metrics,
flushes,
)
}
}
impl Observer for CountingObserver {
fn record_event(&self, _event: &ObserverEvent) {
self.events.fetch_add(1, Ordering::Relaxed);
}
fn record_metric(&self, _metric: &ObserverMetric) {
self.metrics.fetch_add(1, Ordering::Relaxed);
}
fn flush(&self) {
self.flushes.fetch_add(1, Ordering::Relaxed);
}
fn name(&self) -> &str {
"counting"
}
}
#[test]
fn name_is_multi() {
let multi = MultiObserver::new(vec![]);
assert_eq!(multi.name(), "multi");
}
#[test]
fn empty_multi_does_not_panic() {
let multi = MultiObserver::new(vec![]);
multi.record_event(&ObserverEvent::TurnComplete);
multi.record_metric(&ObserverMetric::TokensUsed(100));
multi.flush();
}
#[test]
fn dispatches_to_all_observers() {
let (a, a_events, a_metrics, a_flushes) = CountingObserver::new();
let (b, b_events, b_metrics, b_flushes) = CountingObserver::new();
let multi = MultiObserver::new(vec![Box::new(a), Box::new(b)]);
multi.record_event(&ObserverEvent::TurnComplete);
multi.record_event(&ObserverEvent::HeartbeatTick);
multi.record_metric(&ObserverMetric::TokensUsed(50));
multi.flush();
assert_eq!(a_events.load(Ordering::Relaxed), 2);
assert_eq!(a_metrics.load(Ordering::Relaxed), 1);
assert_eq!(a_flushes.load(Ordering::Relaxed), 1);
assert_eq!(b_events.load(Ordering::Relaxed), 2);
assert_eq!(b_metrics.load(Ordering::Relaxed), 1);
assert_eq!(b_flushes.load(Ordering::Relaxed), 1);
}
#[test]
fn single_observer_works() {
let (obs, events, _, _) = CountingObserver::new();
let multi = MultiObserver::new(vec![Box::new(obs)]);
multi.record_event(&ObserverEvent::AgentEnd {
duration: Duration::from_secs(1),
tokens_used: None,
});
assert_eq!(events.load(Ordering::Relaxed), 1);
}
}