rusty_tip/
logger.rs

1use chrono::{DateTime, Utc};
2use log::info;
3use serde::{Deserialize, Serialize};
4use std::{io::Write, path::PathBuf};
5
6use crate::error::NanonisError;
7
8#[derive(Debug, Serialize, Deserialize)]
9struct LogEntry<T>
10where
11    T: Serialize,
12{
13    timestamp: DateTime<Utc>,
14    data: T,
15}
16
17#[derive(Debug)]
18pub struct Logger<T>
19where
20    T: Serialize,
21{
22    buffer: Vec<LogEntry<T>>,
23    buffer_size: usize,
24    file_path: PathBuf,
25}
26
27impl<T> Logger<T>
28where
29    T: Serialize,
30{
31    pub fn new<P: Into<PathBuf>>(file_path: P, buffer_size: usize) -> Self {
32        Self {
33            buffer: Vec::with_capacity(buffer_size),
34            buffer_size,
35            file_path: file_path.into(),
36        }
37    }
38
39    pub fn add(&mut self, data: T) -> Result<(), NanonisError> {
40        let entry = LogEntry {
41            timestamp: Utc::now(),
42            data,
43        };
44
45        self.buffer.push(entry);
46
47        if self.buffer.len() >= self.buffer_size {
48            self.flush()?;
49        }
50
51        Ok(())
52    }
53
54    pub fn flush(&mut self) -> Result<(), NanonisError> {
55        if self.buffer.is_empty() {
56            return Ok(());
57        }
58
59        let file = std::fs::OpenOptions::new()
60            .create(true)
61            .append(true)
62            .open(&self.file_path)
63            .map_err(|source| NanonisError::Io {
64                source,
65                context: format!(
66                    "Logger could not create file at {:?}",
67                    self.file_path
68                ),
69            })?;
70
71        let mut writer = std::io::BufWriter::new(file);
72
73        for entry in &self.buffer {
74            let json_line = serde_json::to_string(entry)?;
75            writeln!(writer, "{}", json_line)?;
76        }
77
78        writer.flush()?;
79        self.buffer.clear();
80        info!("Logger flushed successfully to file");
81        Ok(())
82    }
83
84    pub fn len(&self) -> usize {
85        self.buffer.len()
86    }
87
88    pub fn is_empty(&self) -> bool {
89        self.buffer.len() == 0
90    }
91}
92
93impl<T> Drop for Logger<T>
94where
95    T: Serialize,
96{
97    fn drop(&mut self) {
98        let _ = self.flush();
99    }
100}