1 | use nu_ansi_term::Color; |
2 | use std::{ |
3 | fmt::{self, Write as _}, |
4 | io, |
5 | }; |
6 | use tracing_core::{ |
7 | field::{Field, Visit}, |
8 | Level, |
9 | }; |
10 | |
11 | pub(crate) const LINE_VERT: &str = "│" ; |
12 | const LINE_HORIZ: &str = "─" ; |
13 | pub(crate) const LINE_BRANCH: &str = "├" ; |
14 | pub(crate) const LINE_CLOSE: &str = "┘" ; |
15 | pub(crate) const LINE_OPEN: &str = "┐" ; |
16 | |
17 | #[derive (Copy, Clone)] |
18 | pub(crate) enum SpanMode { |
19 | PreOpen, |
20 | Open { verbose: bool }, |
21 | Close { verbose: bool }, |
22 | PostClose, |
23 | Event, |
24 | } |
25 | |
26 | #[derive (Debug)] |
27 | pub 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 | |
50 | impl 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 | |
128 | impl 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)] |
146 | pub struct Buffers { |
147 | pub current_buf: String, |
148 | pub indent_buf: String, |
149 | } |
150 | |
151 | impl 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 | |
222 | pub struct FmtEvent<'a> { |
223 | pub bufs: &'a mut Buffers, |
224 | pub comma: bool, |
225 | } |
226 | |
227 | impl<'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 | |
247 | pub struct ColorLevel<'a>(pub &'a Level); |
248 | |
249 | impl<'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 | |
262 | fn 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 | |
403 | fn 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 | |