use crate::level::Level;
use crate::style::LogStyle;
use chrono::Local;
use glyphs::style;
use std::fmt::Display;
use std::io::{self, Write};
#[derive(Debug, Clone)]
pub struct Logger {
style: LogStyle,
min_level: Level,
prefix: Option<String>,
}
impl Logger {
pub fn new() -> Self {
Self {
style: LogStyle::default(),
min_level: Level::Debug,
prefix: None,
}
}
#[must_use]
pub fn style(mut self, style: LogStyle) -> Self {
self.style = style;
self
}
#[must_use]
pub fn with_timestamp(mut self) -> Self {
self.style.timestamp = true;
self
}
#[must_use]
pub fn with_icons(mut self) -> Self {
self.style.show_icons = true;
self
}
#[must_use]
pub fn min_level(mut self, level: Level) -> Self {
self.min_level = level;
self
}
#[must_use]
pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
self.prefix = Some(prefix.into());
self
}
pub fn log(&self, level: Level, message: &str) {
if level < self.min_level {
return;
}
self.write_log(level, message, &[]);
}
pub fn log_kv(&self, level: Level, message: &str, kvs: &[(&str, &dyn Display)]) {
if level < self.min_level {
return;
}
self.write_log(level, message, kvs);
}
fn write_log(&self, level: Level, message: &str, kvs: &[(&str, &dyn Display)]) {
let mut output = String::new();
if self.style.timestamp {
let timestamp = Local::now().format(&self.style.timestamp_format).to_string();
output.push_str(&style(×tamp).fg(self.style.timestamp_color).to_string());
output.push(' ');
}
if self.style.show_level {
let level_str = if self.style.short_level {
level.short_name()
} else {
level.name()
};
if self.style.show_icons {
output.push_str(level.icon());
output.push(' ');
}
output.push_str(&style(level_str).fg(level.color()).bold().to_string());
output.push(' ');
}
if let Some(ref prefix) = self.prefix {
output.push_str(&style(format!("[{}]", prefix)).dim().to_string());
output.push(' ');
}
output.push_str(&style(message).fg(self.style.message_color).to_string());
if !kvs.is_empty() {
output.push(' ');
let kv_parts: Vec<String> = kvs
.iter()
.map(|(k, v)| {
format!(
"{}{}{}",
style(*k).fg(self.style.key_color),
self.style.kv_separator,
style(v.to_string()).fg(self.style.value_color)
)
})
.collect();
output.push_str(&kv_parts.join(&self.style.separator));
}
let mut stderr = io::stderr().lock();
writeln!(stderr, "{}", output).ok();
}
pub fn debug(&self, message: &str) {
self.log(Level::Debug, message);
}
pub fn debug_kv(&self, message: &str, kvs: &[(&str, &dyn Display)]) {
self.log_kv(Level::Debug, message, kvs);
}
pub fn info(&self, message: &str) {
self.log(Level::Info, message);
}
pub fn info_kv(&self, message: &str, kvs: &[(&str, &dyn Display)]) {
self.log_kv(Level::Info, message, kvs);
}
pub fn warn(&self, message: &str) {
self.log(Level::Warn, message);
}
pub fn warn_kv(&self, message: &str, kvs: &[(&str, &dyn Display)]) {
self.log_kv(Level::Warn, message, kvs);
}
pub fn error(&self, message: &str) {
self.log(Level::Error, message);
}
pub fn error_kv(&self, message: &str, kvs: &[(&str, &dyn Display)]) {
self.log_kv(Level::Error, message, kvs);
}
pub fn fatal(&self, message: &str) {
self.log(Level::Fatal, message);
}
pub fn fatal_kv(&self, message: &str, kvs: &[(&str, &dyn Display)]) {
self.log_kv(Level::Fatal, message, kvs);
}
}
impl Default for Logger {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_logger_creation() {
let logger = Logger::new().with_timestamp().with_icons();
assert!(logger.style.timestamp);
assert!(logger.style.show_icons);
}
}