#![warn(clippy::all, clippy::pedantic)] use chrono::{DateTime, Utc}; use serde::Serialize; use std::fs::OpenOptions; use std::io::{BufWriter, Write}; use std::path::Path; use std::sync::{LazyLock, Mutex}; use url::Url; #[derive(Serialize)] pub struct ErrorEntry { pub timestamp: DateTime, pub url: String, pub error_type: String, pub message: String, #[serde(skip_serializing_if = "Option::is_none")] pub status_code: Option, } static LOG_WRITER: LazyLock>>> = LazyLock::new(|| Mutex::new(None)); pub fn log_error( log_path: &Path, url: &Url, error_type: &str, message: &str, status_code: Option, ) { let entry = ErrorEntry { timestamp: Utc::now(), url: url.to_string(), error_type: error_type.to_string(), message: message.to_string(), status_code, }; let line = match serde_json::to_string(&entry) { Ok(json) => json + "\n", Err(e) => { log::error!("Failed to serialize error entry: {e}"); return; } }; { let mut guard = LOG_WRITER.lock().unwrap(); match guard.as_mut() { Some(writer) => { if let Err(e) = writer.write_all(line.as_bytes()) { log::error!("Failed to write error log: {e}"); } } None => { if let Ok(file) = OpenOptions::new().append(true).create(true).open(log_path) { let mut writer = BufWriter::new(file); let _ = writer.write_all(line.as_bytes()); let _ = writer.flush(); } else { log::error!("Failed to open error log file: {}", log_path.display()); return; } } } } log::info!("LOGGED ERROR [{error_type}]: {message}"); }