lexa_logger/stdout/
mod.rs1mod builder;
12mod extension;
13
14use std::collections::HashMap;
15use std::sync::{Arc, Mutex};
16
17use console::style;
18
19pub use self::builder::LoggerStdoutBuilder;
20pub use self::extension::LoggerStdoutBuilderExtension;
21use crate::builder::{LoggerFilterCallback, LoggerFormatFn};
22use crate::echo::Echo;
23use crate::layout;
24
25pub struct LoggerStdout
31{
32 pub(crate) colorized: bool,
33 pub(crate) timestamp: bool,
34 #[cfg(not(feature = "tracing"))]
35 pub(crate) level: log::LevelFilter,
36 #[cfg(feature = "tracing")]
37 pub(crate) level: tracing::level_filters::LevelFilter,
38 pub(crate) format_fn: LoggerFormatFn,
39 pub(crate) filter: LoggerFilter,
40 pub(crate) cache: Arc<Mutex<HashMap<String, bool>>>,
41}
42
43#[derive(Default)]
44pub struct LoggerFilter
45{
46 callbacks: Vec<Box<LoggerFilterCallback>>,
47 dependencies: Vec<String>,
48}
49
50impl LoggerStdout
55{
56 pub fn builder() -> builder::LoggerStdoutBuilder
57 {
58 builder::LoggerStdoutBuilder::default()
59 }
60}
61
62impl LoggerStdout
63{
64 #[cfg(not(feature = "tracing"))]
65 pub fn level(&self) -> log::LevelFilter
66 {
67 self.level
68 }
69
70 #[cfg(feature = "tracing")]
71 pub fn level(&self) -> tracing::level_filters::LevelFilter
72 {
73 self.level
74 }
75
76 fn default_format(message: &std::fmt::Arguments, record: &log::Record, echo: &mut Echo) -> String
77 {
78 let local_date_format = echo
79 .time
80 .map(|local_datetime| local_datetime.format("%Y-%m-%d@%H:%M:%S"));
81
82 if let Some(time) = local_date_format {
83 echo.table.add_line([
84 layout::Cell::new(&echo.level).with_alignment(layout::Alignment::Right),
85 layout::Cell::new(&echo.delimiter),
86 layout::Cell::new(time),
87 layout::Cell::new(&echo.delimiter),
88 layout::Cell::new(format!(
89 "{} {} {}",
90 if echo.colorized {
91 style(record.target()).black().bright()
92 } else {
93 style(record.target())
94 },
95 if echo.colorized { style("->").red() } else { style("->") },
96 message
97 )),
98 ]);
99 } else {
100 echo.table.add_line([
101 layout::Cell::new(&echo.level).with_alignment(layout::Alignment::Right),
102 layout::Cell::new(&echo.delimiter),
103 layout::Cell::new(style(record.target()).black().bright()),
104 layout::Cell::new(style("->").red()),
105 layout::Cell::new(message),
106 ]);
107 }
108
109 echo.table.render()
110 }
111}
112
113impl LoggerFilter
114{
115 pub(crate) fn push_callback<F>(&mut self, predicate: F)
117 where
118 F: 'static,
119 F: Send + Sync,
120 F: Fn(&log::Metadata) -> bool,
121 {
122 self.callbacks.push(Box::new(predicate));
123 }
124
125 pub(crate) fn add_dependency(&mut self, dependency: impl ToString)
127 {
128 self.dependencies.push(dependency.to_string());
129 }
130}
131
132impl log::Log for LoggerStdout
137{
138 fn enabled(&self, metadata: &log::Metadata) -> bool
144 {
145 let mut guard = self.cache.lock().expect("cache guard");
146
147 metadata.level() != log::LevelFilter::Off
148 && (self.filter.callbacks.is_empty()
149 || self.filter.callbacks.iter().enumerate().any(|(idx, once_fn)| {
150 let cache_key = format!("{}_{}", self.filter.dependencies[idx], metadata.target());
151
152 if let Some(has) = guard.get(&cache_key) {
153 return *has;
154 }
155
156 let is_ok = once_fn(metadata);
157 guard.insert(cache_key, is_ok);
158 is_ok
159 }))
160 }
161
162 fn log(&self, record: &log::Record)
167 {
168 if !self.enabled(record.metadata()) {
169 return;
170 }
171
172 let message = record.args();
173 if message.to_string().trim().is_empty() {
174 return;
175 }
176
177 let level = if self.colorized {
178 match record.level() {
179 | log::Level::Error => style("ERROR").red(),
180 | log::Level::Warn => style(" WARN").yellow(),
181 | log::Level::Info => style(" INFO").blue(),
182 | log::Level::Debug => style("DEBUG").magenta(),
183 | log::Level::Trace => style("TRACE").white(),
184 }
185 .to_string()
186 } else {
187 record.level().to_string()
188 };
189
190 let mut table = layout::GridLayout::default().define_max_width(120).without_boarder();
191
192 let mut echo = Echo {
193 colorized: self.colorized,
194 delimiter: if self.colorized { style("|").red() } else { style("|") }.to_string(),
195 level,
196 record_level: record.level(),
197 table: &mut table,
198 time: if self.timestamp {
199 Some(chrono::Local::now())
200 } else {
201 None
202 },
203 };
204
205 let text = (self.format_fn)(message, record, &mut echo);
206
207 echo.log(text);
208 }
209
210 fn flush(&self) {}
211}