1use nu_ansi_term::Color;
2use std::{
3 fmt::{self, Write as _},
4 io,
5};
6use tracing_core::{
7 field::{Field, Visit},
8 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_OPEN: &str = "┐";
16
17#[derive(Copy, Clone)]
18pub(crate) enum SpanMode {
19 PreOpen,
20 Open { verbose: bool },
21 Close { verbose: bool },
22 PostClose,
23 Event,
24}
25
26#[derive(Debug)]
27pub struct Config {
28 /// Whether to use colors.
29 pub ansi: bool,
30 /// Whether an ascii art tree is used or (if false) whether to just use whitespace indent
31 pub indent_lines: bool,
32 /// The amount of chars to indent.
33 pub indent_amount: usize,
34 /// Whether to show the module paths.
35 pub targets: bool,
36 /// Whether to show thread ids.
37 pub render_thread_ids: bool,
38 /// Whether to show thread names.
39 pub render_thread_names: bool,
40 /// Specifies after how many indentation levels we will wrap back around to zero
41 pub wraparound: usize,
42 /// Whether to print the current span before activating a new one
43 pub verbose_entry: bool,
44 /// Whether to print the current span before exiting it.
45 pub verbose_exit: bool,
46 /// Whether to print squiggly brackets (`{}`) around the list of fields in a span.
47 pub bracketed_fields: bool,
48}
49
50impl Config {
51 pub fn with_ansi(self, ansi: bool) -> Self {
52 Self { ansi, ..self }
53 }
54
55 pub fn with_indent_lines(self, indent_lines: bool) -> Self {
56 Self {
57 indent_lines,
58 ..self
59 }
60 }
61
62 pub fn with_targets(self, targets: bool) -> Self {
63 Self { targets, ..self }
64 }
65
66 pub fn with_thread_ids(self, render_thread_ids: bool) -> Self {
67 Self {
68 render_thread_ids,
69 ..self
70 }
71 }
72
73 pub fn with_thread_names(self, render_thread_names: bool) -> Self {
74 Self {
75 render_thread_names,
76 ..self
77 }
78 }
79
80 pub fn with_wraparound(self, wraparound: usize) -> Self {
81 Self { wraparound, ..self }
82 }
83
84 pub fn with_verbose_entry(self, verbose_entry: bool) -> Self {
85 Self {
86 verbose_entry,
87 ..self
88 }
89 }
90
91 pub fn with_verbose_exit(self, verbose_exit: bool) -> Self {
92 Self {
93 verbose_exit,
94 ..self
95 }
96 }
97
98 pub fn with_bracketed_fields(self, bracketed_fields: bool) -> Self {
99 Self {
100 bracketed_fields,
101 ..self
102 }
103 }
104
105 pub(crate) fn prefix(&self) -> String {
106 let mut buf = String::new();
107 if self.render_thread_ids {
108 write!(buf, "{:?}", std::thread::current().id()).unwrap();
109 if buf.ends_with(')') {
110 buf.truncate(buf.len() - 1);
111 }
112 if buf.starts_with("ThreadId(") {
113 buf.drain(0.."ThreadId(".len());
114 }
115 }
116 if self.render_thread_names {
117 if let Some(name) = std::thread::current().name() {
118 if self.render_thread_ids {
119 buf.push(':');
120 }
121 buf.push_str(name);
122 }
123 }
124 buf
125 }
126}
127
128impl Default for Config {
129 fn default() -> Self {
130 Self {
131 ansi: true,
132 indent_lines: false,
133 indent_amount: 2,
134 targets: false,
135 render_thread_ids: false,
136 render_thread_names: false,
137 wraparound: usize::max_value(),
138 verbose_entry: false,
139 verbose_exit: false,
140 bracketed_fields: false,
141 }
142 }
143}
144
145#[derive(Debug)]
146pub struct Buffers {
147 pub current_buf: String,
148 pub indent_buf: String,
149}
150
151impl Buffers {
152 pub fn new() -> Self {
153 Self {
154 current_buf: String::new(),
155 indent_buf: String::new(),
156 }
157 }
158
159 pub fn flush_current_buf(&mut self, mut writer: impl io::Write) {
160 write!(writer, "{}", &self.current_buf).unwrap();
161 self.current_buf.clear();
162 }
163
164 pub fn flush_indent_buf(&mut self) {
165 self.current_buf.push_str(&self.indent_buf);
166 self.indent_buf.clear();
167 }
168
169 pub(crate) fn indent_current(&mut self, indent: usize, config: &Config, style: SpanMode) {
170 let prefix = config.prefix();
171
172 // Render something when wraparound occurs so the user is aware of it
173 if config.indent_lines {
174 self.current_buf.push('\n');
175
176 match style {
177 SpanMode::Close { .. } | SpanMode::PostClose => {
178 if indent > 0 && (indent + 1) % config.wraparound == 0 {
179 self.indent_buf.push_str(&prefix);
180 for _ in 0..(indent % config.wraparound * config.indent_amount) {
181 self.indent_buf.push_str(LINE_HORIZ);
182 }
183 self.indent_buf.push_str(LINE_OPEN);
184 self.indent_buf.push('\n');
185 }
186 }
187 _ => {}
188 }
189 }
190
191 indent_block(
192 &mut self.current_buf,
193 &mut self.indent_buf,
194 indent % config.wraparound,
195 config.indent_amount,
196 config.indent_lines,
197 &prefix,
198 style,
199 );
200 self.current_buf.clear();
201 self.flush_indent_buf();
202
203 // Render something when wraparound occurs so the user is aware of it
204 if config.indent_lines {
205 match style {
206 SpanMode::PreOpen | SpanMode::Open { .. } => {
207 if indent > 0 && (indent + 1) % config.wraparound == 0 {
208 self.current_buf.push_str(&prefix);
209 for _ in 0..(indent % config.wraparound * config.indent_amount) {
210 self.current_buf.push_str(LINE_HORIZ);
211 }
212 self.current_buf.push_str(LINE_CLOSE);
213 self.current_buf.push('\n');
214 }
215 }
216 _ => {}
217 }
218 }
219 }
220}
221
222pub struct FmtEvent<'a> {
223 pub bufs: &'a mut Buffers,
224 pub comma: bool,
225}
226
227impl<'a> Visit for FmtEvent<'a> {
228 fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
229 let buf: &mut String = &mut self.bufs.current_buf;
230 let comma: &str = if self.comma { "," } else { "" };
231 match field.name() {
232 "message" => {
233 write!(buf, "{} {:?}", comma, value).unwrap();
234 self.comma = true;
235 }
236 // Skip fields that are actually log metadata that have already been handled
237 #[cfg(feature = "tracing-log")]
238 name: &str if name.starts_with("log.") => {}
239 name: &str => {
240 write!(buf, "{} {}={:?}", comma, name, value).unwrap();
241 self.comma = true;
242 }
243 }
244 }
245}
246
247pub struct ColorLevel<'a>(pub &'a Level);
248
249impl<'a> fmt::Display for ColorLevel<'a> {
250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251 matchAnsiGenericString<'_, str> *self.0 {
252 Level::TRACE => Color::Purple.bold().paint(input:"TRACE"),
253 Level::DEBUG => Color::Blue.bold().paint(input:"DEBUG"),
254 Level::INFO => Color::Green.bold().paint(input:" INFO"),
255 Level::WARN => Color::Rgb(252, 234, 160).bold().paint(input:" WARN"), // orange
256 Level::ERROR => Color::Red.bold().paint(input:"ERROR"),
257 }
258 .fmt(f)
259 }
260}
261
262fn indent_block_with_lines(
263 lines: &[&str],
264 buf: &mut String,
265 indent: usize,
266 indent_amount: usize,
267 prefix: &str,
268 style: SpanMode,
269) {
270 let indent_spaces = indent * indent_amount;
271 if lines.is_empty() {
272 return;
273 } else if indent_spaces == 0 {
274 for line in lines {
275 buf.push_str(prefix);
276 // The first indent is special, we only need to print open/close and nothing else
277 if indent == 0 {
278 match style {
279 SpanMode::Open { .. } => buf.push_str(LINE_OPEN),
280 SpanMode::Close { .. } => buf.push_str(LINE_CLOSE),
281 SpanMode::PreOpen | SpanMode::PostClose => {}
282 SpanMode::Event => {}
283 }
284 }
285 buf.push_str(line);
286 buf.push('\n');
287 }
288 return;
289 }
290 let mut s = String::with_capacity(indent_spaces + prefix.len());
291 s.push_str(prefix);
292
293 // instead of using all spaces to indent, draw a vertical line at every indent level
294 // up until the last indent
295 for i in 0..(indent_spaces - indent_amount) {
296 if i % indent_amount == 0 {
297 s.push_str(LINE_VERT);
298 } else {
299 s.push(' ');
300 }
301 }
302
303 // draw branch
304 buf.push_str(&s);
305
306 match style {
307 SpanMode::PreOpen => {
308 buf.push_str(LINE_BRANCH);
309 for _ in 1..(indent_amount / 2) {
310 buf.push_str(LINE_HORIZ);
311 }
312 buf.push_str(LINE_OPEN);
313 }
314 SpanMode::Open { verbose: false } => {
315 buf.push_str(LINE_BRANCH);
316 for _ in 1..indent_amount {
317 buf.push_str(LINE_HORIZ);
318 }
319 buf.push_str(LINE_OPEN);
320 }
321 SpanMode::Open { verbose: true } => {
322 buf.push_str(LINE_VERT);
323 for _ in 1..(indent_amount / 2) {
324 buf.push(' ');
325 }
326 // We don't have the space for fancy rendering at single space indent.
327 if indent_amount > 1 {
328 buf.push('└');
329 }
330 for _ in (indent_amount / 2)..(indent_amount - 1) {
331 buf.push_str(LINE_HORIZ);
332 }
333 // We don't have the space for fancy rendering at single space indent.
334 if indent_amount > 1 {
335 buf.push_str(LINE_OPEN);
336 } else {
337 buf.push_str(LINE_VERT);
338 }
339 }
340 SpanMode::Close { verbose: false } => {
341 buf.push_str(LINE_BRANCH);
342 for _ in 1..indent_amount {
343 buf.push_str(LINE_HORIZ);
344 }
345 buf.push_str(LINE_CLOSE);
346 }
347 SpanMode::Close { verbose: true } => {
348 buf.push_str(LINE_VERT);
349 for _ in 1..(indent_amount / 2) {
350 buf.push(' ');
351 }
352 // We don't have the space for fancy rendering at single space indent.
353 if indent_amount > 1 {
354 buf.push('┌');
355 }
356 for _ in (indent_amount / 2)..(indent_amount - 1) {
357 buf.push_str(LINE_HORIZ);
358 }
359 // We don't have the space for fancy rendering at single space indent.
360 if indent_amount > 1 {
361 buf.push_str(LINE_CLOSE);
362 } else {
363 buf.push_str(LINE_VERT);
364 }
365 }
366 SpanMode::PostClose => {
367 buf.push_str(LINE_BRANCH);
368 for _ in 1..(indent_amount / 2) {
369 buf.push_str(LINE_HORIZ);
370 }
371 buf.push_str(LINE_CLOSE);
372 }
373 SpanMode::Event => {
374 buf.push_str(LINE_BRANCH);
375
376 // add `indent_amount - 1` horizontal lines before the span/event
377 for _ in 0..(indent_amount - 1) {
378 buf.push_str(LINE_HORIZ);
379 }
380 }
381 }
382 buf.push_str(lines[0]);
383 buf.push('\n');
384
385 // add the rest of the indentation, since we don't want to draw horizontal lines
386 // for subsequent lines
387 for i in 0..indent_amount {
388 if i % indent_amount == 0 {
389 s.push_str(LINE_VERT);
390 } else {
391 s.push(' ');
392 }
393 }
394
395 // add all of the actual content, with each line preceded by the indent string
396 for line in &lines[1..] {
397 buf.push_str(&s);
398 buf.push_str(line);
399 buf.push('\n');
400 }
401}
402
403fn indent_block(
404 block: &mut String,
405 buf: &mut String,
406 indent: usize,
407 indent_amount: usize,
408 indent_lines: bool,
409 prefix: &str,
410 style: SpanMode,
411) {
412 let lines: Vec<&str> = block.lines().collect();
413 let indent_spaces: usize = indent * indent_amount;
414 buf.reserve(additional:block.len() + (lines.len() * indent_spaces));
415 if indent_lines {
416 indent_block_with_lines(&lines, buf, indent, indent_amount, prefix, style);
417 } else {
418 let indent_str: String = String::from(" ").repeat(indent_spaces);
419 for line: &str in lines {
420 buf.push_str(string:prefix);
421 buf.push(ch:' ');
422 buf.push_str(&indent_str);
423 buf.push_str(string:line);
424 buf.push(ch:'\n');
425 }
426 }
427}
428