1extern crate log;
3extern crate chrono;
4extern crate regex;
5
6use regex::Regex;
7use log::{Log, Record, Level, Metadata};
8use std::fs::{File, OpenOptions, read_dir};
9use std::path::PathBuf;
10use std::io::{prelude::*, BufReader};
11
12use std::sync::mpsc::{sync_channel, SyncSender, Receiver};
14use std::thread;
15
16use chrono::prelude::*;
18
19pub struct LogWriter {
33 pub log_path: PathBuf,
34 pub line_count: u64,
35 pub line_maximum: u64,
36 pub current_log_file_number: i64,
37 pub log_file: File,
38 pub rx: Receiver<String>
39}
40
41impl LogWriter {
42 pub fn new(log_path: PathBuf, rx: Receiver<String>, line_maximum: u64) -> LogWriter {
53 if !log_path.as_path().exists() {
54 panic!("Log folder does not exist!");
55 }
56
57 let reg = Regex::new(r"(?x)^trace-(?P<number>[0-9]+)\.log$").unwrap();
58 let dir_iter = match read_dir(&log_path) {
60 Ok(v) => v,
61 Err(_) => panic!("Could not iterate through files in log dir")
62 };
63 let mut current_log_file_number: i64 = -1;
64 for dir in dir_iter {
65 match dir {
66 Ok(v) => {
67 let filename = String::from(v.file_name().to_string_lossy());
68 match reg.captures(&filename).and_then(|cap| {
69 cap.name("number").map(|number| number.as_str().parse::<i64>().unwrap())
70 }) {
71 Some(v) => {
72 if v > current_log_file_number {
73 current_log_file_number = v;
74 }
75 },
76 None => ()
77 };
78 },
79 Err(_) => panic!("Could not see file inside log dir")
80 };
81 }
82 let (line_count, log_file_path) = if current_log_file_number == -1 {
84 current_log_file_number += 1;
86 let mut file_path = log_path.clone();
87 file_path.push(format!("trace-{}.log", current_log_file_number));
88 (0, file_path.clone())
89 } else {
90 let mut log_file_path = log_path.clone();
91 log_file_path.push(format!("trace-{}.log", current_log_file_number));
92 match File::open(&log_file_path) {
93 Ok(f) => {
94 let mut curr_line_count = 0;
95 for _line in BufReader::new(f).lines() {
96 curr_line_count += 1;
97 }
98 if curr_line_count < line_maximum {
100 (curr_line_count, log_file_path.clone())
101 } else {
102 current_log_file_number += 1;
104 let mut file_path = log_path.clone();
105 file_path.push(format!("trace-{}.log", current_log_file_number));
106 (0, file_path.clone())
107 }
108 },
109 Err(e) => {
110 panic!("Could not count file lines: {}", e);
111 }
112 }
113 };
114
115 let f = if log_file_path.as_path().exists() {
117 match OpenOptions::new()
118 .write(true)
119 .append(true)
120 .open(&log_file_path) {
121 Ok(v) => v,
122 Err(_) => panic!("Imposible sobreescribir la bitácora.")
123 }
124 } else {
125 match File::create(&log_file_path) {
126 Ok(v) => v,
127 Err(e) => panic!("Could not create log file {}! ({})", log_file_path.as_os_str().to_string_lossy(), e)
128 }
129 };
130 LogWriter{
131 log_path,
132 log_file: f,
133 rx: rx,
134 line_count,
135 line_maximum,
136 current_log_file_number
137 }
138 }
139
140 pub fn run(&mut self){
149 for record in &self.rx {
150 match self.log_file.write((record+"\n").as_bytes()) {
151 Ok(_) => (),
152 Err(e) => {
153 println!("Could not write to log file: {}", e)
154 }
155 }
156 self.line_count = (self.line_count + 1) % self.line_maximum;
157 if self.line_count == 0 {
158 match self.log_file.flush() {
160 Ok(_) => (),
161 Err(_) => panic!("Could not flush contents")
162 };
163 self.current_log_file_number += 1;
164 let mut log_file_path = self.log_path.clone();
165 log_file_path.push(format!("trace-{}.log", self.current_log_file_number));
166 self.log_file = match File::create(&log_file_path) {
167 Ok(v) => v,
169 Err(e) => panic!("Could not create log file {}! ({})", log_file_path.as_os_str().to_string_lossy(), e)
170 }
171 }
172 }
173 }
174}
175
176pub struct DzahuiLogger {
188 print_to_term: bool,
189 print_to_file: bool,
190 tx: SyncSender<String>,
191 logger_id: &'static str
192}
193
194impl Log for DzahuiLogger {
195 fn enabled(&self, metadata: &Metadata) -> bool {
205 match metadata.level() {
206 Level::Error => true,
207 Level::Warn => true,
208 Level::Info => true,
209 Level::Debug => true,
210 Level::Trace => true
211 }
212 }
213
214 fn log(&self, record: &Record) {
224 if self.enabled(record.metadata()) {
226 let level_string = {
227 match record.level() {
228 Level::Error => format!("\u{001b}[0;31m{}\u{001b}[0m", record.level().to_string()),
229 Level::Warn => format!("\u{001b}[0;33m{}\u{001b}[0m", record.level().to_string()),
230 Level::Info => format!("\u{001b}[0;36m{}\u{001b}[0m", record.level().to_string()),
231 Level::Debug => format!("\u{001b}[0;35m{}\u{001b}[0m", record.level().to_string()),
232 Level::Trace => format!("{}", record.level().to_string()),
233 }
234 };
235 let registry = if cfg!(feature = "log-module") {format!(
236 "{} {}[{:<5}, {}]: {}",
237 Local::now().format("%Y-%m-%d %H:%M:%S,%3f"),
238 self.logger_id,
239 level_string,
240 record.module_path().unwrap_or_default(),
241 record.args()
242 )} else {format!(
243 "{} {}[{:>16}]: {}",
244 Local::now().format("%Y-%m-%d %H:%M:%S,%3f"),
245 self.logger_id,
246 level_string,
247 record.args()
248 )};
249
250 if self.print_to_term {
251 println!("{}",®istry);
252 }
253 if self.print_to_file {
254 match self.tx.send(registry) {
255 Ok(_) => (),
256 Err(_) => {
257 println!("Cannot write anymore to log file (thread crashed)");
258 panic!("Could not write anymore to the log file");
259 }
260 };
261 }
262 }
263 }
264
265 fn flush(&self) {}
267}
268
269impl DzahuiLogger {
270 pub fn new(logger_id: &'static str, print_to_term: bool, log_path: Option<PathBuf>) -> DzahuiLogger {
281 if let Some(log_path) = log_path {
282 if !log_path.as_path().exists() {
283 panic!("Could not find log path ({})", log_path.as_os_str().to_string_lossy());
284 }
285 let (sender, receiver) = sync_channel::<String>(0);
287 thread::spawn(move || {
289 LogWriter::new(log_path, receiver, 10_000_000).run()
290 });
291 DzahuiLogger{print_to_term, print_to_file: true, tx: sender, logger_id}
292 } else {
293 let (sender, _) = sync_channel::<String>(0);
294 DzahuiLogger{print_to_term, print_to_file: false, tx: sender, logger_id}
296 }
297 }
298}
299
300pub fn spawn(log_level: log::LevelFilter, prefix: &'static str) -> Result<(), log::SetLoggerError> {
311 log::set_boxed_logger(Box::new(DzahuiLogger::new(prefix, true, None))).map(|()|
312 log::set_max_level(log_level)
313 )
314}