#[macro_use]
extern crate lazy_static;
extern crate chrono;
extern crate regex;
use chrono::{Date, Duration, Local, TimeZone};
use regex::Regex;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::collections::VecDeque;
use std::fmt;
use std::fs;
use std::fs::{File, OpenOptions};
use std::io::{BufWriter, Write};
use std::ops::Add;
use std::path::Path;
use std::process;
use std::sync::Mutex;
use std::thread;
use std::time::Instant;
#[cfg(windows)]
const NEW_LINE: &'static str = "\r\n";
#[cfg(windows)]
const NEW_LINE_SEQUENCE: &'static str = "\\r\\n";
#[cfg(not(windows))]
const NEW_LINE: &'static str = "\n";
#[cfg(not(windows))]
const NEW_LINE_SEQUENCE: &'static str = "\\n";
#[derive(Clone)]
pub enum Level {
#[allow(dead_code)]
Fatal,
Error,
Warn,
Notice,
Info,
Debug,
Trace,
}
impl Level {
pub fn number(&self) -> usize {
match self {
Level::Fatal => 1,
Level::Error => 2,
Level::Warn => 3,
Level::Notice => 4,
Level::Info => 5,
Level::Debug => 6,
Level::Trace => 7,
}
}
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Level::Fatal => write!(f, "Fatal"),
Level::Error => write!(f, "Error"),
Level::Warn => write!(f, "Warn"),
Level::Notice => write!(f, "Notice"),
Level::Info => write!(f, "Info"),
Level::Debug => write!(f, "Debug"),
Level::Trace => write!(f, "Trace"),
}
}
}
lazy_static! {
pub static ref LOGGER: Mutex<Logger> = Mutex::new(Logger::default());
static ref POOL: Mutex<Pool> = Mutex::new(Pool::default());
static ref QUEUE: Mutex<VecDeque<Table>> = Mutex::new(VecDeque::<Table>::new());
static ref LAST_FLUSH_TIME: Mutex<LastFlushTime> = Mutex::new(LastFlushTime::default());
}
thread_local!(static SEQ: RefCell<u128> = {
RefCell::new(1)
});
#[derive(Clone)]
pub struct Table {
thread_id: String,
seq: u128,
level: Level,
message: String,
message_trailing_newline: bool,
sorted_map: BTreeMap<String, String>,
}
impl Default for Table {
fn default() -> Self {
Table {
thread_id: "".to_string(),
seq: 0,
sorted_map: BTreeMap::new(),
level: Level::Trace,
message: "".to_string(),
message_trailing_newline: false,
}
}
}
impl Table {
fn new(level: Level, message: &str, trailing_newline: bool) -> Self {
Table {
thread_id: "".to_string(),
seq: 0,
sorted_map: BTreeMap::new(),
level: level,
message: message.to_string(),
message_trailing_newline: trailing_newline,
}
}
pub fn format_str_value(value: &str) -> String {
let mut body = if value[value.len() - NEW_LINE.len()..] == *NEW_LINE {
format!("{}{}", value.trim_end(), NEW_LINE_SEQUENCE)
} else {
value.to_string()
};
body = body.replace("\"", "\\\"");
if 1 < value.lines().count() {
format!(
"\"\"\"
{}
\"\"\"",
body
)
} else {
format!("\"{}\"", body)
}
}
pub fn str<'a>(&'a mut self, key: &'a str, value: &'a str) -> &'a mut Self {
self.sorted_map.insert(
key.to_string(),
Table::format_str_value(value).to_string(),
);
self
}
pub fn literal<'a>(&'a mut self, key: &'a str, value: &'a str) -> &'a mut Self {
self.sorted_map.insert(
key.to_string(),
value.to_string(),
);
self
}
}
pub struct Log {}
impl Log {
pub fn set_file_name(prefix: &str) {
if let Ok(mut logger) = LOGGER.lock() {
logger.file_prefix = prefix.to_string();
}
}
pub fn set_file_ext(ext: Extension) {
if let Ok(mut logger) = LOGGER.lock() {
match ext {
Extension::LogToml => {
logger.file_suffix = ".log".to_string();
logger.file_extention = ".toml".to_string();
}
Extension::Log => {
logger.file_suffix = "".to_string();
logger.file_extention = ".log".to_string();
}
}
}
}
pub fn set_level(level: Level) {
if let Ok(mut logger) = LOGGER.lock() {
logger.level = level;
}
}
pub fn set_retention_days(days: u32) {
if let Ok(mut logger) = LOGGER.lock() {
logger.retention_days = days as i64;
}
}
pub fn remove_old_logs() -> usize {
let remove_num = if let Ok(logger) = LOGGER.lock() {
let remove_num = logger.remove_old_logs();
if logger.development && 0 < remove_num {
println!("casual_logger: Remove {} log file(s).", remove_num);
}
remove_num
} else {
0
};
remove_num
}
pub fn wait() {
let (timeout_secs, development) = if let Ok(logger) = LOGGER.lock() {
(Logger::get_timeout_sec(&logger), logger.development)
} else {
(0, false)
};
Log::wait_for_logging_to_complete(timeout_secs, |secs, message| {
if development {
eprintln!("casual_logger: {} sec(s). {}", secs, message,);
}
});
}
#[deprecated(
since = "0.3.2",
note = "Please use the casual_logger::Log::wait() method instead"
)]
pub fn wait_for_logging_to_complete<F>(timeout_secs: u64, count_down: F)
where
F: Fn(u64, String),
{
let mut elapsed_secs = 0;
while elapsed_secs < timeout_secs {
let mut thr_num = None;
let mut queue_len = None;
if let Ok(pool) = POOL.lock() {
let thr_num_val = pool.get_thread_count();
thr_num = Some(thr_num_val);
if thr_num_val < 1 {
if let Ok(queue) = QUEUE.lock() {
if queue.is_empty() {
break;
}
queue_len = Some(queue.len());
}
Log::flush();
}
}
count_down(
elapsed_secs,
format!(
"{}{}",
if let Some(thr_num_val) = thr_num {
if 0 < thr_num_val {
format!("Wait for {} thread(s). ", thr_num_val)
} else {
"".to_string()
}
} else {
"".to_string()
},
if let Some(queue_len_val) = queue_len {
if 0 < queue_len_val {
format!("{} table(s) left. ", queue_len_val)
} else {
"".to_string()
}
} else {
"".to_string()
},
)
.trim_end()
.to_string(),
);
thread::sleep(std::time::Duration::from_secs(1));
elapsed_secs += 1;
}
}
pub fn enabled(level: Level) -> bool {
if let Ok(logger) = LOGGER.lock() {
if logger.enabled(level) {
return true;
}
}
false
}
#[allow(dead_code)]
pub fn trace(message: &str) {
if Log::enabled(Level::Trace) {
Log::send(&Table::new(Level::Trace, message, false));
}
}
#[allow(dead_code)]
pub fn traceln(message: &str) {
if Log::enabled(Level::Trace) {
Log::send(&Table::new(Level::Trace, message, true));
}
}
#[allow(dead_code)]
pub fn trace_t(message: &str, table: &mut Table) {
if Log::enabled(Level::Trace) {
table.level = Level::Trace;
table.message = message.to_string();
table.message_trailing_newline = false;
Log::send(table);
}
}
#[allow(dead_code)]
pub fn traceln_t(message: &str, table: &mut Table) {
if Log::enabled(Level::Trace) {
table.level = Level::Trace;
table.message = message.to_string();
table.message_trailing_newline = true;
Log::send(table);
}
}
#[allow(dead_code)]
pub fn debug(message: &str) {
if Log::enabled(Level::Debug) {
Log::send(&Table::new(Level::Debug, message, false));
}
}
#[allow(dead_code)]
pub fn debugln(message: &str) {
if Log::enabled(Level::Debug) {
Log::send(&Table::new(Level::Debug, message, true));
}
}
#[allow(dead_code)]
pub fn debug_t(message: &str, table: &mut Table) {
if Log::enabled(Level::Debug) {
table.level = Level::Debug;
table.message = message.to_string();
table.message_trailing_newline = false;
Log::send(table);
}
}
#[allow(dead_code)]
pub fn debugln_t(message: &str, table: &mut Table) {
if Log::enabled(Level::Debug) {
table.level = Level::Debug;
table.message = message.to_string();
table.message_trailing_newline = true;
Log::send(table);
}
}
#[allow(dead_code)]
pub fn info(message: &str) {
if Log::enabled(Level::Info) {
Log::send(&Table::new(Level::Info, message, false));
}
}
#[allow(dead_code)]
pub fn infoln(message: &str) {
if Log::enabled(Level::Info) {
Log::send(&Table::new(Level::Info, message, true));
}
}
#[allow(dead_code)]
pub fn info_t(message: &str, table: &mut Table) {
if Log::enabled(Level::Info) {
table.level = Level::Info;
table.message = message.to_string();
table.message_trailing_newline = false;
Log::send(table);
}
}
#[allow(dead_code)]
pub fn infoln_t(message: &str, table: &mut Table) {
if Log::enabled(Level::Info) {
table.level = Level::Info;
table.message = message.to_string();
table.message_trailing_newline = true;
Log::send(table);
}
}
#[allow(dead_code)]
pub fn notice(message: &str) {
if Log::enabled(Level::Notice) {
Log::send(&Table::new(Level::Notice, message, false));
}
}
#[allow(dead_code)]
pub fn noticeln(message: &str) {
if Log::enabled(Level::Notice) {
Log::send(&Table::new(Level::Notice, message, true));
}
}
#[allow(dead_code)]
pub fn notice_t(message: &str, table: &mut Table) {
if Log::enabled(Level::Notice) {
table.level = Level::Notice;
table.message = message.to_string();
table.message_trailing_newline = false;
Log::send(table);
}
}
#[allow(dead_code)]
pub fn noticeln_t(message: &str, table: &mut Table) {
if Log::enabled(Level::Notice) {
table.level = Level::Notice;
table.message = message.to_string();
table.message_trailing_newline = true;
Log::send(table);
}
}
#[allow(dead_code)]
pub fn warn(message: &str) {
if Log::enabled(Level::Warn) {
Log::send(&Table::new(Level::Warn, message, false));
}
}
#[allow(dead_code)]
pub fn warnln(message: &str) {
if Log::enabled(Level::Warn) {
Log::send(&Table::new(Level::Warn, message, true));
}
}
#[allow(dead_code)]
pub fn warn_t(message: &str, table: &mut Table) {
if Log::enabled(Level::Warn) {
table.level = Level::Warn;
table.message = message.to_string();
table.message_trailing_newline = false;
Log::send(table);
}
}
#[allow(dead_code)]
pub fn warnln_t(message: &str, table: &mut Table) {
if Log::enabled(Level::Warn) {
table.level = Level::Warn;
table.message = message.to_string();
table.message_trailing_newline = true;
Log::send(table);
}
}
#[allow(dead_code)]
pub fn error(message: &str) {
if Log::enabled(Level::Error) {
Log::send(&Table::new(Level::Error, message, false));
}
}
#[allow(dead_code)]
pub fn errorln(message: &str) {
if Log::enabled(Level::Error) {
Log::send(&Table::new(Level::Error, message, true));
}
}
#[allow(dead_code)]
pub fn error_t(message: &str, table: &mut Table) {
if Log::enabled(Level::Error) {
table.level = Level::Error;
table.message = message.to_string();
table.message_trailing_newline = false;
Log::send(table);
}
}
#[allow(dead_code)]
pub fn errorln_t(message: &str, table: &mut Table) {
if Log::enabled(Level::Error) {
table.level = Level::Error;
table.message = message.to_string();
table.message_trailing_newline = true;
Log::send(table);
}
}
#[allow(dead_code)]
pub fn fatal(message: &str) -> String {
Log::send(&Table::new(Level::Fatal, message, false));
Log::wait();
message.to_string()
}
#[allow(dead_code)]
pub fn fatalln(message: &str) -> String {
Log::send(&Table::new(Level::Fatal, message, true));
Log::wait();
format!("{}{}", message, NEW_LINE).to_string()
}
#[allow(dead_code)]
pub fn fatal_t(message: &str, table: &mut Table) -> String {
table.level = Level::Fatal;
table.message = message.to_string();
table.message_trailing_newline = false;
Log::send(table);
Log::wait();
message.to_string()
}
#[allow(dead_code)]
pub fn fatalln_t(message: &str, table: &mut Table) -> String {
table.level = Level::Fatal;
table.message = message.to_string();
table.message_trailing_newline = true;
Log::send(table);
Log::wait();
format!("{}{}", message, NEW_LINE).to_string()
}
fn send(table: &Table) {
let mut table_clone = table.clone();
table_clone.thread_id = format!("{:?}", thread::current().id());
SEQ.with(move |seq| {
table_clone.seq = seq.borrow().clone();
if let Ok(mut queue) = QUEUE.lock() {
queue.push_front(table_clone);
}
let can_flush = if let Ok(last_flush_time) = LAST_FLUSH_TIME.lock() {
last_flush_time.can_flush()
} else {
false
};
if can_flush {
if let Ok(mut pool) = POOL.lock() {
pool.increase_thread_count();
}
thread::spawn(move || {
Log::flush();
if let Ok(mut pool) = POOL.lock() {
pool.decrease_thread_count();
}
});
}
*seq.borrow_mut() += 1;
});
}
fn flush() {
let mut str_buf = String::new();
if let Ok(mut queue) = QUEUE.lock() {
loop {
if let Some(table) = queue.pop_back() {
str_buf.push_str(&Log::convert_table_to_string(&table));
} else {
break;
}
}
}
if let Ok(mut logger) = LOGGER.lock() {
let mut file_buf = BufWriter::new(logger.current_file());
if let Err(_why) = file_buf.write_all(str_buf.as_bytes()) {
}
if let Ok(mut last_flush_time) = LAST_FLUSH_TIME.lock() {
last_flush_time.reset();
}
}
}
fn convert_table_to_string(table: &Table) -> String {
let message = if table.message_trailing_newline {
format!("{}{}", table.message, NEW_LINE)
} else {
table.message.to_string()
};
let mut toml = format!(
"[\"Now={}&Pid={}&Thr={}&Seq={}\"]
",
Local::now().format("%Y-%m-%d %H:%M:%S"),
process::id(),
table.thread_id,
table.seq,
);
toml += &format!(
"{} = {}
",
table.level,
Table::format_str_value(&message).to_string()
)
.to_string();
for (k, v) in &table.sorted_map {
toml += &format!(
"{} = {}
",
k, v
)
.to_string();
}
toml += "
";
toml
}
}
struct Pool {
thread_count: u32,
}
impl Default for Pool {
fn default() -> Self {
Pool { thread_count: 0 }
}
}
impl Pool {
fn increase_thread_count(&mut self) {
self.thread_count += 1;
}
fn decrease_thread_count(&mut self) {
self.thread_count -= 1;
}
pub fn get_thread_count(&self) -> u32 {
self.thread_count
}
}
pub struct Logger {
file_prefix: String,
file_suffix: String,
file_extention: String,
#[deprecated(
since = "0.3.8",
note = "Please use the casual_logger::Log::set_retention_days() method instead"
)]
pub retention_days: i64,
log_file: Option<LogFile>,
#[deprecated(
since = "0.3.7",
note = "Please use the casual_logger::Log::set_level() method instead"
)]
pub level: Level,
#[deprecated(
since = "0.3.2",
note = "Please use the logger.timeout_secs property instead"
)]
pub fatal_timeout_secs: u64,
pub timeout_secs: u64,
pub development: bool,
}
impl Default for Logger {
fn default() -> Self {
let prefix = "default";
let suffix = ".log";
let extention = ".toml";
Logger {
file_prefix: prefix.to_string(),
file_suffix: suffix.to_string(),
file_extention: extention.to_string(),
retention_days: 7,
log_file: None,
level: Level::Trace,
fatal_timeout_secs: 30,
timeout_secs: 30,
development: false,
}
}
}
impl Logger {
fn get_timeout_sec(logger: &Logger) -> u64 {
if logger.timeout_secs != 30 {
logger.timeout_secs
} else if logger.fatal_timeout_secs != 30 {
logger.fatal_timeout_secs
} else {
logger.timeout_secs
}
}
pub fn enabled(&self, level: Level) -> bool {
if level.number() <= self.level.number() {
return true;
}
false
}
#[allow(dead_code)]
pub fn get_file_prefix(&self) -> &str {
&self.file_prefix
}
#[allow(dead_code)]
pub fn get_file_suffix(&self) -> &str {
&self.file_suffix
}
#[allow(dead_code)]
pub fn get_file_extention(&self) -> &str {
&self.file_extention
}
#[deprecated(
since = "0.3.6",
note = "Please use the casual_logger::Log::set_file_name() or casual_logger::Log::set_toml_ext() method instead"
)]
#[allow(dead_code)]
pub fn set_file_name(&mut self, prefix: &str, suffix: &str, extention: &str) {
self.file_prefix = prefix.to_string();
self.file_suffix = suffix.to_string();
self.file_extention = extention.to_string();
}
fn new_today_file(
file_prefix: &str,
file_suffix: &str,
file_extention: &str,
) -> (Date<Local>, File) {
let start_date = Local::today();
let file = OpenOptions::new()
.create(true)
.append(true)
.open(Path::new(&format!(
"{}-{}{}{}",
file_prefix,
start_date.format("%Y-%m-%d"),
file_suffix,
file_extention
)))
.unwrap();
(start_date, file)
}
#[deprecated(
since = "0.3.3",
note = "Please use the casual_logger::Log::remove_old_logs() method instead"
)]
pub fn remove_old_logs(&self) -> usize {
let mut count = 0;
let re = if let Ok(x) = Regex::new(&format!(
"./{}-{}{}{}",
self.file_prefix, r"(\d{4})-(\d{2})-(\d{2})", self.file_suffix, self.file_extention
)) {
x
} else {
return 0;
};
let paths = if let Ok(x) = fs::read_dir("./") {
x
} else {
return 0;
};
for path in paths {
let name = if let Ok(x) = path {
x.path().display().to_string()
} else {
continue;
};
if let Some(caps) = re.captures(&name) {
let year: i32 = if let Some(cap) = caps.get(1) {
if let Ok(n) = cap.as_str().parse() {
n
} else {
0
}
} else {
0
};
let month: u32 = if let Some(cap) = caps.get(2) {
if let Ok(n) = cap.as_str().parse() {
n
} else {
0
}
} else {
0
};
let day: u32 = if let Some(cap) = caps.get(3) {
if let Ok(n) = cap.as_str().parse() {
n
} else {
0
}
} else {
0
};
if month != 0 && day != 0 {
let file_date = Local.ymd(year, month, day);
if file_date.add(Duration::days(self.retention_days)) < Local::today() {
if let Ok(_why) = fs::remove_file(name) {
count += 1;
}
}
}
}
}
count
}
fn current_file(&mut self) -> &File {
let date_changed = if let Some(log_file) = &self.log_file {
log_file.start_date < Local::today()
} else {
false
};
if date_changed {
self.log_file = None;
}
if let None = self.log_file {
let (start_date, file) =
Logger::new_today_file(&self.file_prefix, &self.file_suffix, &self.file_extention);
self.log_file = Some(LogFile::new(start_date, file));
}
&self.log_file.as_ref().unwrap().file
}
}
struct LogFile {
pub start_date: Date<Local>,
pub file: File,
}
impl LogFile {
pub fn new(start_date: Date<Local>, file: File) -> Self {
LogFile {
start_date: start_date,
file: file,
}
}
}
struct LastFlushTime {
pub last_flush_time: Instant,
}
impl Default for LastFlushTime {
fn default() -> Self {
LastFlushTime {
last_flush_time: Instant::now(),
}
}
}
impl LastFlushTime {
fn reset(&mut self) {
if 1 <= self.last_flush_time.elapsed().as_secs() {
self.last_flush_time = Instant::now();
}
}
fn can_flush(&self) -> bool {
1 <= self.last_flush_time.elapsed().as_secs()
}
}
pub enum Extension {
Log,
LogToml,
}