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