packxel_utils/logging/
mod.rs1use std::{env::temp_dir, fs::File, io::Write, str::FromStr};
25
26use colored::{ColoredString, Colorize};
27use log::{warn, Level};
28
29use crate::initializer::PackxelInitializer;
30
31#[derive(Debug)]
34struct FileWriter {
35 file: File,
36}
37
38#[derive(Debug, Default)]
39pub struct PackxelLogger {
41 log_level: Option<Level>,
42 print_lines: bool,
43 file_writer: Option<FileWriter>,
44 log_file_path: String,
45}
46
47#[derive(Debug)]
48pub struct PackxelLoggerBuilder {
57 log_level: Option<Level>,
58 print_lines: bool,
59 initializer: Option<PackxelInitializer>,
60 file_writer: bool,
61}
62
63impl PackxelLoggerBuilder {
64 fn new(initializer: Option<PackxelInitializer>) -> Self {
65 Self {
66 log_level: None,
67 print_lines: false,
68 initializer,
69 file_writer: true,
70 }
71 }
72
73 fn with_default_log_level(mut self) -> PackxelLoggerBuilder {
74 let log_level = get_level_from_env();
75 self.log_level = log_level;
76 self
77 }
78
79 pub fn with_log_level(mut self, log_level: Level) -> PackxelLoggerBuilder {
80 self.log_level = Some(log_level);
81 self
82 }
83
84 pub fn with_print_lines(mut self, print_lines: bool) -> PackxelLoggerBuilder {
85 self.print_lines = print_lines;
86 self
87 }
88
89 pub fn with_file_writer(mut self, file_writer: bool) -> PackxelLoggerBuilder {
90 self.file_writer = file_writer;
91 self
92 }
93
94 fn try_build(self) -> std::io::Result<PackxelLogger> {
95 let file_writer;
96 let mut log_file_path;
97 if self.initializer.is_some() {
98 let initializer = self.initializer.unwrap();
99 log_file_path = initializer.get_log_file();
100 } else {
101 log_file_path = temp_dir();
102 log_file_path.push("data.log");
103 }
104
105 let file = File::options()
106 .append(true)
107 .create(true)
108 .open(&log_file_path)
109 .expect("Unable to open file");
110
111 let log_file_path = log_file_path
112 .to_str()
113 .expect("Failed to get log file path from path buffer")
114 .to_string();
115
116 if self.file_writer {
117 file_writer = Some(FileWriter { file })
118 } else {
119 file_writer = None
120 }
121
122 Ok(PackxelLogger {
123 log_level: self.log_level,
124 print_lines: self.print_lines,
125 file_writer,
126 log_file_path,
127 })
128 }
129
130 fn build(self) -> PackxelLogger {
131 self.try_build().expect("Failed to build logger.")
132 }
133}
134
135impl PackxelLogger {
136 pub fn init_with_builder(builder: PackxelLoggerBuilder) -> Result<(), log::SetLoggerError> {
154 log::set_boxed_logger(Box::new(builder.build()))
155 .map(|()| log::set_max_level(log::LevelFilter::Trace))
156 }
157
158 pub fn init(initializer: PackxelInitializer) -> Result<(), log::SetLoggerError> {
173 log::set_boxed_logger(Box::new(
174 PackxelLoggerBuilder::new(Some(initializer))
175 .with_default_log_level()
176 .build(),
177 ))
178 .map(|()| log::set_max_level(log::LevelFilter::Trace))
179 }
180
181 fn get_colored_string(level: Level) -> ColoredString {
182 match level {
183 Level::Error => format!("{} ", level).as_str().red(),
184 Level::Warn => format!("{} ", level).as_str().yellow(),
185 Level::Info => format!("{} ", level).as_str().green(),
186 Level::Debug => format!("{} ", level).as_str().blue(),
187 Level::Trace => format!("{} ", level).as_str().clear(),
188 }
189 }
190}
191
192fn get_level_from_env() -> Option<Level> {
193 let log_level = std::env::var("RUST_LOG").unwrap_or("".to_string());
194 let log_level = Level::from_str(&log_level);
195 let log_level = match log_level {
196 Ok(log_level) => Some(log_level),
197 Err(_) => None,
198 };
199 log_level
200}
201
202fn rotate_file<S: AsRef<str>>(buffer: &mut &File, log_file_path: S) -> std::io::Result<bool> {
203 let file_metadata = buffer.metadata()?;
204
205 let file_size = file_metadata.len();
206
207 let one_mb = 1000 * 1000;
208
209 if file_size > 9 * one_mb {
210 warn!("Log file is getting large. Will be rotating file.")
211 }
212
213 if file_size > 10 * one_mb {
214 let new_file_name = format!(
215 "{}-{}.old",
216 chrono::Local::now().timestamp(),
217 log_file_path.as_ref()
218 );
219 std::fs::copy(log_file_path.as_ref(), new_file_name)?;
220
221 {
222 todo!("Seek to start of file")
223 }
226 }
227
228 Ok(false)
229}
230
231impl log::Log for PackxelLogger {
232 fn enabled(&self, metadata: &log::Metadata) -> bool {
234 if self.log_level.is_none() {
235 return false;
236 }
237 return metadata.level() <= self.log_level.unwrap();
238 }
239
240 fn log(&self, record: &log::Record) {
242 let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
243 let level = Self::get_colored_string(record.level());
244
245 if self.enabled(record.metadata()) {
246 if self.file_writer.is_some() {
247 let file_writer = self.file_writer.as_ref().unwrap();
248
249 let output = format!(
250 "{} {} {}\n",
251 level.clone().clear(),
252 timestamp,
253 record.args()
254 );
255
256 let mut file = &file_writer.file;
257
258 file.write(output.as_bytes())
259 .expect("Unable to write to file");
260
261 match rotate_file(&mut &file_writer.file, &self.log_file_path) {
262 Ok(rotated) => {
263 if rotated {
264 println!("Rotated");
265 }
266 }
267 Err(_) => {
268 log::error!("Failed to rotate log file");
269 }
270 };
271 }
272
273 print!("{}", level);
274 print!("{}\t", timestamp);
275
276 if record.level() == Level::Trace || self.print_lines {
277 println!(
278 "{}:{} - {}",
279 record.file().unwrap_or("unknown"),
280 record.line().unwrap_or(0),
281 record.args(),
282 );
283 } else {
284 println!("{}", record.args());
285 }
286 }
287 }
288
289 fn flush(&self) {}
290}