1use crate::level::Level;
4use crate::style::LogStyle;
5use chrono::Local;
6use glyphs::style;
7use std::fmt::Display;
8use std::io::{self, Write};
9
10#[derive(Debug, Clone)]
12pub struct Logger {
13 style: LogStyle,
14 min_level: Level,
15 prefix: Option<String>,
16}
17
18impl Logger {
19 pub fn new() -> Self {
21 Self {
22 style: LogStyle::default(),
23 min_level: Level::Debug,
24 prefix: None,
25 }
26 }
27
28 #[must_use]
30 pub fn style(mut self, style: LogStyle) -> Self {
31 self.style = style;
32 self
33 }
34
35 #[must_use]
37 pub fn with_timestamp(mut self) -> Self {
38 self.style.timestamp = true;
39 self
40 }
41
42 #[must_use]
44 pub fn with_icons(mut self) -> Self {
45 self.style.show_icons = true;
46 self
47 }
48
49 #[must_use]
51 pub fn min_level(mut self, level: Level) -> Self {
52 self.min_level = level;
53 self
54 }
55
56 #[must_use]
58 pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
59 self.prefix = Some(prefix.into());
60 self
61 }
62
63 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 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 if self.style.timestamp {
84 let timestamp = Local::now().format(&self.style.timestamp_format).to_string();
85 output.push_str(&style(×tamp).fg(self.style.timestamp_color).to_string());
86 output.push(' ');
87 }
88
89 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 if let Some(ref prefix) = self.prefix {
108 output.push_str(&style(format!("[{}]", prefix)).dim().to_string());
109 output.push(' ');
110 }
111
112 output.push_str(&style(message).fg(self.style.message_color).to_string());
114
115 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 let mut stderr = io::stderr().lock();
134 writeln!(stderr, "{}", output).ok();
135 }
136
137 pub fn debug(&self, message: &str) {
139 self.log(Level::Debug, message);
140 }
141
142 pub fn debug_kv(&self, message: &str, kvs: &[(&str, &dyn Display)]) {
144 self.log_kv(Level::Debug, message, kvs);
145 }
146
147 pub fn info(&self, message: &str) {
149 self.log(Level::Info, message);
150 }
151
152 pub fn info_kv(&self, message: &str, kvs: &[(&str, &dyn Display)]) {
154 self.log_kv(Level::Info, message, kvs);
155 }
156
157 pub fn warn(&self, message: &str) {
159 self.log(Level::Warn, message);
160 }
161
162 pub fn warn_kv(&self, message: &str, kvs: &[(&str, &dyn Display)]) {
164 self.log_kv(Level::Warn, message, kvs);
165 }
166
167 pub fn error(&self, message: &str) {
169 self.log(Level::Error, message);
170 }
171
172 pub fn error_kv(&self, message: &str, kvs: &[(&str, &dyn Display)]) {
174 self.log_kv(Level::Error, message, kvs);
175 }
176
177 pub fn fatal(&self, message: &str) {
179 self.log(Level::Fatal, message);
180 }
181
182 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