use std::fs::File;
use std::io::{Result, Write};
use std::path::Path;
use tracing_appender::{
non_blocking,
non_blocking::{NonBlocking, WorkerGuard},
};
use crate::agent::AgentEvent;
pub(super) struct JsonlWriter {
writer: NonBlocking,
_guard: WorkerGuard,
}
impl JsonlWriter {
pub(super) fn open(file: &Path) -> Result<Self> {
let (writer, _guard) = non_blocking(File::options().append(true).create(true).open(file)?);
Ok(Self { writer, _guard })
}
pub(super) fn write(&mut self, event: &AgentEvent) -> Result<usize> {
let json = serde_json::to_vec(event)?;
let len = json.len() + 1;
self.writer.write_all(&json)?;
self.writer.write_all(b"\n")?;
Ok(len)
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn appends_events_as_json_lines_without_truncating_existing_file() {
let temp = tempfile::tempdir().expect("tempdir");
let path = temp.path().join("session.jsonl");
std::fs::write(&path, "{\"kind\":\"message\",\"text\":\"before\"}\n").expect("seed JSONL file");
{
let mut writer = JsonlWriter::open(&path).expect("writer opens");
assert!(
writer
.write(&AgentEvent::SessionStarted {
session_id: "session-1".into(),
})
.expect("session-started event writes")
> 0
);
assert!(writer.write(&AgentEvent::Completed).expect("completed event writes") > 0);
}
let lines = std::fs::read_to_string(&path)
.expect("JSONL file reads")
.lines()
.map(|line| serde_json::from_str(line).expect("line is JSON"))
.collect::<Vec<serde_json::Value>>();
assert_eq!(
lines,
vec![
json!({
"kind": "message",
"text": "before"
}),
json!({
"kind": "session_started",
"session_id": "session-1"
}),
json!({
"kind": "completed"
}),
]
);
}
}