use serde::Serialize;
use serde_json::Value;
use std::collections::VecDeque;
pub const DEFAULT_LOG_CAPACITY: usize = 4096;
#[derive(Debug, Clone, Serialize)]
pub struct AgentLogEntry {
pub seq: u64,
pub ts_ms: f64,
pub window: Option<String>,
pub category: String,
pub payload: Value,
}
#[derive(Debug, Clone)]
pub struct AgentLog {
entries: VecDeque<AgentLogEntry>,
next_seq: u64,
capacity: usize,
}
impl AgentLog {
pub fn new(capacity: usize) -> Self {
Self {
entries: VecDeque::with_capacity(capacity),
next_seq: 1,
capacity,
}
}
pub fn push(
&mut self,
ts_ms: f64,
window: Option<String>,
category: impl Into<String>,
payload: Value,
) -> u64 {
let seq = self.next_seq;
self.next_seq = self.next_seq.wrapping_add(1);
if self.entries.len() == self.capacity {
self.entries.pop_front();
}
self.entries.push_back(AgentLogEntry {
seq,
ts_ms,
window,
category: category.into(),
payload,
});
seq
}
pub fn head_seq(&self) -> u64 {
self.entries.back().map(|e| e.seq).unwrap_or(0)
}
pub fn since(
&self,
since: u64,
limit: usize,
prefix: Option<&str>,
) -> Vec<AgentLogEntry> {
self.entries
.iter()
.filter(|e| e.seq > since)
.filter(|e| match prefix {
Some(p) => e.category.starts_with(p),
None => true,
})
.take(limit)
.cloned()
.collect()
}
pub fn tail(&self, n: usize) -> Vec<AgentLogEntry> {
let len = self.entries.len();
let start = len.saturating_sub(n);
self.entries.iter().skip(start).cloned().collect()
}
pub fn snapshot(&self) -> Vec<AgentLogEntry> {
self.entries.iter().cloned().collect()
}
}
impl Default for AgentLog {
fn default() -> Self {
Self::new(DEFAULT_LOG_CAPACITY)
}
}