1use nu_ansi_term::Color;
2use std::{
3 fmt::{self, Write as _},
4 io,
5};
6use tracing_core::{
7 field::{Field, Visit},
8 span, Level,
9};
10
11pub(crate) const LINE_VERT: &str = "│";
12const LINE_HORIZ: &str = "─";
13pub(crate) const LINE_BRANCH: &str = "├";
14pub(crate) const LINE_CLOSE: &str = "┘";
15pub(crate) const LINE_CLOSE2: char = '┌';
16pub(crate) const LINE_OPEN: &str = "┐";
17pub(crate) const LINE_OPEN2: char = '└';
18
19#[derive(Debug, Copy, Clone)]
20pub(crate) enum SpanMode {
21 /// Executed on the parent before entering a child span
22 PreOpen,
23 Open {
24 verbose: bool,
25 },
26 Close {
27 verbose: bool,
28 },
29 /// A span has been entered but another *different* span has been entered in the meantime.
30 Retrace {
31 verbose: bool,
32 },
33 PostClose,
34 Event,
35}
36
37#[derive(Debug)]
38pub struct Config {
39 /// Whether to use colors.
40 pub ansi: bool,
41 /// Whether an ascii art tree is used or (if false) whether to just use whitespace indent
42 pub indent_lines: bool,
43 /// The amount of chars to indent.
44 pub indent_amount: usize,
45 /// Whether to show the module paths.
46 pub targets: bool,
47 /// Whether to show thread ids.
48 pub render_thread_ids: bool,
49 /// Whether to show thread names.
50 pub render_thread_names: bool,
51 /// Specifies after how many indentation levels we will wrap back around to zero
52 pub wraparound: usize,
53 /// Whether to print the current span before activating a new one
54 pub verbose_entry: bool,
55 /// Whether to print the current span before exiting it.
56 pub verbose_exit: bool,
57 /// Print the path leading up to a span if a different span was entered concurrently
58 pub span_retrace: bool,
59 /// Whether to print squiggly brackets (`{}`) around the list of fields in a span.
60 pub bracketed_fields: bool,
61 /// Defer printing a span until an event is generated inside of it
62 pub deferred_spans: bool,
63 /// Print a label of the span mode (open/close etc).
64 pub span_modes: bool,
65 /// Whether to print the time with higher precision.
66 pub higher_precision: bool,
67}
68
69impl Config {
70 pub fn with_ansi(self, ansi: bool) -> Self {
71 Self { ansi, ..self }
72 }
73
74 pub fn with_indent_lines(self, indent_lines: bool) -> Self {
75 Self {
76 indent_lines,
77 ..self
78 }
79 }
80
81 pub fn with_targets(self, targets: bool) -> Self {
82 Self { targets, ..self }
83 }
84
85 pub fn with_thread_ids(self, render_thread_ids: bool) -> Self {
86 Self {
87 render_thread_ids,
88 ..self
89 }
90 }
91
92 pub fn with_thread_names(self, render_thread_names: bool) -> Self {
93 Self {
94 render_thread_names,
95 ..self
96 }
97 }
98
99 pub fn with_wraparound(self, wraparound: usize) -> Self {
100 Self { wraparound, ..self }
101 }
102
103 pub fn with_verbose_entry(self, verbose_entry: bool) -> Self {
104 Self {
105 verbose_entry,
106 ..self
107 }
108 }
109
110 pub fn with_verbose_exit(self, verbose_exit: bool) -> Self {
111 Self {
112 verbose_exit,
113 ..self
114 }
115 }
116
117 pub fn with_span_retrace(self, enabled: bool) -> Self {
118 Self {
119 span_retrace: enabled,
120 ..self
121 }
122 }
123
124 pub fn with_deferred_spans(self, enable: bool) -> Self {
125 Self {
126 deferred_spans: enable,
127 ..self
128 }
129 }
130
131 pub fn with_span_modes(self, enable: bool) -> Self {
132 Self {
133 span_modes: enable,
134 ..self
135 }
136 }
137
138 pub fn with_bracketed_fields(self, bracketed_fields: bool) -> Self {
139 Self {
140 bracketed_fields,
141 ..self
142 }
143 }
144
145 pub fn with_higher_precision(self, higher_precision: bool) -> Self {
146 Self {
147 higher_precision,
148 ..self
149 }
150 }
151
152 pub(crate) fn prefix(&self) -> String {
153 let mut buf = String::new();
154 if self.render_thread_ids {
155 write!(buf, "{:?}", std::thread::current().id()).unwrap();
156 if buf.ends_with(')') {
157 buf.truncate(buf.len() - 1);
158 }
159 if buf.starts_with("ThreadId(") {
160 buf.drain(0.."ThreadId(".len());
161 }
162 }
163 if self.render_thread_names {
164 if let Some(name) = std::thread::current().name() {
165 if self.render_thread_ids {
166 buf.push(':');
167 }
168 buf.push_str(name);
169 }
170 }
171 buf
172 }
173}
174
175impl Default for Config {
176 fn default() -> Self {
177 Self {
178 ansi: true,
179 indent_lines: false,
180 indent_amount: 2,
181 targets: false,
182 render_thread_ids: false,
183 render_thread_names: false,
184 wraparound: usize::max_value(),
185 verbose_entry: false,
186 verbose_exit: false,
187 span_retrace: false,
188 bracketed_fields: false,
189 deferred_spans: false,
190 span_modes: false,
191 higher_precision: false,
192 }
193 }
194}
195
196#[derive(Debug)]
197pub struct Buffers {
198 pub current_buf: String,
199 pub indent_buf: String,
200
201 /// The last seen span of this layer
202 ///
203 /// This serves to serialize spans as two events can be generated in different spans
204 /// without the spans entering and exiting beforehand. This happens for multithreaded code
205 /// and instrumented futures
206 pub current_span: Option<span::Id>,
207}
208
209impl Buffers {
210 pub fn new() -> Self {
211 Self {
212 current_buf: String::new(),
213 indent_buf: String::new(),
214 current_span: None,
215 }
216 }
217
218 pub fn flush_current_buf(&mut self, mut writer: impl io::Write) {
219 write!(writer, "{}", &self.current_buf).unwrap();
220 self.current_buf.clear();
221 }
222
223 pub fn flush_indent_buf(&mut self) {
224 self.current_buf.push_str(&self.indent_buf);
225 self.indent_buf.clear();
226 }
227
228 pub(crate) fn indent_current(&mut self, indent: usize, config: &Config, style: SpanMode) {
229 let prefix = config.prefix();
230
231 // Render something when wraparound occurs so the user is aware of it
232 if config.indent_lines {
233 self.current_buf.push('\n');
234
235 match style {
236 SpanMode::Close { .. } | SpanMode::PostClose => {
237 if indent > 0 && (indent + 1) % config.wraparound == 0 {
238 self.indent_buf.push_str(&prefix);
239 for _ in 0..(indent % config.wraparound * config.indent_amount) {
240 self.indent_buf.push_str(LINE_HORIZ);
241 }
242 self.indent_buf.push_str(LINE_OPEN);
243 self.indent_buf.push('\n');
244 }
245 }
246 _ => {}
247 }
248 }
249
250 indent_block(
251 &self.current_buf,
252 &mut self.indent_buf,
253 indent % config.wraparound,
254 config.indent_amount,
255 config.indent_lines,
256 &prefix,
257 style,
258 );
259
260 self.current_buf.clear();
261 self.flush_indent_buf();
262
263 // Render something when wraparound occurs so the user is aware of it
264 if config.indent_lines {
265 match style {
266 SpanMode::PreOpen { .. } | SpanMode::Open { .. } => {
267 if indent > 0 && (indent + 1) % config.wraparound == 0 {
268 self.current_buf.push_str(&prefix);
269 for _ in 0..(indent % config.wraparound * config.indent_amount) {
270 self.current_buf.push_str(LINE_HORIZ);
271 }
272 self.current_buf.push_str(LINE_CLOSE);
273 self.current_buf.push('\n');
274 }
275 }
276 _ => {}
277 }
278 }
279 }
280}
281
282pub struct FmtEvent<'a> {
283 pub bufs: &'a mut Buffers,
284 pub comma: bool,
285}
286
287impl<'a> Visit for FmtEvent<'a> {
288 fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
289 let buf: &mut String = &mut self.bufs.current_buf;
290 let comma: &'static str = if self.comma { "," } else { "" };
291 match field.name() {
292 "message" => {
293 write!(buf, "{} {:?}", comma, value).unwrap();
294 self.comma = true;
295 }
296 // Skip fields that are actually log metadata that have already been handled
297 #[cfg(feature = "tracing-log")]
298 name: &'static str if name.starts_with("log.") => {}
299 name: &'static str => {
300 write!(buf, "{} {}={:?}", comma, name, value).unwrap();
301 self.comma = true;
302 }
303 }
304 }
305}
306
307pub struct ColorLevel<'a>(pub &'a Level);
308
309impl<'a> fmt::Display for ColorLevel<'a> {
310 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
311 matchAnsiGenericString<'_, str> *self.0 {
312 Level::TRACE => Color::Purple.bold().paint(input:"TRACE"),
313 Level::DEBUG => Color::Blue.bold().paint(input:"DEBUG"),
314 Level::INFO => Color::Green.bold().paint(input:" INFO"),
315 Level::WARN => Color::Rgb(252, 234, 160).bold().paint(input:" WARN"), // orange
316 Level::ERROR => Color::Red.bold().paint(input:"ERROR"),
317 }
318 .fmt(f)
319 }
320}
321
322pub(crate) fn write_span_mode(buf: &mut String, style: SpanMode) {
323 match style {
324 SpanMode::Open { verbose: true } => buf.push_str(string:"open(v)"),
325 SpanMode::Open { verbose: false } => buf.push_str(string:"open"),
326 SpanMode::Retrace { verbose: false } => buf.push_str(string:"retrace"),
327 SpanMode::Retrace { verbose: true } => buf.push_str(string:"retrace(v)"),
328 SpanMode::Close { verbose: true } => buf.push_str(string:"close(v)"),
329 SpanMode::Close { verbose: false } => buf.push_str(string:"close"),
330 SpanMode::PreOpen => buf.push_str(string:"pre_open"),
331 SpanMode::PostClose => buf.push_str(string:"post_close"),
332 SpanMode::Event => buf.push_str(string:"event"),
333 }
334
335 buf.push_str(string:": ")
336}
337
338fn indent_block_with_lines(
339 lines: &[&str],
340 buf: &mut String,
341 indent: usize,
342 // width of one level of indent
343 indent_amount: usize,
344 prefix: &str,
345 style: SpanMode,
346) {
347 let indent_spaces = indent * indent_amount;
348
349 if lines.is_empty() {
350 return;
351 } else if indent_spaces == 0 {
352 for line in lines {
353 buf.push_str(prefix);
354 // The first indent is special, we only need to print open/close and nothing else
355 if indent == 0 {
356 match style {
357 SpanMode::Open { .. } => buf.push_str(LINE_OPEN),
358 SpanMode::Retrace { .. } => buf.push_str(LINE_OPEN),
359 SpanMode::Close { .. } => buf.push_str(LINE_CLOSE),
360 SpanMode::PreOpen { .. } | SpanMode::PostClose => {}
361 SpanMode::Event => {}
362 }
363 }
364 buf.push_str(line);
365 buf.push('\n');
366 }
367 return;
368 }
369
370 let mut s = String::with_capacity(indent_spaces + prefix.len());
371 s.push_str(prefix);
372
373 for _ in 0..(indent_spaces - indent_amount) {
374 s.push(' ');
375 }
376
377 // draw branch
378 buf.push_str(&s);
379
380 match style {
381 SpanMode::PreOpen => {
382 buf.push(LINE_OPEN2);
383 for _ in 1..(indent_amount / 2) {
384 buf.push_str(LINE_HORIZ);
385 }
386 buf.push_str(LINE_OPEN);
387 }
388 SpanMode::Open { verbose: false } | SpanMode::Retrace { verbose: false } => {
389 buf.push(LINE_OPEN2);
390 for _ in 1..indent_amount {
391 buf.push_str(LINE_HORIZ);
392 }
393 buf.push_str(LINE_OPEN);
394 }
395 SpanMode::Open { verbose: true } | SpanMode::Retrace { verbose: true } => {
396 buf.push(' ');
397 for _ in 1..(indent_amount / 2) {
398 buf.push(' ');
399 }
400 // We don't have the space for fancy rendering at single space indent.
401 if indent_amount > 1 {
402 buf.push(LINE_OPEN2);
403 }
404 for _ in (indent_amount / 2)..(indent_amount - 1) {
405 buf.push_str(LINE_HORIZ);
406 }
407 // We don't have the space for fancy rendering at single space indent.
408 if indent_amount > 1 {
409 buf.push_str(LINE_OPEN);
410 } else {
411 buf.push(' ');
412 }
413 }
414 SpanMode::Close { verbose: false } => {
415 buf.push(LINE_CLOSE2);
416 for _ in 1..indent_amount {
417 buf.push_str(LINE_HORIZ);
418 }
419 buf.push_str(LINE_CLOSE);
420 }
421 SpanMode::Close { verbose: true } => {
422 buf.push(' ');
423 for _ in 1..(indent_amount / 2) {
424 buf.push(' ');
425 }
426 // We don't have the space for fancy rendering at single space indent.
427 if indent_amount > 1 {
428 buf.push(LINE_CLOSE2);
429 }
430 for _ in (indent_amount / 2)..(indent_amount - 1) {
431 buf.push_str(LINE_HORIZ);
432 }
433 // We don't have the space for fancy rendering at single space indent.
434 if indent_amount > 1 {
435 buf.push_str(LINE_CLOSE);
436 } else {
437 buf.push(' ');
438 }
439 }
440 SpanMode::PostClose => {
441 buf.push(LINE_CLOSE2);
442 for _ in 1..(indent_amount / 2) {
443 buf.push_str(LINE_HORIZ);
444 }
445 buf.push_str(LINE_CLOSE);
446 }
447 SpanMode::Event => {
448 buf.push_str(LINE_BRANCH);
449
450 // add `indent_amount - 1` horizontal lines before the span/event
451 for _ in 0..(indent_amount - 1) {
452 buf.push_str(LINE_HORIZ);
453 }
454 }
455 }
456 buf.push_str(lines[0]);
457 buf.push('\n');
458
459 // add the rest of the indentation, since we don't want to draw horizontal lines
460 // for subsequent lines
461 for i in 0..indent_amount {
462 if i % indent_amount == 0 {
463 s.push_str(LINE_VERT);
464 } else {
465 s.push(' ');
466 }
467 }
468
469 // add all of the actual content, with each line preceded by the indent string
470 for line in &lines[1..] {
471 buf.push_str(&s);
472 buf.push_str(line);
473 buf.push('\n');
474 }
475}
476
477fn indent_block(
478 block: &str,
479 buf: &mut String,
480 mut indent: usize,
481 indent_amount: usize,
482 indent_lines: bool,
483 prefix: &str,
484 style: SpanMode,
485) {
486 let lines: Vec<&str> = block.lines().collect();
487 let indent_spaces = indent * indent_amount;
488 buf.reserve(block.len() + (lines.len() * indent_spaces));
489
490 // The PreOpen and PostClose need to match up with the indent of the entered child span one more indent
491 // deep
492 match style {
493 SpanMode::PreOpen | SpanMode::PostClose => {
494 indent += 1;
495 }
496 _ => (),
497 }
498
499 if indent_lines {
500 indent_block_with_lines(&lines, buf, indent, indent_amount, prefix, style);
501 } else {
502 let indent_str = String::from(" ").repeat(indent_spaces);
503 for line in lines {
504 buf.push_str(prefix);
505 buf.push(' ');
506 buf.push_str(&indent_str);
507 buf.push_str(line);
508 buf.push('\n');
509 }
510 }
511}
512