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 channel: _,
30 agent_alias: _,
31 turn_id: _,
32 } => {
33 eprintln!("> Thinking");
34 eprintln!(
35 "> Send (model_provider={}, model={}, messages={})",
36 model_provider, model, messages_count
37 );
38 }
39 ObserverEvent::LlmResponse {
40 duration, success, ..
41 } => {
42 let ms = u64::try_from(duration.as_millis()).unwrap_or(u64::MAX);
43 eprintln!("< Receive (success={success}, duration_ms={ms})");
44 }
45 ObserverEvent::ToolCallStart { tool, .. } => {
46 eprintln!("> Tool {tool}");
47 }
48 ObserverEvent::ToolCall {
49 tool,
50 duration,
51 success,
52 ..
53 } => {
54 let ms = u64::try_from(duration.as_millis()).unwrap_or(u64::MAX);
55 eprintln!("< Tool {tool} (success={success}, duration_ms={ms})");
56 }
57 ObserverEvent::TurnComplete => {
58 eprintln!("< Complete");
59 }
60 _ => {}
61 }
62 }
63
64 #[inline(always)]
65 fn record_metric(&self, _metric: &ObserverMetric) {}
66
67 fn name(&self) -> &str {
68 "verbose"
69 }
70
71 fn as_any(&self) -> &dyn Any {
72 self
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use std::time::Duration;
80
81 #[test]
82 fn verbose_name() {
83 assert_eq!(VerboseObserver::new().name(), "verbose");
84 }
85
86 #[test]
87 fn verbose_events_do_not_panic() {
88 let obs = VerboseObserver::new();
89 obs.record_event(&ObserverEvent::LlmRequest {
90 model_provider: "openrouter".into(),
91 model: "claude".into(),
92 messages_count: 3,
93 channel: None,
94 agent_alias: None,
95 turn_id: None,
96 });
97 obs.record_event(&ObserverEvent::LlmResponse {
98 model_provider: "openrouter".into(),
99 model: "claude".into(),
100 duration: Duration::from_millis(12),
101 success: true,
102 error_message: None,
103 input_tokens: Some(50),
104 output_tokens: Some(25),
105 channel: None,
106 agent_alias: None,
107 turn_id: None,
108 });
109 obs.record_event(&ObserverEvent::ToolCallStart {
110 tool: "shell".into(),
111 tool_call_id: None,
112 arguments: None,
113 channel: None,
114 agent_alias: None,
115 turn_id: None,
116 });
117 obs.record_event(&ObserverEvent::ToolCall {
118 tool: "shell".into(),
119 tool_call_id: None,
120 duration: Duration::from_millis(2),
121 success: true,
122 arguments: None,
123 result: None,
124 channel: None,
125 agent_alias: None,
126 turn_id: None,
127 });
128 obs.record_event(&ObserverEvent::TurnComplete);
129 }
130}