snm_brightdata_client/extras/
logger.rs1use serde::{Deserialize, Serialize};
3use serde_json::{json, Value};
4use std::collections::HashMap;
5use tokio::fs;
6use chrono::{DateTime, Utc};
7use log::{info, error};
8use std::path::Path;
9
10#[derive(Debug, Serialize, Deserialize)]
11pub struct ExecutionLog {
12 pub execution_id: String,
13 pub tool_name: String,
14 pub timestamp: DateTime<Utc>,
15 pub request_data: RequestData,
16 pub response_data: Option<ResponseData>,
17 pub execution_metadata: ExecutionMetadata,
18 pub brightdata_details: Option<BrightDataDetails>,
19}
20
21#[derive(Debug, Serialize, Deserialize)]
22pub struct RequestData {
23 pub parameters: Value,
24 pub method: String,
25 pub user_agent: Option<String>,
26 pub ip_address: Option<String>,
27 pub headers: HashMap<String, String>,
28}
29
30#[derive(Debug, Serialize, Deserialize)]
31pub struct ResponseData {
32 pub content: Value,
33 pub status: String,
34 pub error: Option<String>,
35 pub content_length: usize,
36 pub processing_time_ms: u64,
37}
38
39#[derive(Debug, Serialize, Deserialize)]
40pub struct ExecutionMetadata {
41 pub start_time: DateTime<Utc>,
42 pub end_time: Option<DateTime<Utc>>,
43 pub duration_ms: Option<u64>,
44 pub memory_usage_mb: Option<f64>,
45 pub cpu_usage_percent: Option<f64>,
46 pub success: bool,
47}
48
49#[derive(Debug, Serialize, Deserialize)]
50pub struct BrightDataDetails {
51 pub zone_used: String,
52 pub target_url: String,
53 pub request_payload: Value,
54 pub response_status: u16,
55 pub response_headers: HashMap<String, String>,
56 pub data_format: String,
57 pub cost_estimate: Option<f64>,
58}
59
60pub struct JsonLogger {
61 log_directory: String,
62}
63
64impl JsonLogger {
65 pub fn new(log_directory: Option<String>) -> Self {
66 let log_dir = log_directory.unwrap_or_else(|| "logs".to_string());
67 Self {
68 log_directory: log_dir,
69 }
70 }
71
72 pub async fn ensure_log_directory(&self) -> Result<(), std::io::Error> {
73 if !Path::new(&self.log_directory).exists() {
74 fs::create_dir_all(&self.log_directory).await?;
75 }
76 Ok(())
77 }
78
79 pub async fn start_execution(&self, tool_name: &str, parameters: Value) -> ExecutionLog {
80 let execution_id = self.generate_execution_id(tool_name);
81 let timestamp = Utc::now();
82
83 ExecutionLog {
84 execution_id: execution_id.clone(),
85 tool_name: tool_name.to_string(),
86 timestamp,
87 request_data: RequestData {
88 parameters,
89 method: "execute".to_string(),
90 user_agent: None,
91 ip_address: None,
92 headers: HashMap::new(),
93 },
94 response_data: None,
95 execution_metadata: ExecutionMetadata {
96 start_time: timestamp,
97 end_time: None,
98 duration_ms: None,
99 memory_usage_mb: None,
100 cpu_usage_percent: None,
101 success: false,
102 },
103 brightdata_details: None,
104 }
105 }
106
107 pub async fn complete_execution(
108 &self,
109 mut log: ExecutionLog,
110 response: Value,
111 success: bool,
112 error: Option<String>,
113 ) -> Result<(), Box<dyn std::error::Error>> {
114 let end_time = Utc::now();
115 let duration = (end_time - log.execution_metadata.start_time).num_milliseconds() as u64;
116
117 log.execution_metadata.end_time = Some(end_time);
118 log.execution_metadata.duration_ms = Some(duration);
119 log.execution_metadata.success = success;
120
121 log.response_data = Some(ResponseData {
122 content_length: response.to_string().len(),
123 content: response,
124 status: if success { "success".to_string() } else { "error".to_string() },
125 error,
126 processing_time_ms: duration,
127 });
128
129 self.save_execution_log(&log).await?;
130 Ok(())
131 }
132
133 pub async fn log_brightdata_request(
134 &self,
135 execution_id: &str,
136 zone: &str,
137 url: &str,
138 payload: Value,
139 response_status: u16,
140 response_headers: HashMap<String, String>,
141 data_format: &str,
142 ) -> Result<(), Box<dyn std::error::Error>> {
143 let brightdata_log = json!({
144 "execution_id": execution_id,
145 "timestamp": Utc::now().to_rfc3339(),
146 "zone_used": zone,
147 "target_url": url,
148 "request_payload": payload,
149 "response_status": response_status,
150 "response_headers": response_headers,
151 "data_format": data_format,
152 "cost_estimate": self.estimate_cost(zone, &payload)
153 });
154
155 let filename = format!("{}/brightdata_{}.json", self.log_directory, execution_id);
156 self.write_json_file(&filename, &brightdata_log).await?;
157
158 info!("💾 BrightData request logged: {}", filename);
159 Ok(())
160 }
161
162 async fn save_execution_log(&self, log: &ExecutionLog) -> Result<(), Box<dyn std::error::Error>> {
163 self.ensure_log_directory().await?;
164
165 let main_filename = format!("{}/execution_{}.json", self.log_directory, log.execution_id);
167 let log_json = serde_json::to_value(log)?;
168 self.write_json_file(&main_filename, &log_json).await?;
169
170 let request_filename = format!("{}/request_{}.json", self.log_directory, log.execution_id);
172 let request_json = serde_json::to_value(&log.request_data)?;
173 self.write_json_file(&request_filename, &request_json).await?;
174
175 if let Some(ref response_data) = log.response_data {
177 let response_filename = format!("{}/response_{}.json", self.log_directory, log.execution_id);
178 let response_json = serde_json::to_value(response_data)?;
179 self.write_json_file(&response_filename, &response_json).await?;
180 }
181
182 self.update_daily_summary(log).await?;
184
185 info!("💾 Execution logged: {}", main_filename);
186 Ok(())
187 }
188
189 async fn write_json_file(&self, filename: &str, data: &Value) -> Result<(), Box<dyn std::error::Error>> {
190 let pretty_json = serde_json::to_string_pretty(data)?;
191 fs::write(filename, pretty_json).await?;
192 Ok(())
193 }
194
195 async fn update_daily_summary(&self, log: &ExecutionLog) -> Result<(), Box<dyn std::error::Error>> {
196 let date = log.timestamp.format("%Y-%m-%d");
197 let summary_filename = format!("{}/daily_summary_{}.json", self.log_directory, date);
198
199 let mut summary = if Path::new(&summary_filename).exists() {
201 let content = fs::read_to_string(&summary_filename).await?;
202 serde_json::from_str::<Value>(&content).unwrap_or_else(|_| json!({}))
203 } else {
204 json!({
205 "date": date.to_string(),
206 "total_executions": 0,
207 "successful_executions": 0,
208 "failed_executions": 0,
209 "tools_used": {},
210 "total_processing_time_ms": 0,
211 "average_processing_time_ms": 0.0
212 })
213 };
214
215 summary["total_executions"] = json!(summary["total_executions"].as_u64().unwrap_or(0) + 1);
217
218 if log.execution_metadata.success {
219 summary["successful_executions"] = json!(summary["successful_executions"].as_u64().unwrap_or(0) + 1);
220 } else {
221 summary["failed_executions"] = json!(summary["failed_executions"].as_u64().unwrap_or(0) + 1);
222 }
223
224 let mut tools_used = summary["tools_used"].as_object().cloned().unwrap_or_default();
226 let current_count = tools_used.get(&log.tool_name)
227 .and_then(|v| v.as_u64())
228 .unwrap_or(0);
229 tools_used.insert(log.tool_name.clone(), json!(current_count + 1));
230 summary["tools_used"] = json!(tools_used);
231
232 if let Some(duration) = log.execution_metadata.duration_ms {
234 let total_time = summary["total_processing_time_ms"].as_u64().unwrap_or(0) + duration;
235 summary["total_processing_time_ms"] = json!(total_time);
236
237 let total_executions = summary["total_executions"].as_u64().unwrap_or(1);
238 let avg_time = total_time as f64 / total_executions as f64;
239 summary["average_processing_time_ms"] = json!(avg_time);
240 }
241
242 summary["last_updated"] = json!(Utc::now().to_rfc3339());
243
244 self.write_json_file(&summary_filename, &summary).await?;
245 Ok(())
246 }
247
248 fn generate_execution_id(&self, tool_name: &str) -> String {
249 let timestamp = Utc::now().format("%Y%m%d_%H%M%S%.3f");
250 format!("{}_{}", tool_name, timestamp)
251 }
252
253 fn estimate_cost(&self, zone: &str, _payload: &Value) -> Option<f64> {
254 match zone {
256 z if z.contains("serp") => Some(0.01), z if z.contains("browser") => Some(0.05), _ => Some(0.001), }
260 }
261
262 pub async fn get_execution_logs_by_tool(&self, tool_name: &str) -> Result<Vec<ExecutionLog>, Box<dyn std::error::Error>> {
264 let mut logs = Vec::new();
265 let mut dir = fs::read_dir(&self.log_directory).await?;
266
267 while let Some(entry) = dir.next_entry().await? {
268 let filename = entry.file_name().to_string_lossy().to_string();
269 if filename.starts_with("execution_") && filename.contains(tool_name) {
270 let content = fs::read_to_string(entry.path()).await?;
271 if let Ok(log) = serde_json::from_str::<ExecutionLog>(&content) {
272 logs.push(log);
273 }
274 }
275 }
276
277 logs.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
278 Ok(logs)
279 }
280
281 pub async fn get_daily_summary(&self, date: &str) -> Result<Option<Value>, Box<dyn std::error::Error>> {
282 let summary_filename = format!("{}/daily_summary_{}.json", self.log_directory, date);
283
284 if Path::new(&summary_filename).exists() {
285 let content = fs::read_to_string(summary_filename).await?;
286 let summary = serde_json::from_str::<Value>(&content)?;
287 Ok(Some(summary))
288 } else {
289 Ok(None)
290 }
291 }
292}
293
294lazy_static::lazy_static! {
296 pub static ref JSON_LOGGER: JsonLogger = JsonLogger::new(
297 std::env::var("BRIGHTDATA_JSON_LOG_PATH").ok()
298 .or_else(|| std::env::var("LOG_DIRECTORY").ok())
299 );
300}