zeroclaw_runtime/observability/
verbose.rs1use super::traits::{Observer, ObserverEvent, ObserverMetric};
2use std::any::Any;
3
4pub struct VerboseObserver;
9
10impl Default for VerboseObserver {
11 fn default() -> Self {
12 Self::new()
13 }
14}
15
16impl VerboseObserver {
17 pub fn new() -> Self {
18 Self
19 }
20}
21
22impl Observer for VerboseObserver {
23 fn record_event(&self, event: &ObserverEvent) {
24 match event {
25 ObserverEvent::LlmRequest {
26 model_provider,
27 model,
28 messages_count,
29 } => {
30 eprintln!("> Thinking");
31 eprintln!(
32 "> Send (model_provider={}, model={}, messages={})",
33 model_provider, model, messages_count
34 );
35 }
36 ObserverEvent::LlmResponse {
37 duration, success, ..
38 } => {
39 let ms = u64::try_from(duration.as_millis()).unwrap_or(u64::MAX);
40 eprintln!("< Receive (success={success}, duration_ms={ms})");
41 }
42 ObserverEvent::ToolCallStart { tool, .. } => {
43 eprintln!("> Tool {tool}");
44 }
45 ObserverEvent::ToolCall {
46 tool,
47 duration,
48 success,
49 ..
50 } => {
51 let ms = u64::try_from(duration.as_millis()).unwrap_or(u64::MAX);
52 eprintln!("< Tool {tool} (success={success}, duration_ms={ms})");
53 }
54 ObserverEvent::TurnComplete => {
55 eprintln!("< Complete");
56 }
57 _ => {}
58 }
59 }
60
61 #[inline(always)]
62 fn record_metric(&self, _metric: &ObserverMetric) {}
63
64 fn name(&self) -> &str {
65 "verbose"
66 }
67
68 fn as_any(&self) -> &dyn Any {
69 self
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use std::time::Duration;
77
78 #[test]
79 fn verbose_name() {
80 assert_eq!(VerboseObserver::new().name(), "verbose");
81 }
82
83 #[test]
84 fn verbose_events_do_not_panic() {
85 let obs = VerboseObserver::new();
86 obs.record_event(&ObserverEvent::LlmRequest {
87 model_provider: "openrouter".into(),
88 model: "claude".into(),
89 messages_count: 3,
90 });
91 obs.record_event(&ObserverEvent::LlmResponse {
92 model_provider: "openrouter".into(),
93 model: "claude".into(),
94 duration: Duration::from_millis(12),
95 success: true,
96 error_message: None,
97 input_tokens: Some(50),
98 output_tokens: Some(25),
99 });
100 obs.record_event(&ObserverEvent::ToolCallStart {
101 tool: "shell".into(),
102 tool_call_id: None,
103 arguments: None,
104 });
105 obs.record_event(&ObserverEvent::ToolCall {
106 tool: "shell".into(),
107 tool_call_id: None,
108 duration: Duration::from_millis(2),
109 success: true,
110 arguments: None,
111 result: None,
112 });
113 obs.record_event(&ObserverEvent::TurnComplete);
114 }
115}