1use crate::database::AnalyticsDatabase;
4use crate::error::Result;
5use crate::models::*;
6use std::io::Write;
7
8impl AnalyticsDatabase {
9 pub async fn export_to_csv<W: Write>(
11 &self,
12 writer: &mut W,
13 filter: &AnalyticsFilter,
14 ) -> Result<usize> {
15 writeln!(
17 writer,
18 "timestamp,protocol,method,endpoint,status_code,request_count,error_count,avg_latency_ms,p95_latency_ms,bytes_sent,bytes_received"
19 )?;
20
21 let aggregates = self.get_minute_aggregates(filter).await?;
22
23 for agg in &aggregates {
24 let avg_latency = if agg.request_count > 0 {
25 agg.latency_sum / agg.request_count as f64
26 } else {
27 0.0
28 };
29
30 writeln!(
31 writer,
32 "{},{},{},{},{},{},{},{:.2},{:.2},{},{}",
33 agg.timestamp,
34 agg.protocol,
35 agg.method.as_deref().unwrap_or(""),
36 agg.endpoint.as_deref().unwrap_or(""),
37 agg.status_code.unwrap_or(0),
38 agg.request_count,
39 agg.error_count,
40 avg_latency,
41 agg.latency_p95.unwrap_or(0.0),
42 agg.bytes_sent,
43 agg.bytes_received
44 )?;
45 }
46
47 Ok(aggregates.len())
48 }
49
50 pub async fn export_to_json(&self, filter: &AnalyticsFilter) -> Result<String> {
52 let aggregates = self.get_minute_aggregates(filter).await?;
53 let json = serde_json::to_string_pretty(&aggregates)?;
54 Ok(json)
55 }
56
57 pub async fn export_endpoints_to_csv<W: Write>(
59 &self,
60 writer: &mut W,
61 workspace_id: Option<&str>,
62 limit: i64,
63 ) -> Result<usize> {
64 writeln!(
65 writer,
66 "endpoint,protocol,method,total_requests,total_errors,error_rate,avg_latency_ms,p95_latency_ms,bytes_sent,bytes_received"
67 )?;
68
69 let endpoints = self.get_top_endpoints(limit, workspace_id).await?;
70
71 for ep in &endpoints {
72 let error_rate = if ep.total_requests > 0 {
73 (ep.total_errors as f64 / ep.total_requests as f64) * 100.0
74 } else {
75 0.0
76 };
77
78 writeln!(
79 writer,
80 "{},{},{},{},{},{:.2},{:.2},{:.2},{},{}",
81 ep.endpoint,
82 ep.protocol,
83 ep.method.as_deref().unwrap_or(""),
84 ep.total_requests,
85 ep.total_errors,
86 error_rate,
87 ep.avg_latency_ms.unwrap_or(0.0),
88 ep.p95_latency_ms.unwrap_or(0.0),
89 ep.total_bytes_sent,
90 ep.total_bytes_received
91 )?;
92 }
93
94 Ok(endpoints.len())
95 }
96
97 pub async fn export_errors_to_csv<W: Write>(
99 &self,
100 writer: &mut W,
101 filter: &AnalyticsFilter,
102 limit: i64,
103 ) -> Result<usize> {
104 writeln!(
105 writer,
106 "timestamp,protocol,method,endpoint,status_code,error_type,error_category,error_message,client_ip,trace_id"
107 )?;
108
109 let errors = self.get_recent_errors(limit, filter).await?;
110
111 for err in &errors {
112 writeln!(
113 writer,
114 "{},{},{},{},{},{},{},{},{},{}",
115 err.timestamp,
116 err.protocol,
117 err.method.as_deref().unwrap_or(""),
118 err.endpoint.as_deref().unwrap_or(""),
119 err.status_code.unwrap_or(0),
120 err.error_type.as_deref().unwrap_or(""),
121 err.error_category.as_deref().unwrap_or(""),
122 err.error_message.as_deref().unwrap_or(""),
123 err.client_ip.as_deref().unwrap_or(""),
124 err.trace_id.as_deref().unwrap_or("")
125 )?;
126 }
127
128 Ok(errors.len())
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use std::path::Path;
136
137 #[tokio::test]
138 async fn test_export_to_csv() {
139 let db = AnalyticsDatabase::new(Path::new(":memory:")).await.unwrap();
140 db.run_migrations().await.unwrap();
141
142 let mut buffer = Vec::new();
143 let filter = AnalyticsFilter::default();
144
145 let count = db.export_to_csv(&mut buffer, &filter).await.unwrap();
146 assert_eq!(count, 0); let csv = String::from_utf8(buffer).unwrap();
149 assert!(csv.contains("timestamp,protocol"));
150 }
151}