use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{mpsc, Arc};
use std::fs;
use std::io::prelude::*;
use std::path::PathBuf;
use chrono::Local;
use crate::{Command, Level, OutputStream};
const LEVEL_LABEL_WIDTH: usize = 9;
const MESSAGE_LEFT_PADDING: usize = 3;
#[cfg(test)]
pub const STDOUT_FILENAME: &str = "logs/stdout_redirect.log";
#[cfg(test)]
pub const FILE_OUT_FILENAME: &str = "logs/file_out_redirect.log";
pub struct Receiver {
logger_rx: mpsc::Receiver<Command>,
output_level: Level,
output_stream: OutputStream,
msg_count: Arc<AtomicU64>,
}
impl Receiver {
pub fn new(
logger_rx: mpsc::Receiver<Command>,
output_level: Level,
output_stream: OutputStream,
msg_count: Arc<AtomicU64>,
) -> Self {
Self {
logger_rx,
output_level,
output_stream,
msg_count,
}
}
pub fn main(&mut self) {
let start_time = Local::now();
println!(
"{}: Entered LogReceiver thread.",
start_time.format("%Y-%m-%d %T%.3f")
);
let logfile_dir = "logs";
let logfile_name = format!(
"mt_log_{}.log",
start_time.format("%F_%H_%M_%S%.3f")
);
let mut path_buf = PathBuf::from(logfile_dir);
if !path_buf.as_path().exists() {
match fs::create_dir(path_buf.as_path()) {
Ok(()) => (),
Err(e) => panic!("Failed to create logs directory. Error: {}", e),
}
}
path_buf.push(logfile_name);
let mut logfile = match fs::File::create(path_buf.as_path()) {
Ok(file) => file,
Err(err) => panic!(
"Failed to open logfile at {}. Error: {}",
path_buf.as_path().display(),
err
),
};
#[cfg(test)]
{
fs::File::create(STDOUT_FILENAME).unwrap_or_else(|err| {
panic!(
"Encountered error '{}' while creating stdout verification file",
err
)
});
fs::File::create(FILE_OUT_FILENAME).unwrap_or_else(|err| {
panic!(
"Encountered error '{}' while creating file output verification file",
err
)
});
}
loop {
if let Ok(logger_cmd) = self.logger_rx.recv() {
let timestamp = Local::now().format("%Y-%m-%d %T%.3f");
match logger_cmd {
Command::LogMsg(log_tuple) => {
if log_tuple.level >= self.output_level {
if self.output_stream as u8 & OutputStream::StdOut as u8 != 0 {
let log_color = match log_tuple.level {
Level::Trace => "\x1b[030;105m",
Level::Debug => "\x1b[030;106m",
Level::Info => "\x1b[030;107m",
Level::Warning => "\x1b[030;103m",
Level::Error => "\x1b[030;101m",
Level::Fatal => "\x1b[031;040m",
};
let msg_formatted = format!(
"{timestamp}: {color_set}[{level:^level_width$}]\x1b[0m {fn_name}() line {line}:\n{msg:>msg_leftpad$}",
timestamp = timestamp,
color_set = log_color,
level = log_tuple.level.to_string(),
level_width = LEVEL_LABEL_WIDTH,
fn_name = log_tuple.fn_name,
line = log_tuple.line,
msg = log_tuple.msg,
msg_leftpad = MESSAGE_LEFT_PADDING + log_tuple.msg.len(),
);
println!("{}", msg_formatted);
#[cfg(test)]
{
let writeable_msg = format!("{}\n", msg_formatted);
let mut stdout_redirect = fs::OpenOptions::new().append(true).open(STDOUT_FILENAME)
.unwrap_or_else(
|err| panic!("Encountered error '{}' while attempting to open stdout verification file.", err)
);
stdout_redirect.write_all(writeable_msg.as_bytes())
.unwrap_or_else(
|err| panic!("Encountered error '{}' while attempting to write to stdout verification file.", err)
);
}
}
if self.output_stream as u8 & OutputStream::File as u8 != 0 {
let msg_formatted = format!(
"{timestamp}: [{level:^level_width$}] {fn_name}() line {line}:\n{msg:>msg_leftpad$}\n",
timestamp = timestamp,
level = log_tuple.level.to_string(),
level_width = LEVEL_LABEL_WIDTH,
fn_name = log_tuple.fn_name,
line = log_tuple.line,
msg = log_tuple.msg,
msg_leftpad = MESSAGE_LEFT_PADDING + log_tuple.msg.len(),
);
logfile.write_all(msg_formatted.as_bytes())
.unwrap_or_else(
|err| eprintln!("{}: Encountered error '{}' while attempting to write to log file.", timestamp, err)
);
#[cfg(test)]
{
let mut file_redirect = fs::OpenOptions::new().append(true).open(FILE_OUT_FILENAME)
.unwrap_or_else(
|err| panic!("Encountered error '{}' while attempting to open file output verification file.", err)
);
file_redirect.write_all(msg_formatted.as_bytes())
.unwrap_or_else(
|err| panic!("Encountered error '{}' while attempting to write to file output verification file.", err)
)
}
}
}
self.msg_count.fetch_add(1, Ordering::SeqCst);
}
Command::SetOutputLevel(output_level) => {
self.output_level = output_level;
}
Command::SetOutputStream(output_stream) => {
self.output_stream = output_stream;
}
};
}
}
}
}