use crate::RecordKind;
use crate::record::Record;
use std::borrow::Cow;
use std::collections;
use std::io::Write;
use std::str::FromStr;
use std::sync::mpsc;
pub trait Logger: Send + 'static {
fn log(&mut self, record: Record);
}
impl Logger for Box<dyn Logger> {
fn log(&mut self, record: Record) {
(**self).log(record)
}
}
#[derive(Debug, Clone)]
pub struct ConsoleLogger {
level: log::Level,
prefix: Option<Cow<'static, str>>,
}
impl ConsoleLogger {
pub fn new(level: &str) -> Result<Self, log::ParseLevelError> {
let level = log::Level::from_str(level)?;
Ok(Self {
level,
prefix: None,
})
}
pub fn new_unchecked(level: &str) -> Self {
Self::new(level).unwrap()
}
pub fn with_prefix(mut self, prefix: impl Into<Cow<'static, str>>) -> Self {
self.prefix = Some(prefix.into());
self
}
pub fn set_prefix(&mut self, prefix: impl Into<Cow<'static, str>>) {
self.prefix = Some(prefix.into());
}
pub fn clear_prefix(&mut self) {
self.prefix = None;
}
#[inline]
pub fn prefix(&self) -> Option<&str> {
self.prefix.as_deref()
}
}
impl Logger for ConsoleLogger {
fn log(&mut self, record: Record) {
let level = match record.kind {
RecordKind::Error => log::Level::Error,
_ => self.level,
};
match self.prefix.as_deref() {
Some(prefix) => log::log!(level, "{}{} {}", prefix, record.kind, record.message),
None => log::log!(level, "{} {}", record.kind, record.message),
}
}
}
impl Logger for Box<ConsoleLogger> {
fn log(&mut self, record: Record) {
(**self).log(record)
}
}
#[derive(Debug, Clone)]
pub struct MemoryStorageLogger {
storage: collections::VecDeque<Record>,
max_length: usize,
}
impl MemoryStorageLogger {
pub fn new(max_length: usize) -> Self {
Self {
storage: collections::VecDeque::new(),
max_length,
}
}
#[inline]
pub fn get_log_records(&self) -> collections::VecDeque<Record> {
self.storage.clone()
}
#[inline]
pub fn clear_log_records(&mut self) {
self.storage.clear()
}
}
impl Logger for MemoryStorageLogger {
fn log(&mut self, record: Record) {
self.storage.push_back(record);
if self.storage.len() > self.max_length {
let _ = self.storage.pop_front();
}
}
}
impl Logger for Box<MemoryStorageLogger> {
fn log(&mut self, record: Record) {
(**self).log(record)
}
}
#[derive(Debug)]
pub struct ChannelLogger {
sender: mpsc::Sender<Record>,
receiver: Option<mpsc::Receiver<Record>>,
}
impl ChannelLogger {
pub fn new() -> Self {
let (sender, receiver) = mpsc::channel();
Self {
sender,
receiver: Some(receiver),
}
}
#[inline]
pub fn take_receiver(&mut self) -> Option<mpsc::Receiver<Record>> {
self.receiver.take()
}
pub fn take_receiver_unchecked(&mut self) -> mpsc::Receiver<Record> {
self.take_receiver().unwrap()
}
}
impl Default for ChannelLogger {
fn default() -> Self {
Self::new()
}
}
impl Logger for ChannelLogger {
fn log(&mut self, record: Record) {
let _ = self.sender.send(record);
}
}
impl Logger for Box<ChannelLogger> {
fn log(&mut self, record: Record) {
(**self).log(record)
}
}
pub struct FileLogger {
file: std::fs::File,
}
impl FileLogger {
pub fn new(file: std::fs::File) -> Self {
Self { file }
}
}
impl Logger for FileLogger {
fn log(&mut self, record: Record) {
let _ = writeln!(
self.file,
"[{}] {} {}",
record.time.format("%+"),
record.kind,
record.message
);
}
}
impl Logger for Box<FileLogger> {
fn log(&mut self, record: Record) {
(**self).log(record)
}
}
#[cfg(test)]
mod tests {
use crate::logger::ChannelLogger;
use crate::logger::ConsoleLogger;
use crate::logger::FileLogger;
use crate::logger::Logger;
use crate::logger::MemoryStorageLogger;
use crate::record::Record;
use crate::record::RecordKind;
use std::cell::RefCell;
use std::sync::Once;
thread_local! {
static CAPTURED: RefCell<Vec<(log::Level, String)>> = const { RefCell::new(Vec::new()) };
}
struct CapturingLogger;
impl log::Log for CapturingLogger {
fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool {
true
}
fn log(&self, record: &log::Record<'_>) {
CAPTURED.with(|captured| {
captured
.borrow_mut()
.push((record.level(), format!("{}", record.args())))
});
}
fn flush(&self) {}
}
static CAPTURING_LOGGER: CapturingLogger = CapturingLogger;
static INIT_CAPTURING_LOGGER: Once = Once::new();
fn install_capturing_logger() {
INIT_CAPTURING_LOGGER.call_once(|| {
let _ = log::set_logger(&CAPTURING_LOGGER);
log::set_max_level(log::LevelFilter::Trace);
});
CAPTURED.with(|captured| captured.borrow_mut().clear());
}
fn captured_lines() -> Vec<String> {
CAPTURED.with(|captured| {
captured
.borrow()
.iter()
.map(|(_, msg)| msg.clone())
.collect()
})
}
fn captured_records() -> Vec<(log::Level, String)> {
CAPTURED.with(|captured| captured.borrow().clone())
}
fn assert_unpin<T: Unpin>() {}
#[test]
fn test_unpin() {
assert_unpin::<ConsoleLogger>();
assert_unpin::<ChannelLogger>();
assert_unpin::<MemoryStorageLogger>();
assert_unpin::<FileLogger>();
}
#[test]
fn test_trait_object_safety() {
let mut console: Box<dyn Logger> = Box::new(ConsoleLogger::new_unchecked("debug"));
let mut memory: Box<dyn Logger> = Box::new(MemoryStorageLogger::new(100));
let mut channel: Box<dyn Logger> = Box::new(ChannelLogger::new());
let record = Record::new(RecordKind::Open, String::from("test log record"));
console.log(record.clone());
memory.log(record.clone());
channel.log(record);
}
fn assert_logger<T: Logger>() {}
#[test]
fn test_box() {
assert_logger::<Box<dyn Logger>>();
assert_logger::<Box<ConsoleLogger>>();
assert_logger::<Box<MemoryStorageLogger>>();
assert_logger::<Box<ChannelLogger>>();
assert_logger::<Box<FileLogger>>();
}
#[test]
fn test_console_logger_prefix_default_none() {
assert_eq!(ConsoleLogger::new_unchecked("debug").prefix(), None);
assert_eq!(ConsoleLogger::new("info").unwrap().prefix(), None);
}
#[test]
fn test_console_logger_with_prefix() {
let logger = ConsoleLogger::new_unchecked("debug").with_prefix("[conn 5] ");
assert_eq!(logger.prefix(), Some("[conn 5] "));
let id = 42;
let logger = ConsoleLogger::new_unchecked("debug").with_prefix(format!("[conn {id}] "));
assert_eq!(logger.prefix(), Some("[conn 42] "));
}
#[test]
fn test_console_logger_set_and_clear_prefix() {
let mut logger = ConsoleLogger::new_unchecked("debug");
assert_eq!(logger.prefix(), None);
logger.set_prefix(String::from("[server] "));
assert_eq!(logger.prefix(), Some("[server] "));
logger.set_prefix("[client] ");
assert_eq!(logger.prefix(), Some("[client] "));
logger.clear_prefix();
assert_eq!(logger.prefix(), None);
}
#[test]
fn test_console_logger_logs_prefix_before_kind() {
install_capturing_logger();
let mut logger = ConsoleLogger::new_unchecked("debug");
logger.log(Record::new(RecordKind::Write, String::from("ab:cd")));
logger.set_prefix("[conn 5] ");
logger.log(Record::new(RecordKind::Read, String::from("01:02")));
logger.clear_prefix();
logger.log(Record::new(
RecordKind::Shutdown,
String::from("Writer shutdown request."),
));
assert_eq!(
captured_lines(),
vec![
String::from("> ab:cd"),
String::from("[conn 5] < 01:02"),
String::from("- Writer shutdown request."),
]
);
}
#[test]
fn test_console_logger_forces_error_level() {
install_capturing_logger();
let mut logger = ConsoleLogger::new_unchecked("debug");
logger.log(Record::new(RecordKind::Write, String::from("01:02")));
logger.log(Record::new(RecordKind::Error, String::from("boom")));
logger.set_prefix("[conn 5] ");
logger.log(Record::new(RecordKind::Error, String::from("kaboom")));
assert_eq!(
captured_records(),
vec![
(log::Level::Debug, String::from("> 01:02")),
(log::Level::Error, String::from("! boom")),
(log::Level::Error, String::from("[conn 5] ! kaboom")),
]
);
}
#[test]
fn test_console_logger_empty_prefix_matches_no_prefix() {
install_capturing_logger();
let mut logger = ConsoleLogger::new_unchecked("debug");
logger.log(Record::new(RecordKind::Write, String::from("01:02")));
logger.set_prefix("");
logger.log(Record::new(RecordKind::Write, String::from("01:02")));
let lines = captured_lines();
assert_eq!(lines.len(), 2);
assert_eq!(lines[0], lines[1]);
assert_eq!(lines[0], "> 01:02");
}
fn assert_send<T: Send>() {}
#[test]
fn test_send() {
assert_send::<ConsoleLogger>();
assert_send::<MemoryStorageLogger>();
assert_send::<ChannelLogger>();
assert_send::<FileLogger>();
assert_send::<Box<dyn Logger>>();
assert_send::<Box<ConsoleLogger>>();
assert_send::<Box<MemoryStorageLogger>>();
assert_send::<Box<ChannelLogger>>();
assert_send::<Box<FileLogger>>();
}
}