1#![allow(dead_code)]
2
3use std::env;
4use std::fmt;
5use std::fs;
6use std::fs::File;
7use std::io::Write;
8use std::net::SocketAddr;
9use std::path::PathBuf;
10use std::sync::atomic::{AtomicBool, Ordering};
11use std::thread;
12use std::time::Duration;
13
14use crate::channel::{self, SendError};
15use crate::chrono::{DateTime, Utc};
16use crate::parking_lot::{Mutex, Once, RwLock, ONCE_INIT};
17use crate::support::debug;
18
19lazy_static! {
20 static ref TEMP_STORE: Mutex<Vec<LogInfo>> = Mutex::new(Vec::new());
21 static ref CONFIG: RwLock<LoggerConfig> = RwLock::new(LoggerConfig::initialize(""));
22}
23
24const DEFAULT_LOCATION: &str = "./logs";
25
26static ONCE: Once = ONCE_INIT;
27static mut SENDER: Option<channel::Sender<LogInfo>> = None;
28static mut REFRESH_HANDLER: Option<thread::JoinHandle<()>> = None;
29static mut DUMPING_RUNNING: AtomicBool = AtomicBool::new(false);
30static mut LOG_WRITER: Option<Box<dyn LogWriter>> = None;
31
32#[derive(Debug)]
33pub enum InfoLevel {
34 Trace,
35 Debug,
36 Info,
37 Warn,
38 Error,
39 Critical,
40}
41
42impl fmt::Display for InfoLevel {
43 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
44 write!(f, "{:?}", self)
45 }
46}
47
48pub struct LogInfo {
49 message: String,
50 client: Option<SocketAddr>,
51 level: InfoLevel,
52 time: DateTime<Utc>,
53}
54
55impl fmt::Display for LogInfo {
56 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
57 match self.client {
58 Some(addr) => write!(
59 f,
60 "[{}] @ {} (from client {}): {}",
61 self.level,
62 self.time.to_string(),
63 addr.to_string(),
64 self.message
65 ),
66 None => write!(
67 f,
68 "[{}] @ {}: {}",
69 self.level,
70 self.time.to_string(),
71 self.message
72 ),
73 }
74 }
75}
76
77struct LoggerConfig {
78 id: String,
79 refresh_period: Duration,
80 log_folder_path: Option<PathBuf>,
81 meta_info_provider: Option<fn(bool) -> String>,
82 rx_handler: Option<thread::JoinHandle<()>>,
83}
84
85pub trait LogWriter {
86 fn dump(&self, log_store: Vec<LogInfo>) -> Result<(), Vec<LogInfo>>;
87}
88
89struct DefaultLogWriter {}
90
91impl DefaultLogWriter {
92 fn get_log_file(config: &LoggerConfig) -> Result<File, String> {
93 match config.log_folder_path {
94 Some(ref location) if location.is_dir() => create_dump_file(config.get_id(), location),
95 _ => create_dump_file(config.get_id(), &PathBuf::from(DEFAULT_LOCATION)),
96 }
97 }
98}
99
100impl LogWriter for DefaultLogWriter {
101 fn dump(&self, log_store: Vec<LogInfo>) -> Result<(), Vec<LogInfo>> {
102 let config = CONFIG.read();
103
104 if let Ok(mut file) = DefaultLogWriter::get_log_file(&config) {
105 if let Some(meta_func) = config.meta_info_provider {
107 write_to_file(&mut file, &meta_func(true));
108 }
109
110 let mut content: String = String::new();
111
112 for (count, info) in log_store.iter().enumerate() {
113 content.push_str(&format_content(&info.level, &info.message, info.time));
114
115 if count % 10 == 0 {
116 write_to_file(&mut file, &content);
117 content.clear();
118 }
119 }
120
121 if !content.is_empty() {
123 write_to_file(&mut file, &content);
124 }
125
126 return Ok(());
127 }
128
129 Err(log_store)
130 }
131}
132
133impl LoggerConfig {
134 fn initialize(id: &str) -> Self {
135 LoggerConfig {
136 id: id.to_owned(),
137 refresh_period: Duration::from_secs(1800),
138 log_folder_path: None,
139 meta_info_provider: None,
140 rx_handler: None,
141 }
142 }
143
144 #[inline]
145 fn set_id(&mut self, id: &str) {
146 self.id = id.to_owned();
147 }
148
149 #[inline]
150 fn get_id(&self) -> String {
151 self.id.clone()
152 }
153
154 #[inline]
155 fn get_refresh_period(&self) -> Duration {
156 self.refresh_period.to_owned()
157 }
158
159 #[inline]
160 fn set_refresh_period(&mut self, period: Duration) {
161 self.refresh_period = period;
162 }
163
164 pub fn set_log_folder_path(&mut self, path: &str) {
165 let mut path_buff = PathBuf::new();
166
167 let location: Option<PathBuf> = if path.is_empty() {
168 match env::var_os("LOG_FOLDER_PATH") {
169 Some(p) => {
170 path_buff.push(p);
171 Some(path_buff)
172 }
173 None => None,
174 }
175 } else {
176 path_buff.push(path);
177 Some(path_buff)
178 };
179
180 if let Some(loc) = location {
181 if loc.as_path().is_dir() {
182 self.log_folder_path = Some(loc);
183 }
184 }
185 }
186
187 #[inline]
188 pub fn get_log_folder_path(&self) -> Option<PathBuf> {
189 self.log_folder_path.clone()
190 }
191}
192
193pub fn log(message: &str, level: InfoLevel, client: Option<SocketAddr>) -> Result<(), String> {
194 let info = LogInfo {
195 message: message.to_owned(),
196 client,
197 level,
198 time: Utc::now(),
199 };
200
201 unsafe {
202 if let Some(ref tx) = SENDER {
203 if let Err(SendError(msg)) = tx.send(info) {
204 return Err(format!("Failed to log the message: {}", msg.message));
205 }
206
207 return Ok(());
208 }
209 }
210
211 Err(String::from("The logging service is not running..."))
212}
213
214pub fn set_log_writer<T: LogWriter + 'static>(writer: T) {
215 unsafe {
216 LOG_WRITER = Some(Box::new(writer));
217 }
218}
219
220pub(crate) fn start(
221 period: Option<u64>,
222 log_folder_path: Option<&str>,
223 meta_info_provider: Option<fn(bool) -> String>,
224) {
225 {
226 let mut config = CONFIG.write();
227
228 if let Some(time) = period {
229 if time != 1800 {
230 (*config).refresh_period = Duration::from_secs(time);
231 }
232 }
233
234 if let Some(path) = log_folder_path {
235 (*config).set_log_folder_path(path);
236 }
237
238 (*config).meta_info_provider = meta_info_provider;
239 }
240
241 initialize();
242 set_log_writer(DefaultLogWriter {});
243}
244
245pub(crate) fn shutdown() {
246 stop_refresh();
247
248 let mut config = CONFIG.write();
249 if let Some(rx) = (*config).rx_handler.take() {
250 rx.join().unwrap_or_else(|err| {
251 eprintln!("Encountered error while closing the logger: {:?}", err);
252 });
253 }
254
255 let final_msg = LogInfo {
256 message: String::from("Shutting down the logging service..."),
257 client: None,
258 level: InfoLevel::Info,
259 time: Utc::now(),
260 };
261
262 unsafe {
263 if let Some(ref tx) = SENDER.take() {
264 if let Err(SendError(msg)) = tx.send(final_msg) {
265 debug::print("Failed to log the final message", debug::InfoLevel::Warning);
266 }
267 }
268 }
269
270 dump_log();
272}
273
274fn initialize() {
275 ONCE.call_once(|| {
276 let (tx, rx): (channel::Sender<LogInfo>, channel::Receiver<LogInfo>) = channel::bounded(16);
277
278 unsafe {
279 SENDER = Some(tx);
280 }
281
282 let mut config = CONFIG.write();
283
284 if let Some(ref path) = (*config).log_folder_path {
285 let refresh = (*config).refresh_period.as_secs();
286
287 println!(
288 "The logger has started, it will refresh log to folder {:?} every {} seconds",
289 path.to_str().unwrap(),
290 refresh
291 );
292
293 start_refresh((*config).refresh_period);
294 }
295
296 (*config).rx_handler = Some(thread::spawn(move || {
297 for info in rx {
298 let mut store = TEMP_STORE.lock();
299 (*store).push(info);
300 }
301 }));
302 });
303}
304
305fn start_refresh(period: Duration) {
306 unsafe {
307 if REFRESH_HANDLER.is_some() {
308 stop_refresh();
309 }
310
311 REFRESH_HANDLER = Some(thread::spawn(move || loop {
312 thread::sleep(period);
313 thread::spawn(|| {
314 dump_log();
315 });
316 }));
317 }
318}
319
320fn stop_refresh() {
321 unsafe {
322 if let Some(handler) = REFRESH_HANDLER.take() {
323 handler.join().unwrap_or_else(|err| {
324 eprintln!(
325 "Failed to stop the log refresh service, error code: {:?}...",
326 err
327 );
328 });
329 }
330 }
331}
332
333fn reset_refresh(period: Option<Duration>) {
334 thread::spawn(move || {
335 stop_refresh();
336
337 let new_period = {
338 let mut config = CONFIG.write();
339 if let Some(p) = period {
340 (*config).set_refresh_period(p);
341 }
342
343 (*config).get_refresh_period()
344 };
345
346 start_refresh(new_period);
347 });
348}
349
350fn dump_log() {
351 if unsafe { DUMPING_RUNNING.load(Ordering::SeqCst) } {
352 let config = CONFIG.read();
353
354 if let Ok(mut file) = DefaultLogWriter::get_log_file(&config) {
355 if let Some(meta_func) = config.meta_info_provider {
356 write_to_file(&mut file, &meta_func(false));
357 }
358
359 write_to_file(
360 &mut file,
361 &format_content(
362 &InfoLevel::Info,
363 "A dumping process is already in progress, skipping this scheduled dump.",
364 Utc::now(),
365 ),
366 );
367 }
368
369 return;
370 }
371
372 let store: Vec<LogInfo> = {
373 let mut res = TEMP_STORE.lock();
374 (*res).drain(..).collect()
375 };
376
377 if !store.is_empty() {
378 unsafe {
379 if let Some(ref writer) = LOG_WRITER {
380 *DUMPING_RUNNING.get_mut() = true;
381
382 match writer.dump(store) {
383 Ok(_) => {}
384 Err(vec) => {
385 let mut res = TEMP_STORE.lock();
386
387 (*res).extend(vec);
389 }
390 }
391
392 *DUMPING_RUNNING.get_mut() = false;
393 }
394 }
395 }
396}
397
398fn write_to_file(file: &mut File, content: &str) {
399 file.write_all(content.as_bytes()).unwrap_or_else(|err| {
400 eprintln!("Failed to write to dump file: {}...", err);
401 });
402}
403
404fn format_content(level: &InfoLevel, message: &str, timestamp: DateTime<Utc>) -> String {
405 [
406 "\r\n[",
407 &level.to_string(),
408 "] @ ",
409 ×tamp.to_rfc3339(),
410 ": ",
411 message,
412 ]
413 .join("")
414}
415
416fn create_dump_file(id: String, loc: &PathBuf) -> Result<File, String> {
417 if !loc.as_path().is_dir() {
418 if let Err(e) = fs::create_dir_all(loc) {
419 return Err(format!("Failed to dump the logging information: {}", e));
420 }
421 }
422
423 let base = if id.is_empty() {
424 [&Utc::now().to_string(), ".txt"].join("")
425 } else {
426 [&id, "-", &Utc::now().to_string(), ".txt"].join("")
427 };
428
429 let mut path = loc.to_owned();
430 path.push(base);
431
432 match File::create(path.as_path()) {
433 Ok(file) => Ok(file),
434 _ => Err(String::from("Unable to create the dump file")),
435 }
436}