]>
Commit | Line | Data |
---|---|---|
5869c6ff XL |
1 | use super::*; |
2 | use crate::{ | |
3 | field::{VisitFmt, VisitOutput}, | |
4 | fmt::fmt_layer::{FmtContext, FormattedFields}, | |
5 | registry::LookupSpan, | |
6 | }; | |
7 | ||
8 | use std::{ | |
9 | fmt::{self, Write}, | |
10 | iter, | |
11 | }; | |
12 | use tracing_core::{ | |
13 | field::{self, Field}, | |
14 | Event, Level, Subscriber, | |
15 | }; | |
16 | ||
17 | #[cfg(feature = "tracing-log")] | |
18 | use tracing_log::NormalizeEvent; | |
19 | ||
20 | use ansi_term::{Colour, Style}; | |
21 | ||
22 | /// An excessively pretty, human-readable event formatter. | |
23 | #[derive(Debug, Clone, Eq, PartialEq)] | |
24 | pub struct Pretty { | |
25 | display_location: bool, | |
26 | } | |
27 | ||
28 | /// The [visitor] produced by [`Pretty`]'s [`MakeVisitor`] implementation. | |
29 | /// | |
30 | /// [visitor]: ../../field/trait.Visit.html | |
31 | /// [`DefaultFields`]: struct.DefaultFields.html | |
32 | /// [`MakeVisitor`]: ../../field/trait.MakeVisitor.html | |
33 | pub struct PrettyVisitor<'a> { | |
34 | writer: &'a mut dyn Write, | |
35 | is_empty: bool, | |
36 | style: Style, | |
37 | result: fmt::Result, | |
38 | } | |
39 | ||
40 | // === impl Pretty === | |
41 | ||
42 | impl Default for Pretty { | |
43 | fn default() -> Self { | |
44 | Self { | |
45 | display_location: true, | |
46 | } | |
47 | } | |
48 | } | |
49 | ||
50 | impl Pretty { | |
51 | fn style_for(level: &Level) -> Style { | |
52 | match *level { | |
53 | Level::TRACE => Style::new().fg(Colour::Purple), | |
54 | Level::DEBUG => Style::new().fg(Colour::Blue), | |
55 | Level::INFO => Style::new().fg(Colour::Green), | |
56 | Level::WARN => Style::new().fg(Colour::Yellow), | |
57 | Level::ERROR => Style::new().fg(Colour::Red), | |
58 | } | |
59 | } | |
60 | ||
61 | /// Sets whether the event's source code location is displayed. | |
62 | /// | |
63 | /// This defaults to `true`. | |
64 | pub fn with_source_location(self, display_location: bool) -> Self { | |
65 | Self { | |
66 | display_location, | |
67 | ..self | |
68 | } | |
69 | } | |
70 | } | |
71 | ||
72 | impl<T> Format<Pretty, T> { | |
73 | /// Sets whether or not the source code location from which an event | |
74 | /// originated is displayed. | |
75 | /// | |
76 | /// This defaults to `true`. | |
77 | pub fn with_source_location(mut self, display_location: bool) -> Self { | |
78 | self.format = self.format.with_source_location(display_location); | |
79 | self | |
80 | } | |
81 | } | |
82 | ||
83 | impl<C, N, T> FormatEvent<C, N> for Format<Pretty, T> | |
84 | where | |
85 | C: Subscriber + for<'a> LookupSpan<'a>, | |
86 | N: for<'a> FormatFields<'a> + 'static, | |
87 | T: FormatTime, | |
88 | { | |
89 | fn format_event( | |
90 | &self, | |
91 | ctx: &FmtContext<'_, C, N>, | |
92 | writer: &mut dyn fmt::Write, | |
93 | event: &Event<'_>, | |
94 | ) -> fmt::Result { | |
95 | #[cfg(feature = "tracing-log")] | |
96 | let normalized_meta = event.normalized_metadata(); | |
97 | #[cfg(feature = "tracing-log")] | |
98 | let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata()); | |
99 | #[cfg(not(feature = "tracing-log"))] | |
100 | let meta = event.metadata(); | |
101 | write!(writer, " ")?; | |
102 | time::write(&self.timer, writer, true)?; | |
103 | ||
104 | let style = if self.display_level { | |
105 | Pretty::style_for(meta.level()) | |
106 | } else { | |
107 | Style::new() | |
108 | }; | |
109 | ||
110 | if self.display_target { | |
111 | let bold = style.bold(); | |
112 | write!( | |
113 | writer, | |
114 | "{}{}{}: ", | |
115 | bold.prefix(), | |
116 | meta.target(), | |
117 | bold.infix(style) | |
118 | )?; | |
119 | } | |
120 | let mut v = PrettyVisitor::new(writer, true).with_style(style); | |
121 | event.record(&mut v); | |
122 | v.finish()?; | |
123 | writer.write_char('\n')?; | |
124 | ||
125 | let dimmed = Style::new().dimmed().italic(); | |
126 | let thread = self.display_thread_name || self.display_thread_id; | |
127 | if let (true, Some(file), Some(line)) = | |
128 | (self.format.display_location, meta.file(), meta.line()) | |
129 | { | |
130 | write!( | |
131 | writer, | |
132 | " {} {}:{}{}", | |
133 | dimmed.paint("at"), | |
134 | file, | |
135 | line, | |
136 | dimmed.paint(if thread { " " } else { "\n" }) | |
137 | )?; | |
138 | } else if thread { | |
139 | write!(writer, " ")?; | |
140 | } | |
141 | ||
142 | if thread { | |
143 | write!(writer, "{} ", dimmed.paint("on"))?; | |
144 | let thread = std::thread::current(); | |
145 | if self.display_thread_name { | |
146 | if let Some(name) = thread.name() { | |
147 | write!(writer, "{}", name)?; | |
148 | if self.display_thread_id { | |
149 | write!(writer, " ({:?})", thread.id())?; | |
150 | } | |
151 | } else if !self.display_thread_id { | |
152 | write!(writer, " {:?}", thread.id())?; | |
153 | } | |
154 | } else if self.display_thread_id { | |
155 | write!(writer, " {:?}", thread.id())?; | |
156 | } | |
157 | writer.write_char('\n')?; | |
158 | } | |
159 | ||
160 | let bold = Style::new().bold(); | |
161 | let span = event | |
162 | .parent() | |
163 | .and_then(|id| ctx.span(&id)) | |
164 | .or_else(|| ctx.lookup_current()); | |
165 | ||
166 | let scope = span.into_iter().flat_map(|span| { | |
167 | let parents = span.parents(); | |
168 | iter::once(span).chain(parents) | |
169 | }); | |
170 | ||
171 | for span in scope { | |
172 | let meta = span.metadata(); | |
173 | if self.display_target { | |
174 | write!( | |
175 | writer, | |
176 | " {} {}::{}", | |
177 | dimmed.paint("in"), | |
178 | meta.target(), | |
179 | bold.paint(meta.name()), | |
180 | )?; | |
181 | } else { | |
182 | write!( | |
183 | writer, | |
184 | " {} {}", | |
185 | dimmed.paint("in"), | |
186 | bold.paint(meta.name()), | |
187 | )?; | |
188 | } | |
189 | ||
190 | let ext = span.extensions(); | |
191 | let fields = &ext | |
192 | .get::<FormattedFields<N>>() | |
193 | .expect("Unable to find FormattedFields in extensions; this is a bug"); | |
194 | if !fields.is_empty() { | |
195 | write!(writer, " {} {}", dimmed.paint("with"), fields)?; | |
196 | } | |
197 | writer.write_char('\n')?; | |
198 | } | |
199 | ||
200 | writer.write_char('\n') | |
201 | } | |
202 | } | |
203 | ||
204 | impl<'writer> FormatFields<'writer> for Pretty { | |
205 | fn format_fields<R: RecordFields>( | |
206 | &self, | |
207 | writer: &'writer mut dyn fmt::Write, | |
208 | fields: R, | |
209 | ) -> fmt::Result { | |
210 | let mut v = PrettyVisitor::new(writer, true); | |
211 | fields.record(&mut v); | |
212 | v.finish() | |
213 | } | |
214 | ||
215 | fn add_fields(&self, current: &'writer mut String, fields: &span::Record<'_>) -> fmt::Result { | |
216 | let empty = current.is_empty(); | |
217 | let mut v = PrettyVisitor::new(current, empty); | |
218 | fields.record(&mut v); | |
219 | v.finish() | |
220 | } | |
221 | } | |
222 | ||
223 | // === impl PrettyVisitor === | |
224 | ||
225 | impl<'a> PrettyVisitor<'a> { | |
226 | /// Returns a new default visitor that formats to the provided `writer`. | |
227 | /// | |
228 | /// # Arguments | |
229 | /// - `writer`: the writer to format to. | |
230 | /// - `is_empty`: whether or not any fields have been previously written to | |
231 | /// that writer. | |
232 | pub fn new(writer: &'a mut dyn Write, is_empty: bool) -> Self { | |
233 | Self { | |
234 | writer, | |
235 | is_empty, | |
236 | style: Style::default(), | |
237 | result: Ok(()), | |
238 | } | |
239 | } | |
240 | ||
241 | pub(crate) fn with_style(self, style: Style) -> Self { | |
242 | Self { style, ..self } | |
243 | } | |
244 | ||
245 | fn maybe_pad(&mut self) { | |
246 | if self.is_empty { | |
247 | self.is_empty = false; | |
248 | } else { | |
249 | self.result = write!(self.writer, ", "); | |
250 | } | |
251 | } | |
252 | } | |
253 | ||
254 | impl<'a> field::Visit for PrettyVisitor<'a> { | |
255 | fn record_str(&mut self, field: &Field, value: &str) { | |
256 | if self.result.is_err() { | |
257 | return; | |
258 | } | |
259 | ||
260 | if field.name() == "message" { | |
261 | self.record_debug(field, &format_args!("{}", value)) | |
262 | } else { | |
263 | self.record_debug(field, &value) | |
264 | } | |
265 | } | |
266 | ||
267 | fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) { | |
268 | if let Some(source) = value.source() { | |
269 | let bold = self.style.bold(); | |
270 | self.record_debug( | |
271 | field, | |
272 | &format_args!( | |
273 | "{}, {}{}.source{}: {}", | |
274 | value, | |
275 | bold.prefix(), | |
276 | field, | |
277 | bold.infix(self.style), | |
278 | source, | |
279 | ), | |
280 | ) | |
281 | } else { | |
282 | self.record_debug(field, &format_args!("{}", value)) | |
283 | } | |
284 | } | |
285 | ||
286 | fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { | |
287 | if self.result.is_err() { | |
288 | return; | |
289 | } | |
290 | let bold = self.style.bold(); | |
291 | self.maybe_pad(); | |
292 | self.result = match field.name() { | |
293 | "message" => write!(self.writer, "{}{:?}", self.style.prefix(), value,), | |
294 | // Skip fields that are actually log metadata that have already been handled | |
295 | #[cfg(feature = "tracing-log")] | |
296 | name if name.starts_with("log.") => Ok(()), | |
297 | name if name.starts_with("r#") => write!( | |
298 | self.writer, | |
299 | "{}{}{}: {:?}", | |
300 | bold.prefix(), | |
301 | &name[2..], | |
302 | bold.infix(self.style), | |
303 | value | |
304 | ), | |
305 | name => write!( | |
306 | self.writer, | |
307 | "{}{}{}: {:?}", | |
308 | bold.prefix(), | |
309 | name, | |
310 | bold.infix(self.style), | |
311 | value | |
312 | ), | |
313 | }; | |
314 | } | |
315 | } | |
316 | ||
317 | impl<'a> VisitOutput<fmt::Result> for PrettyVisitor<'a> { | |
318 | fn finish(self) -> fmt::Result { | |
319 | write!(self.writer, "{}", self.style.suffix())?; | |
320 | self.result | |
321 | } | |
322 | } | |
323 | ||
324 | impl<'a> VisitFmt for PrettyVisitor<'a> { | |
325 | fn writer(&mut self) -> &mut dyn fmt::Write { | |
326 | self.writer | |
327 | } | |
328 | } | |
329 | ||
330 | impl<'a> fmt::Debug for PrettyVisitor<'a> { | |
331 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
332 | f.debug_struct("PrettyVisitor") | |
333 | .field("writer", &format_args!("<dyn fmt::Write>")) | |
334 | .field("is_empty", &self.is_empty) | |
335 | .field("result", &self.result) | |
336 | .field("style", &self.style) | |
337 | .finish() | |
338 | } | |
339 | } |