//! Bounded observation of this process's local HTTP API, never external traffic. use std::{ collections::VecDeque, sync::{Arc, Mutex}, time::{Instant, SystemTime, UNIX_EPOCH}, }; pub type Activity = Arc>; pub const BODY_PREVIEW: usize = 32 * 1024; const MAX_RECORDS: usize = 80; #[derive(Clone, Debug)] pub struct Exchange { pub id: u64, pub method: String, pub path: String, pub origin: String, pub timestamp: String, pub request: String, pub response: String, pub status: Option, pub elapsed_ms: u128, pub request_headers: String, pub response_headers: String, started: Instant, } #[derive(Debug, Default)] pub struct ActivityLog { pub records: VecDeque, next_id: u64, } impl ActivityLog { pub fn begin(&mut self, method: String, path: String, origin: String, headers: String) -> u64 { self.next_id += 1; let id = self.next_id; let seconds = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_secs() % 86400; self.records.push_back(Exchange { id, method, path: preview(&path), origin, timestamp: format!( "{:02}:{:02}:{:02} UTC", seconds / 3600, seconds / 60 % 60, seconds % 60 ), request: String::new(), response: String::new(), status: None, elapsed_ms: 0, request_headers: preview(&headers), response_headers: String::new(), started: Instant::now(), }); while self.records.len() > MAX_RECORDS { self.records.pop_front(); } id } pub fn body(&mut self, id: u64, body: &str) { if let Some(record) = self.records.iter_mut().find(|r| r.id == id) { record.request = preview(body); } } pub fn finish(&mut self, id: u64, status: u16, body: &str, headers: String) { if let Some(record) = self.records.iter_mut().find(|r| r.id == id) { record.status = Some(status); record.response = preview(body); record.elapsed_ms = record.started.elapsed().as_millis(); record.response_headers = preview(&headers); } } } pub fn preview(value: &str) -> String { if value.len() <= BODY_PREVIEW { return value.to_owned(); } let mut end = BODY_PREVIEW; while !value.is_char_boundary(end) { end -= 1; } format!("{}\n[preview truncated at 32 KiB]", &value[..end]) }