use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TelemetryEvent {
pub id: String,
pub event_type: EventType,
pub timestamp: chrono::DateTime<chrono::Utc>,
pub metadata: EventMetadata,
pub user_id: Option<String>,
pub session_id: String,
}
impl TelemetryEvent {
pub fn new(event_type: EventType) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
event_type,
timestamp: chrono::Utc::now(),
metadata: EventMetadata::default(),
user_id: None,
session_id: uuid::Uuid::new_v4().to_string(),
}
}
pub fn command_executed(command: String, duration_ms: u64) -> Self {
let mut event = Self::new(EventType::CommandExecuted);
event.metadata.set("command", command);
event.metadata.set("duration_ms", duration_ms.to_string());
event
}
pub fn synthesis_request(
voice: String,
text_length: usize,
duration_ms: u64,
success: bool,
) -> Self {
let mut event = Self::new(EventType::SynthesisRequest);
event.metadata.set("voice", voice);
event.metadata.set("text_length", text_length.to_string());
event.metadata.set("duration_ms", duration_ms.to_string());
event.metadata.set("success", success.to_string());
event
}
pub fn error(error_type: String, message: String, severity: ErrorSeverity) -> Self {
let mut event = Self::new(EventType::Error);
event.metadata.set("error_type", error_type);
event.metadata.set("message", message);
event.metadata.set("severity", severity.to_string());
event
}
pub fn performance(metric_name: String, value: f64, unit: String) -> Self {
let mut event = Self::new(EventType::Performance);
event.metadata.set("metric_name", metric_name);
event.metadata.set("value", value.to_string());
event.metadata.set("unit", unit);
event
}
pub fn with_user_id(mut self, user_id: String) -> Self {
self.user_id = Some(user_id);
self
}
pub fn with_session_id(mut self, session_id: String) -> Self {
self.session_id = session_id;
self
}
pub fn with_metadata(mut self, key: String, value: String) -> Self {
self.metadata.set(key, value);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub enum EventType {
CommandExecuted,
SynthesisRequest,
Error,
Performance,
ConfigurationChanged,
ModelLoaded,
VoiceChanged,
ApplicationStarted,
ApplicationStopped,
FeatureUsed,
Custom,
}
impl std::fmt::Display for EventType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EventType::CommandExecuted => write!(f, "command_executed"),
EventType::SynthesisRequest => write!(f, "synthesis_request"),
EventType::Error => write!(f, "error"),
EventType::Performance => write!(f, "performance"),
EventType::ConfigurationChanged => write!(f, "configuration_changed"),
EventType::ModelLoaded => write!(f, "model_loaded"),
EventType::VoiceChanged => write!(f, "voice_changed"),
EventType::ApplicationStarted => write!(f, "application_started"),
EventType::ApplicationStopped => write!(f, "application_stopped"),
EventType::FeatureUsed => write!(f, "feature_used"),
EventType::Custom => write!(f, "custom"),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct EventMetadata {
data: HashMap<String, String>,
}
impl EventMetadata {
pub fn new() -> Self {
Self {
data: HashMap::new(),
}
}
pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.data.insert(key.into(), value.into());
}
pub fn get(&self, key: &str) -> Option<&String> {
self.data.get(key)
}
pub fn contains(&self, key: &str) -> bool {
self.data.contains_key(key)
}
pub fn keys(&self) -> impl Iterator<Item = &String> {
self.data.keys()
}
pub fn as_map(&self) -> &HashMap<String, String> {
&self.data
}
pub fn remove(&mut self, key: &str) -> Option<String> {
self.data.remove(key)
}
pub fn clear(&mut self) {
self.data.clear();
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ErrorSeverity {
Info,
Warning,
Error,
Critical,
Fatal,
}
impl std::fmt::Display for ErrorSeverity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorSeverity::Info => write!(f, "info"),
ErrorSeverity::Warning => write!(f, "warning"),
ErrorSeverity::Error => write!(f, "error"),
ErrorSeverity::Critical => write!(f, "critical"),
ErrorSeverity::Fatal => write!(f, "fatal"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_creation() {
let event = TelemetryEvent::new(EventType::CommandExecuted);
assert_eq!(event.event_type, EventType::CommandExecuted);
assert!(!event.id.is_empty());
assert!(!event.session_id.is_empty());
}
#[test]
fn test_command_executed_event() {
let event = TelemetryEvent::command_executed("synthesize".to_string(), 1500);
assert_eq!(event.event_type, EventType::CommandExecuted);
assert_eq!(
event
.metadata
.get("command")
.expect("command metadata should exist"),
"synthesize"
);
assert_eq!(
event
.metadata
.get("duration_ms")
.expect("duration_ms metadata should exist"),
"1500"
);
}
#[test]
fn test_synthesis_request_event() {
let event = TelemetryEvent::synthesis_request("kokoro-en".to_string(), 100, 2000, true);
assert_eq!(event.event_type, EventType::SynthesisRequest);
assert_eq!(
event
.metadata
.get("voice")
.expect("voice metadata should exist"),
"kokoro-en"
);
assert_eq!(
event
.metadata
.get("text_length")
.expect("text_length metadata should exist"),
"100"
);
assert_eq!(
event
.metadata
.get("duration_ms")
.expect("duration_ms metadata should exist"),
"2000"
);
assert_eq!(
event
.metadata
.get("success")
.expect("success metadata should exist"),
"true"
);
}
#[test]
fn test_error_event() {
let event = TelemetryEvent::error(
"synthesis_error".to_string(),
"Failed to load model".to_string(),
ErrorSeverity::Error,
);
assert_eq!(event.event_type, EventType::Error);
assert_eq!(
event
.metadata
.get("error_type")
.expect("error_type metadata should exist"),
"synthesis_error"
);
assert_eq!(
event
.metadata
.get("message")
.expect("message metadata should exist"),
"Failed to load model"
);
assert_eq!(
event
.metadata
.get("severity")
.expect("severity metadata should exist"),
"error"
);
}
#[test]
fn test_performance_event() {
let event = TelemetryEvent::performance("rtf".to_string(), 0.25, "ratio".to_string());
assert_eq!(event.event_type, EventType::Performance);
assert_eq!(
event
.metadata
.get("metric_name")
.expect("metric_name metadata should exist"),
"rtf"
);
assert_eq!(
event
.metadata
.get("value")
.expect("value metadata should exist"),
"0.25"
);
assert_eq!(
event
.metadata
.get("unit")
.expect("unit metadata should exist"),
"ratio"
);
}
#[test]
fn test_event_builder() {
let event = TelemetryEvent::new(EventType::Custom)
.with_user_id("user123".to_string())
.with_session_id("session456".to_string())
.with_metadata("key".to_string(), "value".to_string());
assert_eq!(event.user_id.expect("user_id should be set"), "user123");
assert_eq!(event.session_id, "session456");
assert_eq!(
event
.metadata
.get("key")
.expect("key metadata should exist"),
"value"
);
}
#[test]
fn test_event_metadata() {
let mut metadata = EventMetadata::new();
assert!(metadata.is_empty());
metadata.set("key1", "value1");
metadata.set("key2", "value2");
assert_eq!(metadata.len(), 2);
assert!(metadata.contains("key1"));
assert_eq!(
metadata.get("key1").expect("key1 metadata should exist"),
"value1"
);
metadata.remove("key1");
assert_eq!(metadata.len(), 1);
assert!(!metadata.contains("key1"));
metadata.clear();
assert!(metadata.is_empty());
}
#[test]
fn test_event_type_display() {
assert_eq!(EventType::CommandExecuted.to_string(), "command_executed");
assert_eq!(EventType::SynthesisRequest.to_string(), "synthesis_request");
assert_eq!(EventType::Error.to_string(), "error");
}
#[test]
fn test_error_severity_display() {
assert_eq!(ErrorSeverity::Info.to_string(), "info");
assert_eq!(ErrorSeverity::Warning.to_string(), "warning");
assert_eq!(ErrorSeverity::Error.to_string(), "error");
assert_eq!(ErrorSeverity::Critical.to_string(), "critical");
assert_eq!(ErrorSeverity::Fatal.to_string(), "fatal");
}
}