1use log::debug;
2use serde::{de::DeserializeOwned, Serialize};
3use std::{io::Write, path::PathBuf};
4
5use crate::NanonisError;
6
7#[derive(Debug)]
10pub struct Logger<T>
11where
12 T: Serialize + Clone + DeserializeOwned,
13{
14 buffer: Vec<T>,
15 buffer_size: usize,
16 file_path: PathBuf,
17 final_format_json: bool, flush_failures: usize,
19 max_flush_failures: usize,
20}
21
22impl<T> Logger<T>
23where
24 T: Serialize + Clone + DeserializeOwned,
25{
26 pub fn new<P: Into<PathBuf>>(
27 file_path: P,
28 buffer_size: usize,
29 final_format_json: bool,
30 ) -> Self {
31 let mut path = file_path.into();
32
33 if final_format_json {
35 if path.extension().is_none()
37 || path.extension() != Some(std::ffi::OsStr::new("json"))
38 {
39 path.set_extension("json");
40 }
41 } else {
42 if path.extension().is_none()
44 || path.extension() != Some(std::ffi::OsStr::new("jsonl"))
45 {
46 path.set_extension("jsonl");
47 }
48 }
49
50 Self {
51 buffer: Vec::with_capacity(buffer_size),
52 buffer_size,
53 file_path: path,
54 final_format_json,
55 flush_failures: 0,
56 max_flush_failures: 10,
57 }
58 }
59
60 pub fn add(&mut self, data: T) -> Result<(), NanonisError> {
61 self.buffer.push(data);
62
63 if self.buffer.len() >= self.buffer_size {
64 self.flush()?;
65 }
66
67 Ok(())
68 }
69
70 pub fn flush(&mut self) -> Result<(), NanonisError> {
71 if self.buffer.is_empty() {
72 return Ok(());
73 }
74
75 let file_result = std::fs::OpenOptions::new()
77 .create(true)
78 .append(true)
79 .open(&self.file_path);
80
81 let file = match file_result {
82 Ok(f) => f,
83 Err(e) => {
84 self.flush_failures += 1;
85 log::error!(
86 "Flush failure {}/{}: Failed to open log file: {}",
87 self.flush_failures,
88 self.max_flush_failures,
89 e
90 );
91
92 if self.flush_failures > 0 && self.flush_failures % 3 == 0 {
94 log::warn!(
95 "Experiencing intermittent flush failures ({}/{})",
96 self.flush_failures,
97 self.max_flush_failures
98 );
99 }
100
101 if self.flush_failures >= self.max_flush_failures {
102 return Err(NanonisError::Io {
103 source: e,
104 context: format!(
105 "Too many consecutive flush failures ({}) for {:?}",
106 self.max_flush_failures, self.file_path
107 ),
108 });
109 }
110
111 return Ok(());
113 }
114 };
115
116 let mut writer = std::io::BufWriter::new(file);
117
118 let write_result = (|| {
120 for data in &self.buffer {
121 let json_line = serde_json::to_string(data)?;
122 writeln!(writer, "{}", json_line)?;
123 }
124 writer.flush()?;
125 Ok::<(), NanonisError>(())
126 })();
127
128 match write_result {
129 Ok(_) => {
130 self.flush_failures = 0; self.buffer.clear();
132 debug!("Logger flushed successfully to file");
133 Ok(())
134 }
135 Err(e) => {
136 self.flush_failures += 1;
137 log::error!(
138 "Flush failure {}/{}: Write error: {}",
139 self.flush_failures,
140 self.max_flush_failures,
141 e
142 );
143
144 if self.flush_failures > 0 && self.flush_failures % 3 == 0 {
146 log::warn!(
147 "Experiencing intermittent flush failures ({}/{})",
148 self.flush_failures,
149 self.max_flush_failures
150 );
151 }
152
153 if self.flush_failures >= self.max_flush_failures {
154 return Err(NanonisError::Io {
155 source: std::io::Error::other(e.to_string()),
156 context: format!(
157 "Too many consecutive flush failures ({}) for {:?}",
158 self.max_flush_failures, self.file_path
159 ),
160 });
161 }
162
163 Ok(())
165 }
166 }
167 }
168
169 pub fn finalize_as_json(&mut self) -> Result<(), NanonisError> {
171 if !self.final_format_json {
172 return Ok(()); }
174
175 self.flush()?;
177
178 let content =
180 std::fs::read_to_string(&self.file_path).map_err(|source| {
181 NanonisError::Io {
182 source,
183 context: format!(
184 "Could not read JSONL file at {:?}",
185 self.file_path
186 ),
187 }
188 })?;
189
190 let mut entries = Vec::new();
191 for line in content.lines() {
192 if !line.trim().is_empty() {
193 let data: T = serde_json::from_str(line)?;
194 entries.push(data);
195 }
196 }
197
198 let json_output = serde_json::to_string_pretty(&entries)?;
200 std::fs::write(&self.file_path, json_output).map_err(|source| {
201 NanonisError::Io {
202 source,
203 context: format!(
204 "Could not write JSON file at {:?}",
205 self.file_path
206 ),
207 }
208 })?;
209
210 debug!(
211 "Converted {} entries from JSONL to JSON format",
212 entries.len()
213 );
214 Ok(())
215 }
216
217 pub fn len(&self) -> usize {
218 self.buffer.len()
219 }
220
221 pub fn is_empty(&self) -> bool {
222 self.buffer.len() == 0
223 }
224}
225
226impl<T> Drop for Logger<T>
227where
228 T: Serialize + Clone + DeserializeOwned,
229{
230 fn drop(&mut self) {
231 let _ = self.flush();
232 let _ = self.finalize_as_json();
233 }
234}