use std::ops::Deref;
use std::sync::atomic::AtomicI32;
use std::sync::RwLock;
use chrono::{Local};
use crossbeam_channel::{Receiver, SendError};
use log::{Level, Metadata, Record};
use crate::error::LogError;
use crate::filter::{Filter, NoFilter};
use crate::plugin::console::ConsoleAppender;
use crate::plugin::file::FileAppender;
use crate::plugin::file_split::FileSplitAppender;
use crate::appender::{FastLogRecord, LogAppender};
lazy_static! {
static ref LOG_SENDER:RwLock<Option<LoggerSender>>=RwLock::new(Option::None);
}
pub struct LoggerSender {
pub filter: Box<dyn Filter>,
pub std_sender: Option<crossbeam_channel::Sender<FastLogRecord>>,
}
impl LoggerSender {
pub fn new(runtime_type: RuntimeType, cap: usize, filter: Box<dyn Filter>) -> (Self, Receiver<FastLogRecord>) {
return match runtime_type {
_ => {
let (s, r) = crossbeam_channel::bounded(cap);
(Self {
std_sender: Some(s),
filter,
}, r)
}
};
}
pub fn send(&self, data: FastLogRecord) -> Result<(), SendError<FastLogRecord>> {
self.std_sender.as_ref().unwrap().send(data)
}
}
#[derive(Clone, Debug)]
pub enum RuntimeType {
Std
}
fn set_log(runtime_type: RuntimeType, cup: usize, level: log::Level, filter: Box<dyn Filter>) -> Receiver<FastLogRecord> {
LOGGER.set_level(level);
let mut w = LOG_SENDER.write().unwrap();
let (log, recv) = LoggerSender::new(runtime_type, cup, filter);
*w = Some(log);
return recv;
}
pub struct Logger {
level: AtomicI32,
}
impl Logger {
pub fn set_level(&self, level: log::Level) {
self.level.swap(level as i32, std::sync::atomic::Ordering::Relaxed);
}
pub fn get_level(&self) -> log::Level {
match self.level.load(std::sync::atomic::Ordering::Relaxed) {
1 => Level::Error,
2 => Level::Warn,
3 => Level::Info,
4 => Level::Debug,
5 => Level::Trace,
_ => panic!("error log level!")
}
}
}
impl log::Log for Logger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= self.get_level()
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let level = record.level();
if self.get_level() < level {
return;
}
match LOG_SENDER.read() {
Ok(lock) => {
match lock.deref() {
Some(sender) => {
if !sender.filter.filter(record) {
let mut fast_log_record = FastLogRecord {
level,
target: record.metadata().target().to_string(),
args: record.args().to_string(),
module_path: record.module_path().unwrap_or("").to_string(),
file: record.file().unwrap_or("").to_string(),
line: record.line().clone(),
now: Local::now(),
formated: "".to_string(),
};
fast_log_record.set_formated();
sender.send(fast_log_record);
}
}
_ => {}
}
}
_ => {}
}
}
}
fn flush(&self) {}
}
static LOGGER: Logger = Logger { level: AtomicI32::new(1) };
pub fn init_log(log_file_path: &str, channel_cup: usize, level: log::Level, mut filter: Option<Box<dyn Filter>>, debug_mode: bool) -> Result<(), Box<dyn std::error::Error + Send>> {
let mut appenders: Vec<Box<dyn LogAppender>> = vec![
Box::new(FileAppender::new(log_file_path))
];
if debug_mode {
appenders.push(Box::new(ConsoleAppender {}));
}
let mut log_filter: Box<dyn Filter> = Box::new(NoFilter {});
if filter.is_some() {
log_filter = filter.take().unwrap();
}
return init_custom_log(appenders, channel_cup, level, log_filter);
}
pub fn init_split_log(log_dir_path: &str, channel_log_cup: usize, split_log_cup: u64, allow_zip_compress: bool, level: log::Level, mut filter: Option<Box<dyn Filter>>, debug_mode: bool) -> Result<(), Box<dyn std::error::Error + Send>> {
let mut appenders: Vec<Box<dyn LogAppender>> = vec![
Box::new(FileSplitAppender::new(log_dir_path, split_log_cup, allow_zip_compress))
];
if debug_mode {
appenders.push(Box::new(ConsoleAppender {}));
}
let mut log_filter: Box<dyn Filter> = Box::new(NoFilter {});
if filter.is_some() {
log_filter = filter.take().unwrap();
}
return init_custom_log(appenders, channel_log_cup, level, log_filter);
}
pub fn init_custom_log(appenders: Vec<Box<dyn LogAppender>>, log_cup: usize, level: log::Level, filter: Box<dyn Filter>) -> Result<(), Box<dyn std::error::Error + Send>> {
if appenders.is_empty() {
return Err(Box::new(LogError::from("[fast_log] appenders can not be empty!")));
}
let main_recv = set_log(RuntimeType::Std, log_cup, level, filter);
if appenders.len() == 1 {
std::thread::spawn(move || {
loop {
let data = main_recv.recv();
if data.is_ok() {
let s: FastLogRecord = data.unwrap();
for x in &appenders {
x.do_log(&s);
}
}
}
});
} else {
let mut recvs = vec![];
let mut sends = vec![];
for idx in 0..appenders.len() {
let (s, r) = crossbeam_channel::bounded(log_cup);
recvs.push(r);
sends.push(s);
}
std::thread::spawn(move || {
loop {
let data = main_recv.recv();
if data.is_ok() {
let s: FastLogRecord = data.unwrap();
for x in &sends {
x.send(s.clone());
}
}
}
});
let mut index = 0;
for item in appenders {
let recv = recvs[index].to_owned();
std::thread::spawn(move || {
loop {
let data = recv.recv();
if data.is_ok() {
let s: FastLogRecord = data.unwrap();
item.do_log(&s);
}
}
});
index += 1;
}
}
let r = log::set_logger(&LOGGER).map(|()| log::set_max_level(level.to_level_filter()));
if r.is_err() {
return Err(Box::new(r.err().unwrap()));
} else {
return Ok(());
}
}
#[cfg(test)]
mod test {
use std::thread::sleep;
use std::time::{Duration, Instant};
use log::{info, Level,debug};
use log::error;
use crate::{init_custom_log, init_log, init_split_log};
use crate::bencher::QPS;
use crate::fast_log::{FastLogRecord, LogAppender};
use crate::filter::NoFilter;
#[test]
pub fn test_log() {
init_log("requests.log", 1000, log::Level::Debug, None, true);
debug!("Commencing yak shaving{}", 0);
sleep(Duration::from_secs(1));
}
#[test]
pub fn bench_log() {
init_log("requests.log", 1000, log::Level::Info, None, false);
let total = 10000;
let now = Instant::now();
for index in 0..total {
info!("Commencing yak shaving{}", index);
}
now.time(total);
now.qps(total);
sleep(Duration::from_secs(1));
}
struct CustomLog {}
impl LogAppender for CustomLog {
fn do_log(&self, record: &FastLogRecord) {
let data;
match record.level {
Level::Warn | Level::Error => {
data = format!("{} {} {} - {} {}\n", &record.now, record.level, record.module_path, record.args, record.format_line());
}
_ => {
data = format!("{} {} {} - {}\n", &record.now, record.level, record.module_path, record.args);
}
}
print!("{}", data);
}
}
#[test]
pub fn test_custom() {
init_custom_log(vec![Box::new(CustomLog {})], 1000, log::Level::Info, Box::new(NoFilter {}));
info!("Commencing yak shaving");
error!("Commencing error");
sleep(Duration::from_secs(1));
}
#[test]
pub fn test_file_compation() {
init_split_log("target/logs/", 1000, 10000, false, log::Level::Info, None, true);
for _ in 0..20000 {
info!("Commencing yak shaving");
}
sleep(Duration::from_secs(1));
}
#[test]
pub fn test_file_compation_zip() {
init_split_log("target/logs/", 1000, 100, true, log::Level::Info, None, true);
for _ in 0..200 {
info!("Commencing yak shaving");
}
sleep(Duration::from_secs(1));
}
}