use std::fs::{File, OpenOptions};
use std::io::{self, Write};
use std::path::PathBuf;
use std::sync::{Mutex, OnceLock};
pub(crate) struct LogSink {
file: OnceLock<Mutex<OpenFile>>,
filename: &'static str,
}
struct OpenFile {
path: PathBuf,
file: File,
}
impl LogSink {
fn init(&self) {
if let Some((path, file)) = try_create(self.filename) {
let _ = self.file.set(Mutex::new(OpenFile { path, file }));
}
}
pub(crate) fn is_active(&self) -> bool {
self.file.get().is_some()
}
pub(crate) fn write_event(&self, text: &str) {
if let Some(mutex) = self.file.get()
&& let Ok(mut open) = mutex.lock()
{
let body = text.trim_end_matches('\n');
let _ = writeln!(open.file, "{}", body);
let _ = open.file.flush();
}
}
pub(crate) fn path(&self) -> Option<PathBuf> {
self.file
.get()
.and_then(|mutex| mutex.lock().ok().map(|open| open.path.clone()))
}
fn writer(&'static self) -> SinkWriter {
SinkWriter {
sink: self,
buf: Vec::new(),
}
}
}
pub(crate) static TRACE: LogSink = LogSink {
file: OnceLock::new(),
filename: "trace.log",
};
pub(crate) static SUBPROCESS: LogSink = LogSink {
file: OnceLock::new(),
filename: "subprocess.log",
};
pub(crate) fn init() {
TRACE.init();
SUBPROCESS.init();
}
pub(crate) struct SinkWriter {
sink: &'static LogSink,
buf: Vec<u8>,
}
impl io::Write for SinkWriter {
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
self.buf.extend_from_slice(data);
Ok(data.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl Drop for SinkWriter {
fn drop(&mut self) {
let text = String::from_utf8_lossy(&self.buf);
self.sink.write_event(&text);
}
}
pub(crate) struct TraceMakeWriter;
impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for TraceMakeWriter {
type Writer = SinkWriter;
fn make_writer(&'a self) -> SinkWriter {
TRACE.writer()
}
}
pub(crate) struct SubprocessMakeWriter;
impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for SubprocessMakeWriter {
type Writer = SinkWriter;
fn make_writer(&'a self) -> SinkWriter {
SUBPROCESS.writer()
}
}
fn try_create(filename: &str) -> Option<(PathBuf, File)> {
let repo = worktrunk::git::Repository::current().ok()?;
let log_dir = repo.wt_logs_dir();
std::fs::create_dir_all(&log_dir).ok()?;
let path = log_dir.join(filename);
let file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&path)
.ok()?;
Some((path, file))
}
#[cfg(test)]
mod tests {
use std::io::Write;
use super::{LogSink, SinkWriter};
#[test]
fn sink_writer_flush_is_a_no_op() {
static SINK: LogSink = LogSink {
file: std::sync::OnceLock::new(),
filename: "test-flush.log",
};
let mut w = SinkWriter {
sink: &SINK,
buf: Vec::new(),
};
assert!(w.flush().is_ok());
}
}