tracing_fancytree/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use {
4	::core::fmt::{Debug, Write as _},
5	::nu_ansi_term::{Color, Style},
6	::std::{
7		io::{self, IsTerminal, Write},
8		sync::Mutex,
9	},
10	::tracing_core::{
11		field::Visit,
12		span::{Attributes, Id, Record},
13		Event,
14		Field,
15		Level,
16		Metadata,
17		Subscriber,
18	},
19	::tracing_subscriber::{layer::Context, registry::LookupSpan, Layer},
20};
21
22fn span_continue_marker(sym: bool) -> &'static str {
23	if sym {
24		"│ "
25	} else {
26		"| "
27	}
28}
29fn span_close_marker(sym: bool) -> &'static str {
30	if sym {
31		"╰·"
32	} else {
33		"\\-"
34	}
35}
36
37fn event_marker(level: Level, sym: bool) -> (Style, &'static str) {
38	(
39		match level {
40			Level::ERROR => Color::Red,
41			Level::WARN => Color::Yellow,
42			Level::INFO => Color::Blue,
43			Level::DEBUG => Color::Magenta,
44			Level::TRACE => Color::DarkGray,
45		}
46		.bold(),
47		if sym {
48			match level {
49				Level::ERROR => " ",
50				Level::WARN => " ",
51				Level::INFO => " ",
52				Level::DEBUG => " ",
53				Level::TRACE => " ",
54			}
55		} else {
56			match level {
57				Level::ERROR => "[err] ",
58				Level::WARN => "[wrn] ",
59				Level::INFO => "[inf] ",
60				Level::DEBUG => "[dbg] ",
61				Level::TRACE => "[trc] ",
62			}
63		},
64	)
65}
66
67/// print fancy tracing trees
68pub struct FancyTree<O: Write + 'static> {
69	inner: Mutex<Inner<O>>,
70}
71
72impl<O: Write + 'static> FancyTree<O> {
73	/// setup a fancy tree printer
74	///
75	/// - `out` - the stream to write to
76	/// - `ansi` - whether to print colors
77	/// - `sym` - whether to print unicode symbols
78	pub fn new(out: O, ansi: bool, sym: bool) -> Self {
79		Self {
80			inner: Mutex::new(Inner {
81				ansi,
82				sym,
83				out,
84				indent: Vec::new(),
85			}),
86		}
87	}
88}
89
90impl Default for FancyTree<io::Stdout> {
91	fn default() -> Self {
92		let out = io::stdout();
93		let ansi = out.is_terminal();
94		Self::new(out, ansi, ansi)
95	}
96}
97
98impl<S: Subscriber + for<'span> LookupSpan<'span, Data: Debug> + Debug, O: Write + 'static> Layer<S>
99	for FancyTree<O>
100{
101	fn on_new_span(&self, span: &Attributes<'_>, _: &Id, _: Context<'_, S>) {
102		let mut inner = self.inner.lock().unwrap();
103
104		inner.line_prefix();
105
106		let level = *span.metadata().level();
107		let (marker_style, marker) = event_marker(level, inner.sym);
108		if inner.ansi {
109			let style = Color::Green.normal();
110			write!(
111				inner.out,
112				"{}{marker}{}{} {}({}){}",
113				marker_style.prefix(),
114				span.metadata().name(),
115				marker_style.suffix(),
116				style.prefix(),
117				span.metadata().target(),
118				style.suffix(),
119			)
120			.unwrap();
121		} else {
122			write!(
123				inner.out,
124				"{marker}{} ({})",
125				span.metadata().name(),
126				span.metadata().target(),
127			)
128			.unwrap();
129		}
130
131		write_file(&mut *inner, span.metadata());
132
133		writeln!(inner.out).unwrap();
134
135		inner.indent.push(level);
136
137		span.values().record(&mut FieldDisplayVisitor {
138			inner: &mut inner,
139			indent: "",
140			ignore_message: false,
141		});
142	}
143
144	fn on_record(&self, _: &Id, values: &Record<'_>, _: Context<'_, S>) {
145		let mut inner = self.inner.lock().unwrap();
146		values.record(&mut FieldDisplayVisitor {
147			inner: &mut inner,
148			indent: "",
149			ignore_message: false,
150		});
151	}
152
153	fn on_event(&self, event: &Event<'_>, _: Context<'_, S>) {
154		let mut inner = self.inner.lock().unwrap();
155
156		let mut return_finder = FieldVisitor {
157			field: "return",
158			output: None,
159		};
160		event.record(&mut return_finder);
161		if let Some((_, false)) = return_finder.output {
162			event.record(&mut FieldDisplayVisitor {
163				inner: &mut inner,
164				indent: "",
165				ignore_message: false,
166			});
167			return;
168		}
169
170		let meta = event.metadata();
171		let (style, sym) = event_marker(*meta.level(), inner.sym);
172
173		let mut message_finder = FieldVisitor {
174			field: "message",
175			output: None,
176		};
177		event.record(&mut message_finder);
178		if let Some((message, _)) = message_finder.output {
179			inner.line_prefix();
180			if inner.ansi {
181				write!(inner.out, "{}{sym}", style.prefix()).unwrap();
182			} else {
183				write!(inner.out, "{sym}").unwrap();
184			}
185
186			if inner.ansi {
187				write!(inner.out, "{}{message}{}", style.prefix(), style.suffix()).unwrap();
188			} else {
189				write!(inner.out, "{message}").unwrap();
190			}
191		} else {
192			inner.line_prefix();
193			if inner.ansi {
194				write!(
195					inner.out,
196					"{}{sym}{}{}",
197					style.prefix(),
198					meta.name(),
199					style.suffix()
200				)
201				.unwrap();
202			} else {
203				write!(inner.out, "{sym}{}", meta.name()).unwrap();
204			}
205		}
206
207		write_file(&mut *inner, event.metadata());
208
209		writeln!(inner.out).unwrap();
210
211		event.record(&mut FieldDisplayVisitor {
212			inner: &mut inner,
213			indent: "  ",
214			ignore_message: true,
215		});
216	}
217
218	fn on_exit(&self, span: &Id, ctx: Context<'_, S>) {
219		let mut inner = self.inner.lock().unwrap();
220
221		let name = ctx.span(span).unwrap().name();
222		let level = inner.indent.pop().unwrap();
223
224		inner.line_prefix();
225
226		let close_sym = span_close_marker(inner.sym);
227		if inner.ansi {
228			let style = event_marker(level, inner.sym).0;
229			let name_style = Color::DarkGray.normal().bold();
230			writeln!(
231				inner.out,
232				"{}{close_sym}{}{name}{}",
233				style.prefix(),
234				style.infix(name_style),
235				name_style.suffix()
236			)
237			.unwrap();
238		} else {
239			writeln!(inner.out, "{close_sym} {name}").unwrap();
240		}
241	}
242}
243
244fn write_file<O: Write + 'static>(inner: &mut Inner<O>, metadata: &'static Metadata<'static>) {
245	if metadata.file().is_some() || metadata.line().is_some() {
246		if inner.ansi {
247			let style = Color::DarkGray.normal();
248			write!(
249				inner.out,
250				" {}{}:{}{}",
251				style.prefix(),
252				metadata.file().unwrap_or_default(),
253				metadata
254					.line()
255					.map_or_else(String::new, |line| line.to_string()),
256				style.suffix(),
257			)
258			.unwrap();
259		} else {
260			write!(
261				inner.out,
262				" {}:{}",
263				metadata.file().unwrap_or_default(),
264				metadata
265					.line()
266					.map_or_else(String::new, |line| line.to_string()),
267			)
268			.unwrap();
269		}
270	}
271}
272
273struct Inner<O: Write + 'static> {
274	ansi: bool,
275	sym: bool,
276	out: O,
277	indent: Vec<Level>,
278}
279
280impl<O: Write + 'static> Inner<O> {
281	fn line_prefix(&mut self) {
282		let continue_sym = span_continue_marker(self.sym);
283		if self.ansi {
284			let mut last_color = Style::new();
285			for level in &self.indent {
286				let style = event_marker(*level, self.sym).0;
287				write!(self.out, "{}{continue_sym}", last_color.infix(style)).unwrap();
288				last_color = style;
289			}
290			self.out
291				.write_fmt(format_args!("{}", last_color.suffix()))
292				.unwrap();
293		} else {
294			for _ in &self.indent {
295				write!(self.out, "{continue_sym}").unwrap();
296			}
297		}
298	}
299}
300
301struct FieldDisplayVisitor<'inner, O: Write + 'static> {
302	inner: &'inner mut Inner<O>,
303	indent: &'static str,
304	ignore_message: bool,
305}
306
307impl<O: Write + 'static> FieldDisplayVisitor<'_, O> {
308	fn field(&mut self, name: &str, style: Style, content: &str) {
309		if name == "message" && self.ignore_message {
310			return;
311		}
312
313		for (i, line) in content.lines().enumerate() {
314			self.inner.line_prefix();
315
316			write!(self.inner.out, "{}", self.indent).unwrap();
317
318			if i == 0 {
319				if self.inner.ansi {
320					let name_style = Color::LightGreen.normal().bold();
321					write!(
322						self.inner.out,
323						"{}{name}{}: ",
324						name_style.prefix(),
325						name_style.suffix()
326					)
327					.unwrap();
328				} else {
329					write!(self.inner.out, "{name}: ").unwrap();
330				}
331			} else {
332				write!(self.inner.out, "{}  ", " ".repeat(name.len())).unwrap();
333			}
334
335			if self.inner.ansi {
336				write!(self.inner.out, "{}{line}{}", style.prefix(), style.suffix()).unwrap();
337			} else {
338				write!(self.inner.out, "{line}").unwrap();
339			}
340
341			writeln!(self.inner.out).unwrap();
342		}
343	}
344}
345
346impl<O: Write + 'static> Visit for FieldDisplayVisitor<'_, O> {
347	fn record_f64(&mut self, field: &Field, value: f64) {
348		self.field(field.name(), Color::Yellow.normal(), &format!("{value}"));
349	}
350
351	fn record_i64(&mut self, field: &Field, value: i64) {
352		self.field(field.name(), Color::Yellow.normal(), &format!("{value}"));
353	}
354
355	fn record_u64(&mut self, field: &Field, value: u64) {
356		self.field(field.name(), Color::Yellow.normal(), &format!("{value}"));
357	}
358
359	fn record_i128(&mut self, field: &Field, value: i128) {
360		self.field(field.name(), Color::Yellow.normal(), &format!("{value}"));
361	}
362
363	fn record_u128(&mut self, field: &Field, value: u128) {
364		self.field(field.name(), Color::Yellow.normal(), &format!("{value}"));
365	}
366
367	fn record_bool(&mut self, field: &Field, value: bool) {
368		self.field(
369			field.name(),
370			Color::LightGreen.normal(),
371			&format!("{value}"),
372		);
373	}
374
375	fn record_str(&mut self, field: &Field, value: &str) {
376		self.field(field.name(), Color::Blue.normal(), value);
377	}
378
379	fn record_bytes(&mut self, field: &Field, value: &[u8]) {
380		let mut hex = String::with_capacity((value.len() * 3).saturating_sub(1));
381
382		for (i, byte) in value.iter().enumerate() {
383			if i != 0 {
384				hex.push(' ');
385			}
386			write!(hex, "{byte:x}").unwrap();
387		}
388
389		self.field(field.name(), Color::Purple.normal(), &hex);
390	}
391
392	fn record_error(&mut self, field: &Field, value: &(dyn ::std::error::Error + 'static)) {
393		self.field(field.name(), Color::Red.normal(), &format!("{value}"));
394	}
395
396	fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
397		self.field(field.name(), Color::White.normal(), &format!("{value:#?}"));
398	}
399}
400
401struct FieldVisitor {
402	field: &'static str,
403	output: Option<(String, bool)>,
404}
405
406impl Visit for FieldVisitor {
407	fn record_str(&mut self, field: &Field, value: &str) {
408		if let Some((_, any_others)) = &mut self.output {
409			*any_others = true;
410		}
411
412		if field.name() == self.field {
413			self.output = Some((value.to_string(), false));
414		}
415	}
416
417	fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
418		if let Some((_, any_others)) = &mut self.output {
419			*any_others = true;
420		}
421
422		if field.name() == self.field {
423			self.output = Some((format!("{value:#?}"), false));
424		}
425	}
426}