vtcode_core/utils/
trace_writer.rs1use std::fs::{File, OpenOptions};
8use std::io::{BufWriter, Write};
9use std::path::Path;
10use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
11
12use anyhow::{Context, Result};
13
14const BUF_CAPACITY: usize = 64 * 1024;
17
18static GLOBAL_WRITER: OnceLock<FlushableWriter> = OnceLock::new();
22
23#[derive(Clone)]
26pub struct FlushableWriter {
27 inner: Arc<Mutex<BufWriter<File>>>,
28}
29
30impl FlushableWriter {
31 pub fn open(path: &Path) -> Result<Self> {
33 let file = OpenOptions::new()
34 .create(true)
35 .append(true)
36 .open(path)
37 .with_context(|| format!("Failed to open trace log file: {}", path.display()))?;
38 let writer = BufWriter::with_capacity(BUF_CAPACITY, file);
39 let flushable = Self {
40 inner: Arc::new(Mutex::new(writer)),
41 };
42 let _ = GLOBAL_WRITER.set(flushable.clone());
44 vtcode_commons::trace_flush::register_trace_flush_hook(flush_trace_log);
47 Ok(flushable)
48 }
49
50 pub fn flush(&self) {
52 let _ = self.flush_locked();
53 }
54
55 fn lock_writer(&self) -> std::io::Result<MutexGuard<'_, BufWriter<File>>> {
56 self.inner
57 .lock()
58 .map_err(|_| std::io::Error::other("trace writer lock poisoned"))
59 }
60
61 fn flush_locked(&self) -> std::io::Result<()> {
62 let mut guard = self.lock_writer()?;
63 guard.flush()
64 }
65}
66
67impl Write for FlushableWriter {
68 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
69 let mut guard = self.lock_writer()?;
70 guard.write(buf)
71 }
72
73 fn flush(&mut self) -> std::io::Result<()> {
74 self.flush_locked()
75 }
76}
77
78pub fn flush_trace_log() {
83 if let Some(writer) = GLOBAL_WRITER.get() {
84 writer.flush();
85 }
86}