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() || path.extension() != Some(std::ffi::OsStr::new("json"))
37 {
38 path.set_extension("json");
39 }
40 } else {
41 if path.extension().is_none() || path.extension() != Some(std::ffi::OsStr::new("jsonl"))
43 {
44 path.set_extension("jsonl");
45 }
46 }
47
48 Self {
49 buffer: Vec::with_capacity(buffer_size),
50 buffer_size,
51 file_path: path,
52 final_format_json,
53 flush_failures: 0,
54 max_flush_failures: 10,
55 }
56 }
57
58 pub fn add(&mut self, data: T) -> Result<(), NanonisError> {
59 self.buffer.push(data);
60
61 if self.buffer.len() >= self.buffer_size {
62 self.flush()?;
63 }
64
65 Ok(())
66 }
67
68 pub fn flush(&mut self) -> Result<(), NanonisError> {
69 if self.buffer.is_empty() {
70 return Ok(());
71 }
72
73 let file_result = std::fs::OpenOptions::new()
75 .create(true)
76 .append(true)
77 .open(&self.file_path);
78
79 let file = match file_result {
80 Ok(f) => f,
81 Err(e) => {
82 self.flush_failures += 1;
83 log::error!(
84 "Flush failure {}/{}: Failed to open log file: {}",
85 self.flush_failures,
86 self.max_flush_failures,
87 e
88 );
89
90 if self.flush_failures > 0 && self.flush_failures % 3 == 0 {
92 log::warn!(
93 "Experiencing intermittent flush failures ({}/{})",
94 self.flush_failures,
95 self.max_flush_failures
96 );
97 }
98
99 if self.flush_failures >= self.max_flush_failures {
100 return Err(NanonisError::Io {
101 source: e,
102 context: format!(
103 "Too many consecutive flush failures ({}) for {:?}",
104 self.max_flush_failures, self.file_path
105 ),
106 });
107 }
108
109 return Ok(());
111 }
112 };
113
114 let mut writer = std::io::BufWriter::new(file);
115
116 let write_result = (|| {
118 for data in &self.buffer {
119 let json_line = serde_json::to_string(data)?;
120 writeln!(writer, "{}", json_line)?;
121 }
122 writer.flush()?;
123 Ok::<(), NanonisError>(())
124 })();
125
126 match write_result {
127 Ok(_) => {
128 self.flush_failures = 0; self.buffer.clear();
130 debug!("Logger flushed successfully to file");
131 Ok(())
132 }
133 Err(e) => {
134 self.flush_failures += 1;
135 log::error!(
136 "Flush failure {}/{}: Write error: {}",
137 self.flush_failures,
138 self.max_flush_failures,
139 e
140 );
141
142 if self.flush_failures > 0 && self.flush_failures % 3 == 0 {
144 log::warn!(
145 "Experiencing intermittent flush failures ({}/{})",
146 self.flush_failures,
147 self.max_flush_failures
148 );
149 }
150
151 if self.flush_failures >= self.max_flush_failures {
152 return Err(NanonisError::Io {
153 source: std::io::Error::other(e.to_string()),
154 context: format!(
155 "Too many consecutive flush failures ({}) for {:?}",
156 self.max_flush_failures, self.file_path
157 ),
158 });
159 }
160
161 Ok(())
163 }
164 }
165 }
166
167 pub fn finalize_as_json(&mut self) -> Result<(), NanonisError> {
169 if !self.final_format_json {
170 return Ok(()); }
172
173 self.flush()?;
175
176 let content =
178 std::fs::read_to_string(&self.file_path).map_err(|source| NanonisError::Io {
179 source,
180 context: format!("Could not read JSONL file at {:?}", self.file_path),
181 })?;
182
183 let mut entries = Vec::new();
184 for line in content.lines() {
185 if !line.trim().is_empty() {
186 let data: T = serde_json::from_str(line)?;
187 entries.push(data);
188 }
189 }
190
191 let json_output = serde_json::to_string_pretty(&entries)?;
193 std::fs::write(&self.file_path, json_output).map_err(|source| NanonisError::Io {
194 source,
195 context: format!("Could not write JSON file at {:?}", self.file_path),
196 })?;
197
198 debug!(
199 "Converted {} entries from JSONL to JSON format",
200 entries.len()
201 );
202 Ok(())
203 }
204
205 pub fn len(&self) -> usize {
206 self.buffer.len()
207 }
208
209 pub fn is_empty(&self) -> bool {
210 self.buffer.len() == 0
211 }
212}
213
214impl<T> Drop for Logger<T>
215where
216 T: Serialize + Clone + DeserializeOwned,
217{
218 fn drop(&mut self) {
219 let _ = self.flush();
220 let _ = self.finalize_as_json();
221 }
222}