mojentic/tracer/
null_tracer.rs

1//! Null tracer implementation following the Null Object Pattern
2//!
3//! This module provides a NullTracer that implements the same interface as TracerSystem
4//! but performs no operations. This eliminates the need for conditional checks in client code.
5
6use super::tracer_events::TracerEvent;
7use std::collections::HashMap;
8
9/// A no-op implementation of TracerSystem that silently discards all tracing operations
10///
11/// This class follows the Null Object Pattern to eliminate conditional checks in client code.
12/// All record methods are overridden to do nothing, and all query methods return empty results.
13pub struct NullTracer;
14
15impl NullTracer {
16    /// Create a new null tracer
17    pub fn new() -> Self {
18        Self
19    }
20
21    /// Always returns false for null tracer
22    pub fn is_enabled(&self) -> bool {
23        false
24    }
25
26    /// No-op method for interface compatibility
27    pub fn enable(&self) {
28        // Do nothing
29    }
30
31    /// No-op method for interface compatibility
32    pub fn disable(&self) {
33        // Do nothing
34    }
35
36    /// Do nothing implementation of record_event
37    pub fn record_event(&self, _event: Box<dyn TracerEvent>) {
38        // Do nothing
39    }
40
41    /// Do nothing implementation of record_llm_call
42    #[allow(clippy::too_many_arguments)]
43    pub fn record_llm_call(
44        &self,
45        _model: impl Into<String>,
46        _messages: Vec<HashMap<String, serde_json::Value>>,
47        _temperature: f64,
48        _tools: Option<Vec<HashMap<String, serde_json::Value>>>,
49        _source: impl Into<String>,
50        _correlation_id: impl Into<String>,
51    ) {
52        // Do nothing
53    }
54
55    /// Do nothing implementation of record_llm_response
56    pub fn record_llm_response(
57        &self,
58        _model: impl Into<String>,
59        _content: impl Into<String>,
60        _tool_calls: Option<Vec<HashMap<String, serde_json::Value>>>,
61        _call_duration_ms: Option<f64>,
62        _source: impl Into<String>,
63        _correlation_id: impl Into<String>,
64    ) {
65        // Do nothing
66    }
67
68    /// Do nothing implementation of record_tool_call
69    #[allow(clippy::too_many_arguments)]
70    pub fn record_tool_call(
71        &self,
72        _tool_name: impl Into<String>,
73        _arguments: HashMap<String, serde_json::Value>,
74        _result: serde_json::Value,
75        _caller: Option<String>,
76        _call_duration_ms: Option<f64>,
77        _source: impl Into<String>,
78        _correlation_id: impl Into<String>,
79    ) {
80        // Do nothing
81    }
82
83    /// Do nothing implementation of record_agent_interaction
84    pub fn record_agent_interaction(
85        &self,
86        _from_agent: impl Into<String>,
87        _to_agent: impl Into<String>,
88        _event_type: impl Into<String>,
89        _event_id: Option<String>,
90        _source: impl Into<String>,
91        _correlation_id: impl Into<String>,
92    ) {
93        // Do nothing
94    }
95
96    /// Return an empty vector for any get_event_summaries request
97    pub fn get_event_summaries(
98        &self,
99        _start_time: Option<f64>,
100        _end_time: Option<f64>,
101        _filter_func: Option<&dyn super::EventFilterFn>,
102    ) -> Vec<String> {
103        Vec::new()
104    }
105
106    /// Return an empty vector for any get_last_n_summaries request
107    pub fn get_last_n_summaries(
108        &self,
109        _n: usize,
110        _filter_func: Option<&dyn super::EventFilterFn>,
111    ) -> Vec<String> {
112        Vec::new()
113    }
114
115    /// Return 0 for any count_events request
116    pub fn count_events(
117        &self,
118        _start_time: Option<f64>,
119        _end_time: Option<f64>,
120        _filter_func: Option<&dyn super::EventFilterFn>,
121    ) -> usize {
122        0
123    }
124
125    /// Do nothing implementation of clear method
126    pub fn clear(&self) {
127        // Do nothing
128    }
129
130    /// Always returns 0 for null tracer
131    pub fn len(&self) -> usize {
132        0
133    }
134
135    /// Always returns true for null tracer
136    pub fn is_empty(&self) -> bool {
137        true
138    }
139}
140
141impl Default for NullTracer {
142    fn default() -> Self {
143        Self::new()
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_null_tracer_is_disabled() {
153        let tracer = NullTracer::new();
154        assert!(!tracer.is_enabled());
155    }
156
157    #[test]
158    fn test_null_tracer_enable_disable() {
159        let tracer = NullTracer::new();
160        tracer.enable();
161        assert!(!tracer.is_enabled());
162
163        tracer.disable();
164        assert!(!tracer.is_enabled());
165    }
166
167    #[test]
168    fn test_null_tracer_record_methods() {
169        let tracer = NullTracer::new();
170
171        // All record methods should be no-ops
172        tracer.record_llm_call("llama3.2", vec![], 0.7, None, "test", "corr-123");
173
174        tracer.record_llm_response(
175            "llama3.2",
176            "Hello, world!",
177            None,
178            Some(150.5),
179            "test",
180            "corr-456",
181        );
182
183        let mut args = HashMap::new();
184        args.insert("input".to_string(), serde_json::json!("test"));
185
186        tracer.record_tool_call(
187            "example_tool",
188            args,
189            serde_json::json!({"output": "result"}),
190            Some("agent1".to_string()),
191            Some(25.0),
192            "test",
193            "corr-789",
194        );
195
196        tracer.record_agent_interaction(
197            "agent1",
198            "agent2",
199            "message",
200            Some("evt-123".to_string()),
201            "test",
202            "corr-abc",
203        );
204
205        // Should have no events
206        assert_eq!(tracer.len(), 0);
207        assert!(tracer.is_empty());
208    }
209
210    #[test]
211    fn test_null_tracer_query_methods() {
212        let tracer = NullTracer::new();
213
214        // All query methods should return empty results
215        let summaries = tracer.get_event_summaries(None, None, None);
216        assert!(summaries.is_empty());
217
218        let last_summaries = tracer.get_last_n_summaries(10, None);
219        assert!(last_summaries.is_empty());
220
221        let count = tracer.count_events(None, None, None);
222        assert_eq!(count, 0);
223    }
224
225    #[test]
226    fn test_null_tracer_clear() {
227        let tracer = NullTracer::new();
228        tracer.clear();
229        assert!(tracer.is_empty());
230    }
231
232    #[test]
233    fn test_null_tracer_len() {
234        let tracer = NullTracer::new();
235        assert_eq!(tracer.len(), 0);
236    }
237}