1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct TelemetryEvent {
9 pub id: String,
11
12 pub event_type: EventType,
14
15 pub timestamp: chrono::DateTime<chrono::Utc>,
17
18 pub metadata: EventMetadata,
20
21 pub user_id: Option<String>,
23
24 pub session_id: String,
26}
27
28impl TelemetryEvent {
29 pub fn new(event_type: EventType) -> Self {
31 Self {
32 id: uuid::Uuid::new_v4().to_string(),
33 event_type,
34 timestamp: chrono::Utc::now(),
35 metadata: EventMetadata::default(),
36 user_id: None,
37 session_id: uuid::Uuid::new_v4().to_string(),
38 }
39 }
40
41 pub fn command_executed(command: String, duration_ms: u64) -> Self {
43 let mut event = Self::new(EventType::CommandExecuted);
44 event.metadata.set("command", command);
45 event.metadata.set("duration_ms", duration_ms.to_string());
46 event
47 }
48
49 pub fn synthesis_request(
51 voice: String,
52 text_length: usize,
53 duration_ms: u64,
54 success: bool,
55 ) -> Self {
56 let mut event = Self::new(EventType::SynthesisRequest);
57 event.metadata.set("voice", voice);
58 event.metadata.set("text_length", text_length.to_string());
59 event.metadata.set("duration_ms", duration_ms.to_string());
60 event.metadata.set("success", success.to_string());
61 event
62 }
63
64 pub fn error(error_type: String, message: String, severity: ErrorSeverity) -> Self {
66 let mut event = Self::new(EventType::Error);
67 event.metadata.set("error_type", error_type);
68 event.metadata.set("message", message);
69 event.metadata.set("severity", severity.to_string());
70 event
71 }
72
73 pub fn performance(metric_name: String, value: f64, unit: String) -> Self {
75 let mut event = Self::new(EventType::Performance);
76 event.metadata.set("metric_name", metric_name);
77 event.metadata.set("value", value.to_string());
78 event.metadata.set("unit", unit);
79 event
80 }
81
82 pub fn with_user_id(mut self, user_id: String) -> Self {
84 self.user_id = Some(user_id);
85 self
86 }
87
88 pub fn with_session_id(mut self, session_id: String) -> Self {
90 self.session_id = session_id;
91 self
92 }
93
94 pub fn with_metadata(mut self, key: String, value: String) -> Self {
96 self.metadata.set(key, value);
97 self
98 }
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
103pub enum EventType {
104 CommandExecuted,
106
107 SynthesisRequest,
109
110 Error,
112
113 Performance,
115
116 ConfigurationChanged,
118
119 ModelLoaded,
121
122 VoiceChanged,
124
125 ApplicationStarted,
127
128 ApplicationStopped,
130
131 FeatureUsed,
133
134 Custom,
136}
137
138impl std::fmt::Display for EventType {
139 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 match self {
141 EventType::CommandExecuted => write!(f, "command_executed"),
142 EventType::SynthesisRequest => write!(f, "synthesis_request"),
143 EventType::Error => write!(f, "error"),
144 EventType::Performance => write!(f, "performance"),
145 EventType::ConfigurationChanged => write!(f, "configuration_changed"),
146 EventType::ModelLoaded => write!(f, "model_loaded"),
147 EventType::VoiceChanged => write!(f, "voice_changed"),
148 EventType::ApplicationStarted => write!(f, "application_started"),
149 EventType::ApplicationStopped => write!(f, "application_stopped"),
150 EventType::FeatureUsed => write!(f, "feature_used"),
151 EventType::Custom => write!(f, "custom"),
152 }
153 }
154}
155
156#[derive(Debug, Clone, Default, Serialize, Deserialize)]
158pub struct EventMetadata {
159 data: HashMap<String, String>,
160}
161
162impl EventMetadata {
163 pub fn new() -> Self {
165 Self {
166 data: HashMap::new(),
167 }
168 }
169
170 pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
172 self.data.insert(key.into(), value.into());
173 }
174
175 pub fn get(&self, key: &str) -> Option<&String> {
177 self.data.get(key)
178 }
179
180 pub fn contains(&self, key: &str) -> bool {
182 self.data.contains_key(key)
183 }
184
185 pub fn keys(&self) -> impl Iterator<Item = &String> {
187 self.data.keys()
188 }
189
190 pub fn as_map(&self) -> &HashMap<String, String> {
192 &self.data
193 }
194
195 pub fn remove(&mut self, key: &str) -> Option<String> {
197 self.data.remove(key)
198 }
199
200 pub fn clear(&mut self) {
202 self.data.clear();
203 }
204
205 pub fn len(&self) -> usize {
207 self.data.len()
208 }
209
210 pub fn is_empty(&self) -> bool {
212 self.data.is_empty()
213 }
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
218pub enum ErrorSeverity {
219 Info,
221
222 Warning,
224
225 Error,
227
228 Critical,
230
231 Fatal,
233}
234
235impl std::fmt::Display for ErrorSeverity {
236 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237 match self {
238 ErrorSeverity::Info => write!(f, "info"),
239 ErrorSeverity::Warning => write!(f, "warning"),
240 ErrorSeverity::Error => write!(f, "error"),
241 ErrorSeverity::Critical => write!(f, "critical"),
242 ErrorSeverity::Fatal => write!(f, "fatal"),
243 }
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_event_creation() {
253 let event = TelemetryEvent::new(EventType::CommandExecuted);
254 assert_eq!(event.event_type, EventType::CommandExecuted);
255 assert!(!event.id.is_empty());
256 assert!(!event.session_id.is_empty());
257 }
258
259 #[test]
260 fn test_command_executed_event() {
261 let event = TelemetryEvent::command_executed("synthesize".to_string(), 1500);
262 assert_eq!(event.event_type, EventType::CommandExecuted);
263 assert_eq!(
264 event
265 .metadata
266 .get("command")
267 .expect("command metadata should exist"),
268 "synthesize"
269 );
270 assert_eq!(
271 event
272 .metadata
273 .get("duration_ms")
274 .expect("duration_ms metadata should exist"),
275 "1500"
276 );
277 }
278
279 #[test]
280 fn test_synthesis_request_event() {
281 let event = TelemetryEvent::synthesis_request("kokoro-en".to_string(), 100, 2000, true);
282 assert_eq!(event.event_type, EventType::SynthesisRequest);
283 assert_eq!(
284 event
285 .metadata
286 .get("voice")
287 .expect("voice metadata should exist"),
288 "kokoro-en"
289 );
290 assert_eq!(
291 event
292 .metadata
293 .get("text_length")
294 .expect("text_length metadata should exist"),
295 "100"
296 );
297 assert_eq!(
298 event
299 .metadata
300 .get("duration_ms")
301 .expect("duration_ms metadata should exist"),
302 "2000"
303 );
304 assert_eq!(
305 event
306 .metadata
307 .get("success")
308 .expect("success metadata should exist"),
309 "true"
310 );
311 }
312
313 #[test]
314 fn test_error_event() {
315 let event = TelemetryEvent::error(
316 "synthesis_error".to_string(),
317 "Failed to load model".to_string(),
318 ErrorSeverity::Error,
319 );
320 assert_eq!(event.event_type, EventType::Error);
321 assert_eq!(
322 event
323 .metadata
324 .get("error_type")
325 .expect("error_type metadata should exist"),
326 "synthesis_error"
327 );
328 assert_eq!(
329 event
330 .metadata
331 .get("message")
332 .expect("message metadata should exist"),
333 "Failed to load model"
334 );
335 assert_eq!(
336 event
337 .metadata
338 .get("severity")
339 .expect("severity metadata should exist"),
340 "error"
341 );
342 }
343
344 #[test]
345 fn test_performance_event() {
346 let event = TelemetryEvent::performance("rtf".to_string(), 0.25, "ratio".to_string());
347 assert_eq!(event.event_type, EventType::Performance);
348 assert_eq!(
349 event
350 .metadata
351 .get("metric_name")
352 .expect("metric_name metadata should exist"),
353 "rtf"
354 );
355 assert_eq!(
356 event
357 .metadata
358 .get("value")
359 .expect("value metadata should exist"),
360 "0.25"
361 );
362 assert_eq!(
363 event
364 .metadata
365 .get("unit")
366 .expect("unit metadata should exist"),
367 "ratio"
368 );
369 }
370
371 #[test]
372 fn test_event_builder() {
373 let event = TelemetryEvent::new(EventType::Custom)
374 .with_user_id("user123".to_string())
375 .with_session_id("session456".to_string())
376 .with_metadata("key".to_string(), "value".to_string());
377
378 assert_eq!(event.user_id.expect("user_id should be set"), "user123");
379 assert_eq!(event.session_id, "session456");
380 assert_eq!(
381 event
382 .metadata
383 .get("key")
384 .expect("key metadata should exist"),
385 "value"
386 );
387 }
388
389 #[test]
390 fn test_event_metadata() {
391 let mut metadata = EventMetadata::new();
392 assert!(metadata.is_empty());
393
394 metadata.set("key1", "value1");
395 metadata.set("key2", "value2");
396 assert_eq!(metadata.len(), 2);
397 assert!(metadata.contains("key1"));
398 assert_eq!(
399 metadata.get("key1").expect("key1 metadata should exist"),
400 "value1"
401 );
402
403 metadata.remove("key1");
404 assert_eq!(metadata.len(), 1);
405 assert!(!metadata.contains("key1"));
406
407 metadata.clear();
408 assert!(metadata.is_empty());
409 }
410
411 #[test]
412 fn test_event_type_display() {
413 assert_eq!(EventType::CommandExecuted.to_string(), "command_executed");
414 assert_eq!(EventType::SynthesisRequest.to_string(), "synthesis_request");
415 assert_eq!(EventType::Error.to_string(), "error");
416 }
417
418 #[test]
419 fn test_error_severity_display() {
420 assert_eq!(ErrorSeverity::Info.to_string(), "info");
421 assert_eq!(ErrorSeverity::Warning.to_string(), "warning");
422 assert_eq!(ErrorSeverity::Error.to_string(), "error");
423 assert_eq!(ErrorSeverity::Critical.to_string(), "critical");
424 assert_eq!(ErrorSeverity::Fatal.to_string(), "fatal");
425 }
426}