zeroclaw_runtime/observability/
traits.rs1pub use zeroclaw_api::observability_traits::*;
2
3#[allow(unused_imports)]
4pub use async_trait::async_trait;
5
6#[cfg(test)]
7mod tests {
8 use super::*;
9 use parking_lot::Mutex;
10 use std::time::Duration;
11
12 #[derive(Default)]
13 struct DummyObserver {
14 events: Mutex<u64>,
15 metrics: Mutex<u64>,
16 }
17
18 impl Observer for DummyObserver {
19 fn record_event(&self, _event: &ObserverEvent) {
20 let mut guard = self.events.lock();
21 *guard += 1;
22 }
23
24 fn record_metric(&self, _metric: &ObserverMetric) {
25 let mut guard = self.metrics.lock();
26 *guard += 1;
27 }
28
29 fn name(&self) -> &str {
30 "dummy-observer"
31 }
32
33 fn as_any(&self) -> &dyn std::any::Any {
34 self
35 }
36 }
37
38 #[test]
39 fn observer_records_events_and_metrics() {
40 let observer = DummyObserver::default();
41
42 observer.record_event(&ObserverEvent::HeartbeatTick);
43 observer.record_event(&ObserverEvent::Error {
44 component: "test".into(),
45 message: "boom".into(),
46 });
47 observer.record_metric(&ObserverMetric::TokensUsed(42));
48
49 assert_eq!(*observer.events.lock(), 2);
50 assert_eq!(*observer.metrics.lock(), 1);
51 }
52
53 #[test]
54 fn observer_default_flush_and_as_any_work() {
55 let observer = DummyObserver::default();
56
57 observer.flush();
58 assert_eq!(observer.name(), "dummy-observer");
59 assert!(observer.as_any().downcast_ref::<DummyObserver>().is_some());
60 }
61
62 #[test]
63 fn observer_event_and_metric_are_cloneable() {
64 let event = ObserverEvent::ToolCall {
65 tool: "shell".into(),
66 tool_call_id: None,
67 duration: Duration::from_millis(10),
68 success: true,
69 arguments: None,
70 result: None,
71 };
72 let metric = ObserverMetric::RequestLatency(Duration::from_millis(8));
73
74 let cloned_event = event.clone();
75 let cloned_metric = metric.clone();
76
77 assert!(matches!(cloned_event, ObserverEvent::ToolCall { .. }));
78 assert!(matches!(cloned_metric, ObserverMetric::RequestLatency(_)));
79 }
80}