censer/
logger.rs

1//! Logger implementation.
2
3use crate::level::Level;
4use crate::style::LogStyle;
5use chrono::Local;
6use glyphs::style;
7use std::fmt::Display;
8use std::io::{self, Write};
9
10/// A styled logger.
11#[derive(Debug, Clone)]
12pub struct Logger {
13    style: LogStyle,
14    min_level: Level,
15    prefix: Option<String>,
16}
17
18impl Logger {
19    /// Create a new logger.
20    pub fn new() -> Self {
21        Self {
22            style: LogStyle::default(),
23            min_level: Level::Debug,
24            prefix: None,
25        }
26    }
27
28    /// Set the style.
29    #[must_use]
30    pub fn style(mut self, style: LogStyle) -> Self {
31        self.style = style;
32        self
33    }
34
35    /// Enable timestamps.
36    #[must_use]
37    pub fn with_timestamp(mut self) -> Self {
38        self.style.timestamp = true;
39        self
40    }
41
42    /// Enable icons.
43    #[must_use]
44    pub fn with_icons(mut self) -> Self {
45        self.style.show_icons = true;
46        self
47    }
48
49    /// Set minimum log level.
50    #[must_use]
51    pub fn min_level(mut self, level: Level) -> Self {
52        self.min_level = level;
53        self
54    }
55
56    /// Set a prefix for all messages.
57    #[must_use]
58    pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
59        self.prefix = Some(prefix.into());
60        self
61    }
62
63    /// Log at a specific level.
64    pub fn log(&self, level: Level, message: &str) {
65        if level < self.min_level {
66            return;
67        }
68        self.write_log(level, message, &[]);
69    }
70
71    /// Log with key-value pairs.
72    pub fn log_kv(&self, level: Level, message: &str, kvs: &[(&str, &dyn Display)]) {
73        if level < self.min_level {
74            return;
75        }
76        self.write_log(level, message, kvs);
77    }
78
79    fn write_log(&self, level: Level, message: &str, kvs: &[(&str, &dyn Display)]) {
80        let mut output = String::new();
81
82        // Timestamp
83        if self.style.timestamp {
84            let timestamp = Local::now().format(&self.style.timestamp_format).to_string();
85            output.push_str(&style(&timestamp).fg(self.style.timestamp_color).to_string());
86            output.push(' ');
87        }
88
89        // Level
90        if self.style.show_level {
91            let level_str = if self.style.short_level {
92                level.short_name()
93            } else {
94                level.name()
95            };
96
97            if self.style.show_icons {
98                output.push_str(level.icon());
99                output.push(' ');
100            }
101
102            output.push_str(&style(level_str).fg(level.color()).bold().to_string());
103            output.push(' ');
104        }
105
106        // Prefix
107        if let Some(ref prefix) = self.prefix {
108            output.push_str(&style(format!("[{}]", prefix)).dim().to_string());
109            output.push(' ');
110        }
111
112        // Message
113        output.push_str(&style(message).fg(self.style.message_color).to_string());
114
115        // Key-value pairs
116        if !kvs.is_empty() {
117            output.push(' ');
118            let kv_parts: Vec<String> = kvs
119                .iter()
120                .map(|(k, v)| {
121                    format!(
122                        "{}{}{}",
123                        style(*k).fg(self.style.key_color),
124                        self.style.kv_separator,
125                        style(v.to_string()).fg(self.style.value_color)
126                    )
127                })
128                .collect();
129            output.push_str(&kv_parts.join(&self.style.separator));
130        }
131
132        // Write to stderr
133        let mut stderr = io::stderr().lock();
134        writeln!(stderr, "{}", output).ok();
135    }
136
137    /// Log at debug level.
138    pub fn debug(&self, message: &str) {
139        self.log(Level::Debug, message);
140    }
141
142    /// Log at debug level with key-value pairs.
143    pub fn debug_kv(&self, message: &str, kvs: &[(&str, &dyn Display)]) {
144        self.log_kv(Level::Debug, message, kvs);
145    }
146
147    /// Log at info level.
148    pub fn info(&self, message: &str) {
149        self.log(Level::Info, message);
150    }
151
152    /// Log at info level with key-value pairs.
153    pub fn info_kv(&self, message: &str, kvs: &[(&str, &dyn Display)]) {
154        self.log_kv(Level::Info, message, kvs);
155    }
156
157    /// Log at warn level.
158    pub fn warn(&self, message: &str) {
159        self.log(Level::Warn, message);
160    }
161
162    /// Log at warn level with key-value pairs.
163    pub fn warn_kv(&self, message: &str, kvs: &[(&str, &dyn Display)]) {
164        self.log_kv(Level::Warn, message, kvs);
165    }
166
167    /// Log at error level.
168    pub fn error(&self, message: &str) {
169        self.log(Level::Error, message);
170    }
171
172    /// Log at error level with key-value pairs.
173    pub fn error_kv(&self, message: &str, kvs: &[(&str, &dyn Display)]) {
174        self.log_kv(Level::Error, message, kvs);
175    }
176
177    /// Log at fatal level.
178    pub fn fatal(&self, message: &str) {
179        self.log(Level::Fatal, message);
180    }
181
182    /// Log at fatal level with key-value pairs.
183    pub fn fatal_kv(&self, message: &str, kvs: &[(&str, &dyn Display)]) {
184        self.log_kv(Level::Fatal, message, kvs);
185    }
186}
187
188impl Default for Logger {
189    fn default() -> Self {
190        Self::new()
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197
198    #[test]
199    fn test_logger_creation() {
200        let logger = Logger::new().with_timestamp().with_icons();
201        assert!(logger.style.timestamp);
202        assert!(logger.style.show_icons);
203    }
204}
205