use std::{
fs::{File, OpenOptions},
io::{self, Write as _},
path::Path,
sync::{Arc, Mutex},
time::{SystemTime, UNIX_EPOCH},
};
#[derive(Clone)]
pub struct BodyLogger {
file: Arc<Mutex<File>>,
}
impl BodyLogger {
pub fn from_env() -> io::Result<Option<Self>> {
match std::env::var("ASIMOV_PROXY_LOG_FILE") {
Ok(path) if !path.is_empty() => Self::open(path.as_ref()).map(Some),
_ => Ok(None),
}
}
pub fn open(path: &Path) -> io::Result<Self> {
let file = OpenOptions::new().create(true).append(true).open(path)?;
Ok(Self {
file: Arc::new(Mutex::new(file)),
})
}
pub fn log_request_body(&self, data: &[u8]) {
self.log("request body", data);
}
pub fn log_response_chunk(&self, data: &[u8]) {
self.log("response body chunk", data);
}
fn log(&self, label: &str, data: &[u8]) {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_secs())
.unwrap_or_default();
let Ok(mut file) = self.file.lock() else {
return; };
let _ = writeln!(
file,
"--- {} @{} ({} bytes) ---",
label,
timestamp,
data.len()
);
let _ = file.write_all(data);
let _ = writeln!(file);
}
}