1use colored::*;
3use log::{Level, LevelFilter, Log, Metadata, Record, SetLoggerError};
4use rustc_hash::FxHashMap;
5use std::{
6 fs::File,
7 io::Write,
8 path::Path,
9 sync::{
10 Arc, Mutex,
11 mpsc::{Sender, channel},
12 },
13 thread,
14};
15use strip_ansi_escapes::strip;
16use termsize::Size;
17
18use crate::constants;
19
20pub struct TreeLogger {
21 default_level: LevelFilter,
22 threads_enabled: bool,
23 colors_enabled: bool,
24 use_stderr: bool,
25 filter_fn: fn(&LoggingEvent) -> bool,
26 data: LoggingData,
27 maybe_sender: Option<Sender<String>>,
28}
29
30#[derive(Debug, Default, Clone)]
31struct LoggingData {
32 internal_data: Arc<Mutex<FxHashMap<String, InternalLoggingData>>>,
34}
35
36#[derive(Debug, Default, Clone)]
37struct InternalLoggingData {
38 indentation: usize,
39 next_id: usize,
40 events: Vec<LoggingEvent>,
41}
42
43#[derive(Debug, Clone)]
44pub struct LoggingEvent {
45 pub id: Option<usize>,
46 pub indentation: usize,
47 pub elapsed: Option<u128>,
48 pub level: Level,
49 pub target: String,
50 pub args: String,
51 pub thread: String,
52 pub quiet: bool,
53}
54
55impl LoggingEvent {
56 fn get_args(&self) -> String {
57 use ansi_term::Colour::{Cyan, Red};
58 match self.elapsed {
59 Some(elapsed) => {
60 if elapsed > 100 {
61 format!("{}: {}", self.args, Red.paint(format!("{elapsed}ms")))
62 } else {
63 format!("{}: {}", self.args, Cyan.paint(format!("{elapsed}ms")))
64 }
65 }
66 None => self.args.clone(),
67 }
68 }
69}
70
71impl LoggingData {
72 fn get_name(&self) -> String {
73 let thread = std::thread::current();
74 thread.name().unwrap_or("default").to_string()
75 }
76
77 fn increment(&self) {
78 let mut data = self.internal_data.lock().unwrap();
79 let data = data.entry(self.get_name()).or_default();
80 data.indentation += 1;
81 }
82
83 fn decrement(&self) {
84 let mut data = self.internal_data.lock().unwrap();
85 let data = data.entry(self.get_name()).or_default();
86 data.indentation -= 1;
87 }
88
89 fn push_record(&self, record: &Record, should_log_thread: bool) {
90 let id = if let Some(id_value) = record.key_values().get(constants::ID.into()) {
91 if let Ok(id) = id_value.to_string().parse::<usize>() {
92 Some(id)
93 } else {
94 None
95 }
96 } else {
97 None
98 };
99
100 let quiet = if let Some(quiet) = record.key_values().get(constants::QUIET.into()) {
101 match quiet.to_string().parse::<usize>() {
102 Ok(quiet) => quiet == 1,
103 Err(_) => false,
104 }
105 } else {
106 false
107 };
108
109 self.push(LoggingEvent {
110 id,
111 quiet,
112 level: record.level(),
113 target: if !record.target().is_empty() {
114 record.target()
115 } else {
116 record.module_path().unwrap_or_default()
117 }
118 .to_string(),
119
120 args: record.args().to_string(),
121 indentation: 0,
122 elapsed: None,
123 thread: if should_log_thread {
124 let thread = std::thread::current();
125
126 match thread.name() {
127 Some(name) => {
128 if name == "main" {
129 "".into()
130 } else {
131 format!(" @{name}")
132 }
133 }
134 None => "".into(),
135 }
136 } else {
137 "".into()
138 },
139 });
140 }
141
142 fn push(&self, mut event: LoggingEvent) -> usize {
143 let mut data = self.internal_data.lock().unwrap();
144 let data = data.entry(self.get_name()).or_default();
145 event.indentation = data.indentation;
146
147 let id = data.next_id;
149 data.next_id += 1;
150
151 data.events.push(event);
152 id
153 }
154
155 fn get_data_to_log(&self) -> Option<Vec<LoggingEvent>> {
156 let mut data = self.internal_data.lock().unwrap();
157 let data = data.entry(self.get_name()).or_default();
158 if data.indentation == 0 {
159 let mut rv = Vec::new();
160 std::mem::swap(&mut data.events, &mut rv);
161 return Some(rv);
162 }
163 None
164 }
165
166 fn set_time(&self, id: usize, ms: u128) {
167 let mut data = self.internal_data.lock().unwrap();
168 let data = data.entry(self.get_name()).or_default();
169 for record in &mut data.events {
170 if let Some(record_id) = record.id {
171 if record_id == id {
172 record.elapsed = Some(ms);
173 return;
174 }
175 }
176 }
177 }
179}
180
181impl Default for TreeLogger {
182 fn default() -> Self {
183 Self::new()
184 }
185}
186
187impl TreeLogger {
188 #[must_use = "You must call init() to begin logging"]
199 pub fn new() -> TreeLogger {
200 TreeLogger {
201 default_level: LevelFilter::Trace,
202 threads_enabled: false,
203 colors_enabled: false,
204 use_stderr: false,
205 filter_fn: |_| true,
206 data: LoggingData::default(),
207 maybe_sender: None,
208 }
209 }
210
211 pub fn init(self) -> Result<(), SetLoggerError> {
212 log::set_max_level(self.max_level());
213 log::set_boxed_logger(Box::new(self))
214 }
215
216 #[must_use = "You must call init() to begin logging"]
217 pub fn with_filter_fn(mut self, filter_fn: fn(&LoggingEvent) -> bool) -> TreeLogger {
218 self.filter_fn = filter_fn;
219 self
220 }
221
222 #[must_use = "You must call init() to begin logging"]
223 pub fn with_level(mut self, level: LevelFilter) -> TreeLogger {
224 self.default_level = level;
225 self
226 }
227
228 #[must_use = "You must call init() to begin logging"]
229 pub fn with_file<T: AsRef<Path>>(mut self, path: T, append: bool) -> TreeLogger {
230 if self.maybe_sender.is_some() {
231 panic!("Can't set file more than once");
232 }
233
234 let (sender, receiver) = channel::<String>();
236 thread::spawn({
237 let mut file = if append {
238 File::options()
239 .write(true)
240 .create(true)
241 .append(true)
242 .open(path)
243 .unwrap()
244 } else {
245 File::create(path).unwrap()
246 };
247
248 move || {
249 while let Ok(value) = receiver.recv() {
250 _ = writeln!(file, "{}", value);
251 }
252 }
253 });
254
255 self.maybe_sender = Some(sender);
256 self
257 }
258
259 #[must_use = "You must call init() to begin logging"]
260 pub fn with_threads(mut self, enable_threads: bool) -> TreeLogger {
261 self.threads_enabled = enable_threads;
262 self
263 }
264
265 #[must_use = "You must call init() to begin logging"]
267 pub fn with_colors(mut self, enable_colors: bool) -> TreeLogger {
268 self.colors_enabled = enable_colors;
269 self
270 }
271
272 pub fn max_level(&self) -> LevelFilter {
273 self.default_level
274 }
275
276 fn get_level_string(&self, level: Level) -> String {
277 let level_string = format!("{:<5}", level.to_string());
278 if self.colors_enabled {
279 match level {
280 Level::Error => level_string.red(),
281 Level::Warn => level_string.yellow(),
282 Level::Info => level_string.cyan(),
283 Level::Debug => level_string.purple(),
284 Level::Trace => level_string.normal(),
285 }
286 .to_string()
287 } else {
288 level_string
289 }
290 }
291
292 fn print_data(&self, data: Vec<LoggingEvent>) {
293 if data.len() == 0 {
294 return;
295 }
296
297 if !(self.filter_fn)(&data[0]) {
298 return;
299 }
300
301 if data.len() == 1 && data[0].quiet && data[0].elapsed.unwrap_or(u128::MAX) == 0 {
302 return;
303 }
304
305 let terminal_width = termsize::get().unwrap_or(Size { rows: 0, cols: 0 }).cols as usize;
306 for record in data.iter().filter(|e| (self.filter_fn)(e)) {
307 let left = format!(
308 "{} {:indent$}{}",
309 self.get_level_string(record.level),
310 " ",
311 record.get_args(),
312 indent = record.indentation.checked_sub(1).unwrap_or_default() * 2,
313 );
314
315 let right = format!("[{}{}]", record.target, record.thread);
316
317 let width = String::from_utf8(strip(format!("{left}{right}").as_bytes()))
318 .unwrap_or_default()
319 .len();
320 let message = if terminal_width > 0 && width + 5 < terminal_width {
321 format!(
322 "{}{:padding$}{}",
323 left,
324 " ",
325 right,
326 padding = terminal_width - width
327 )
328 } else {
329 left
330 };
331
332 if let Some(sender) = &self.maybe_sender {
333 _ = sender.send(message.clone());
334 }
335
336 if self.use_stderr {
337 eprintln!("{}", message);
338 } else {
339 println!("{}", message);
340 }
341 }
342 }
343}
344
345impl Log for TreeLogger {
346 fn enabled(&self, metadata: &Metadata) -> bool {
347 metadata.level().to_level_filter() <= self.default_level
348 }
349
350 fn log(&self, record: &Record) {
351 if record
352 .key_values()
353 .get(constants::INCREMENT.into())
354 .is_some()
355 {
356 self.data.increment();
357 } else if record
358 .key_values()
359 .get(constants::DECREMENT.into())
360 .is_some()
361 {
362 self.data.decrement();
363 } else if record
364 .key_values()
365 .get(constants::SET_TIME.into())
366 .is_some()
367 {
368 if let Some(time_value) = record.key_values().get(constants::TIME.into()) {
369 if let Ok(time) = time_value.to_string().parse::<u128>() {
370 if let Some(id_value) = record.key_values().get(constants::ID.into()) {
371 if let Ok(id) = id_value.to_string().parse::<usize>() {
372 self.data.set_time(id, time);
373 }
374 }
375 }
376 }
377 } else {
378 if !self.enabled(record.metadata()) {
379 return;
380 }
381
382 self.data.push_record(record, self.threads_enabled);
383 }
384
385 if let Some(data) = self.data.get_data_to_log() {
386 self.print_data(data);
387 }
388 }
389
390 fn flush(&self) {}
391}
392
393