1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use std::fs::File;
use std::fs::OpenOptions;
use std::io;
use std::io::Write;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::Mutex;
use tracing_subscriber::fmt::MakeWriter;
/// A lazy file writer that only creates the file on first write
pub(super) struct LazyFileWriter {
path: PathBuf,
file: Arc<Mutex<Option<File>>>,
}
impl LazyFileWriter {
pub(super) fn new(path: PathBuf) -> Self {
Self {
path,
file: Arc::new(Mutex::new(None)),
}
}
}
impl Clone for LazyFileWriter {
fn clone(&self) -> Self {
Self {
path: self.path.clone(),
file: Arc::clone(&self.file),
}
}
}
/// Writer instance that lazily creates the file on first write
pub(super) struct LazyWriter {
path: PathBuf,
file: Arc<Mutex<Option<File>>>,
}
impl Write for LazyWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut file_guard = self
.file
.lock()
.map_err(|_| io::Error::other("Mutex poisoned"))?;
// Create file on first write or if file was deleted
if file_guard.is_none() || !self.path.exists() {
let file = OpenOptions::new()
.create(true)
.append(true)
.open(&self.path)?;
*file_guard = Some(file);
}
// Try to write to the file
match file_guard.as_mut() {
Some(file) => {
if let Ok(bytes) = file.write(buf) {
drop(file_guard);
Ok(bytes)
} else {
// Write failed, file handle might be stale, recreate
let mut new_file = OpenOptions::new()
.create(true)
.append(true)
.open(&self.path)?;
let bytes = new_file.write(buf)?;
*file_guard = Some(new_file);
drop(file_guard);
Ok(bytes)
}
},
None => {
// This should not happen due to the check above, but handle it
Err(io::Error::other("File handle unexpectedly None"))
},
}
}
fn flush(&mut self) -> io::Result<()> {
let mut file_guard = self
.file
.lock()
.map_err(|_| io::Error::other("Mutex poisoned"))?;
// Check if file was deleted
if file_guard.is_some() && !self.path.exists() {
// File was deleted, clear the stale handle
*file_guard = None;
drop(file_guard);
return Ok(()); // Nothing to flush
}
// Try to flush
if let Some(file) = file_guard.as_mut() {
if matches!(file.flush(), Ok(())) {
// Flush succeeded
} else {
// Flush failed, might be stale handle
if self.path.exists() {
// File exists but handle is stale, recreate
let new_file = OpenOptions::new()
.create(true)
.append(true)
.open(&self.path)?;
*file_guard = Some(new_file);
} else {
// File doesn't exist, clear handle
*file_guard = None;
}
}
}
drop(file_guard);
Ok(())
}
}
impl<'a> MakeWriter<'a> for LazyFileWriter {
type Writer = LazyWriter;
fn make_writer(&'a self) -> Self::Writer {
LazyWriter {
path: self.path.clone(),
file: Arc::clone(&self.file),
}
}
}