use crate::{
log_event::LogEvent, log_level::LogLevel, logger_options::LoggerOptions,
timestamp::FormattedTimestamp,
};
use crossbeam_channel::{Receiver, RecvTimeoutError, Sender};
use crossbeam_queue::ArrayQueue;
use serde::Serialize;
use serde_json::{Map, Value};
use std::{io::Write, sync::Arc, time::Duration};
#[must_use = "Logger does nothing unless you keep it and call log methods like `.info()`"]
pub struct Logger {
pub(crate) sender: Option<Sender<Vec<u8>>>,
pub(crate) worker: Option<std::thread::JoinHandle<()>>,
pub(crate) buffer_pool: Arc<ArrayQueue<Vec<u8>>>,
pub(crate) buffer_pool_initial_capacity: usize,
pub(crate) min_level: LogLevel,
pub(crate) timestamp_format: Option<&'static str>,
pub(crate) timestamp_key: &'static str,
pub(crate) context: Map<String, Value>,
pub(crate) pretty: bool,
}
impl Default for Logger {
fn default() -> Self {
LoggerOptions::default().init()
}
}
impl Drop for Logger {
fn drop(&mut self) {
self.sender.take();
if let Some(worker) = self.worker.take() {
let _ = worker.join();
}
}
}
impl Logger {
pub fn builder() -> LoggerOptions {
LoggerOptions::default()
}
pub fn new() -> Self {
LoggerOptions::default().init()
}
pub fn info<CustomData: Serialize>(&self, message: impl AsRef<str>, custom_data: CustomData) {
self.log(LogLevel::Info, message.as_ref(), custom_data);
}
pub fn warn<CustomData: Serialize>(&self, message: impl AsRef<str>, custom_data: CustomData) {
self.log(LogLevel::Warn, message.as_ref(), custom_data);
}
pub fn error<CustomData: Serialize>(&self, message: impl AsRef<str>, custom_data: CustomData) {
self.log(LogLevel::Error, message.as_ref(), custom_data);
}
pub fn debug<CustomData: Serialize>(&self, message: impl AsRef<str>, custom_data: CustomData) {
self.log(LogLevel::Debug, message.as_ref(), custom_data);
}
fn should_log(&self, log_level: LogLevel) -> bool {
log_level.severity() >= self.min_level.severity()
}
fn log<CustomData: Serialize>(
&self,
log_level: LogLevel,
message: impl AsRef<str>,
custom_data: CustomData,
) {
if !self.should_log(log_level) {
return;
}
if self.sender.is_none() {
return;
}
let data = if size_of::<CustomData>() == 0 {
None
} else {
Some(&custom_data)
};
let log_event = LogEvent {
context: &self.context,
level: log_level.as_str(),
timestamp: FormattedTimestamp::new(self.timestamp_format),
timestamp_key: self.timestamp_key,
data,
message: message.as_ref(),
};
let mut buf = self
.buffer_pool
.pop()
.unwrap_or_else(|| Vec::with_capacity(self.buffer_pool_initial_capacity));
buf.clear();
let result = if self.pretty {
serde_json::to_writer_pretty(&mut buf, &log_event)
} else {
serde_json::to_writer(&mut buf, &log_event)
};
if let Err(e) = result {
eprintln!("Error ocurred converting log event to bytes. Error: {e}");
buf.clear();
let _ = self.buffer_pool.push(buf);
return;
};
buf.push(b'\n');
if let Some(sender) = &self.sender {
let _ = sender.send(buf);
}
}
pub(crate) fn handle_messages(
worker: Receiver<Vec<u8>>,
buffer_pool: Arc<ArrayQueue<Vec<u8>>>,
buffer_pool_max_capacity: usize,
buffer_pool_initial_capacity: usize,
flush_at_bytes: usize,
flush_at_messages: usize,
flush_interval: Duration,
) -> std::thread::JoinHandle<()> {
std::thread::spawn(move || {
let mut batch = Vec::<u8>::with_capacity(flush_at_bytes);
let mut batch_message_count: usize = 0;
loop {
match worker.recv_timeout(flush_interval) {
Ok(mut log_buffer) => {
batch.extend_from_slice(&log_buffer);
batch_message_count += 1;
log_buffer.clear();
if log_buffer.capacity() > buffer_pool_max_capacity {
log_buffer.shrink_to(buffer_pool_initial_capacity);
}
let _ = buffer_pool.push(log_buffer);
if batch_message_count >= flush_at_messages || batch.len() >= flush_at_bytes
{
Logger::flush(&mut batch);
batch_message_count = 0;
}
}
Err(RecvTimeoutError::Disconnected) => {
Logger::flush(&mut batch);
break;
}
Err(RecvTimeoutError::Timeout) => {
Logger::flush(&mut batch);
batch_message_count = 0;
}
}
}
})
}
fn flush(batch: &mut Vec<u8>) {
if batch.is_empty() {
return;
}
let mut out = std::io::stderr().lock();
let _ = out.write_all(batch);
let _ = out.flush();
batch.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_log_min_level_debug() {
let logger = LoggerOptions::default().min_level(LogLevel::Debug).init();
assert_eq!(logger.should_log(LogLevel::Debug), true);
assert_eq!(logger.should_log(LogLevel::Info), true);
assert_eq!(logger.should_log(LogLevel::Warn), true);
assert_eq!(logger.should_log(LogLevel::Error), true);
}
#[test]
fn test_should_log_min_level_info() {
let logger = LoggerOptions::default().min_level(LogLevel::Info).init();
assert_eq!(logger.should_log(LogLevel::Debug), false);
assert_eq!(logger.should_log(LogLevel::Info), true);
assert_eq!(logger.should_log(LogLevel::Warn), true);
assert_eq!(logger.should_log(LogLevel::Error), true);
}
#[test]
fn test_should_log_min_level_warn() {
let logger = LoggerOptions::default().min_level(LogLevel::Warn).init();
assert_eq!(logger.should_log(LogLevel::Debug), false);
assert_eq!(logger.should_log(LogLevel::Info), false);
assert_eq!(logger.should_log(LogLevel::Warn), true);
assert_eq!(logger.should_log(LogLevel::Error), true);
}
#[test]
fn test_should_log_min_level_error() {
let logger = LoggerOptions::default().min_level(LogLevel::Error).init();
assert_eq!(logger.should_log(LogLevel::Debug), false);
assert_eq!(logger.should_log(LogLevel::Info), false);
assert_eq!(logger.should_log(LogLevel::Warn), false);
assert_eq!(logger.should_log(LogLevel::Error), true);
}
#[test]
fn test_should_log_sender_exists() {
let logger = LoggerOptions::default().min_level(LogLevel::Error).init();
assert_eq!(logger.should_log(LogLevel::Error), true);
}
}