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