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 { inner: Arc::new(Mutex::new(writer)) };
40 let _ = GLOBAL_WRITER.set(flushable.clone());
42 vtcode_commons::trace_flush::register_trace_flush_hook(flush_trace_log);
45 Ok(flushable)
46 }
47
48 pub fn flush(&self) {
50 let _ = self.flush_locked();
51 }
52
53 fn lock_writer(&self) -> std::io::Result<MutexGuard<'_, BufWriter<File>>> {
54 self.inner
55 .lock()
56 .map_err(|e| std::io::Error::other(format!("trace writer lock poisoned: {e}")))
57 }
58
59 fn flush_locked(&self) -> std::io::Result<()> {
60 let mut guard = self.lock_writer()?;
61 guard.flush()
62 }
63}
64
65impl Write for FlushableWriter {
66 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
67 let mut guard = self.lock_writer()?;
68 guard.write(buf)
69 }
70
71 fn flush(&mut self) -> std::io::Result<()> {
72 self.flush_locked()
73 }
74}
75
76pub fn flush_trace_log() {
81 if let Some(writer) = GLOBAL_WRITER.get() {
82 writer.flush();
83 }
84}