1extern crate core;
2
3use chrono::{Local, Utc};
4use core_affinity::CoreId;
5use std::fmt::{self};
6use std::fs::File;
7use std::io::{BufWriter, Write};
8use std::thread;
9use std::time::Duration;
10
11pub mod __private_api;
12pub mod macros;
13
14static mut LOGGER: Option<&Logger> = None;
15
16pub enum Level {
17 Debug,
18 Info,
19 Warn,
20 Error,
21}
22
23impl fmt::Display for Level {
24 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25 match *self {
26 Level::Debug => write!(f, "DEBUG"),
27 Level::Info => write!(f, "INFO"),
28 Level::Warn => write!(f, "WARN"),
29 Level::Error => write!(f, "ERROR"),
30 }
31 }
32}
33
34struct LogMetaData {
35 level: Level,
36 func: LoggingFunc,
37}
38
39#[derive(Debug)]
40pub enum LoggerError {
41 InitialisationError,
42}
43
44impl std::error::Error for LoggerError {}
45
46impl fmt::Display for LoggerError {
47 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48 match self {
49 LoggerError::InitialisationError => write!(f, "Error during initialisation"),
50 }
51 }
52}
53
54enum LogCommand {
55 Msg(LogMetaData),
56 Flush(crossbeam_channel::Sender<()>),
57}
58
59pub struct LoggingFunc {
60 data: Box<dyn Fn() -> String + Send + 'static>,
61}
62
63impl LoggingFunc {
64 #[allow(dead_code)]
65 pub fn new<T>(data: T) -> LoggingFunc
66 where
67 T: Fn() -> String + Send + 'static,
68 {
69 LoggingFunc {
70 data: Box::new(data),
71 }
72 }
73 fn invoke(self) -> String {
74 (self.data)()
75 }
76}
77
78impl fmt::Debug for LoggingFunc {
79 fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 Ok(())
81 }
82}
83
84pub struct Logger {
85 cpu: Option<usize>,
86 buffer_size: usize,
87 file_path: Option<String>,
88 filter_level: Level,
89 utc_time: bool,
90 sleep_duration_millis: u64,
91 thread_name: String,
92 sender: Option<crossbeam_channel::Sender<LogCommand>>,
93}
94
95impl Logger {
96 pub fn new() -> Logger {
97 Logger {
98 cpu: None,
99 buffer_size: 0,
100 file_path: None,
101 filter_level: Level::Info,
102 utc_time: false,
103 sleep_duration_millis: 100,
104 thread_name: String::from("logflume - Rust logging library"),
105 sender: None,
106 }
107 }
108
109 pub fn level(mut self, filter: Level) -> Logger {
110 self.filter_level = filter;
111 self
112 }
113
114 pub fn cpu(mut self, cpu: usize) -> Logger {
115 self.cpu = Some(cpu);
116 self
117 }
118
119 pub fn buffer_size(mut self, buf_size: usize) -> Logger {
120 self.buffer_size = buf_size;
121 self
122 }
123
124 pub fn file(mut self, file: &str) -> Logger {
125 self.file_path = Some(file.to_string());
126 self
127 }
128
129 pub fn utc_time(mut self, b: bool) -> Logger {
130 self.utc_time = b;
131 self
132 }
133
134 pub fn sleep_duration_millis(mut self, millis: u64) -> Logger {
136 self.sleep_duration_millis = millis;
137 self
138 }
139
140 pub fn thread_name(mut self, name: &str) -> Logger {
141 self.thread_name = name.to_string();
142 self
143 }
144
145 pub fn init(mut self) -> Result<(), LoggerError> {
146 let (tx, rx) = match self.buffer_size {
147 0 => crossbeam_channel::unbounded(),
148 _ => crossbeam_channel::bounded(self.buffer_size),
149 };
150
151 self.sender = Some(tx);
152 let file_path = self.file_path.clone();
153 let file =
154 File::create(file_path.unwrap()).map_err(|_| LoggerError::InitialisationError)?;
155 let mut buffered_writer = BufWriter::new(file);
156 let time_func = if self.utc_time {
157 get_utc_time
158 } else {
159 get_local_time
160 };
161
162 let _a = thread::Builder::new().name(self.thread_name.to_string()).spawn(move || {
163 if let Some(core) = self.cpu {
164 core_affinity::set_for_current(CoreId { id: core });
165 }
166 loop {
167 match rx.try_recv() {
168 Ok(cmd) => {
169 Self::process_log_command(&mut buffered_writer, cmd, time_func);
170 }
171 Err(e) => match e {
172 crossbeam_channel::TryRecvError::Empty => {
173 let _ = buffered_writer.flush();
174 thread::sleep(Duration::from_millis(self.sleep_duration_millis));
175 }
176 crossbeam_channel::TryRecvError::Disconnected => {
177 let _ = buffered_writer
178 .write_all("Logging channel disconnected".as_bytes());
179 }
180 },
181 }
182 }
183 });
184
185 unsafe {
186 let boxed_logger = Box::new(self);
187 LOGGER = Some(Box::leak(boxed_logger));
188 }
189 Ok(())
190 }
191
192 fn process_log_command(
193 buffered_file_writer: &mut BufWriter<File>,
194 cmd: LogCommand,
195 gettime: fn() -> String,
196 ) {
197 match cmd {
198 LogCommand::Msg(msg) => {
199 let log_msg = format!("{} {} {}\n", gettime(), msg.level, msg.func.invoke());
200 let _ = buffered_file_writer.write_all(log_msg.as_bytes());
201 }
202 LogCommand::Flush(tx) => {
203 let _ = buffered_file_writer.flush();
204 let _ = tx.send(());
205 }
206 }
207 }
208
209 pub fn log(&self, level: Level, func: LoggingFunc) {
210 match &self.sender {
211 Some(tx) => {
212 tx.send(LogCommand::Msg(LogMetaData { level, func }))
213 .unwrap();
214 }
215 None => (),
216 }
217 }
218
219 pub fn flush(&self) {
221 if let Some(tx) = &self.sender {
222 let (flush_tx, flush_rx) = crossbeam_channel::bounded(1);
223 tx.send(LogCommand::Flush(flush_tx)).ok();
224 let _ = flush_rx.recv();
225 }
226 }
227}
228
229impl Default for Logger {
230 fn default() -> Self {
231 Self::new()
232 }
233}
234
235impl Drop for Logger {
236 fn drop(&mut self) {
237 self.flush();
238 }
239}
240
241pub fn logger() -> &'static Logger {
242 unsafe { LOGGER.unwrap() }
243}
244
245fn get_utc_time() -> String {
246 format!("{}", Utc::now())
247}
248
249fn get_local_time() -> String {
250 format!("{}", Local::now())
251}
252
253#[cfg(test)]
254mod tests {
255 use crate::{debug, error, info, warn};
256 use crate::{LogMetaData, Logger};
257
258 #[test]
259 pub fn test_log() {
260 Logger::new().file("test.log").init().unwrap();
261 info!("hello {} {}", "world", 123);
262 warn!("hello world");
263 debug!("debug log");
264 error!("Something went wrong!");
265 assert_eq!(std::mem::size_of::<LogMetaData>(), 24)
266 }
267}