]>
Commit | Line | Data |
---|---|---|
3c0e092e XL |
1 | //! Formatting for log records. |
2 | //! | |
3 | //! This module contains a [`Formatter`] that can be used to format log records | |
4 | //! into without needing temporary allocations. Usually you won't need to worry | |
5 | //! about the contents of this module and can use the `Formatter` like an ordinary | |
6 | //! [`Write`]. | |
7 | //! | |
8 | //! # Formatting log records | |
9 | //! | |
10 | //! The format used to print log records can be customised using the [`Builder::format`] | |
11 | //! method. | |
12 | //! Custom formats can apply different color and weight to printed values using | |
13 | //! [`Style`] builders. | |
14 | //! | |
15 | //! ``` | |
16 | //! use std::io::Write; | |
17 | //! | |
18 | //! let mut builder = env_logger::Builder::new(); | |
19 | //! | |
20 | //! builder.format(|buf, record| { | |
21 | //! writeln!(buf, "{}: {}", | |
22 | //! record.level(), | |
23 | //! record.args()) | |
24 | //! }); | |
25 | //! ``` | |
26 | //! | |
27 | //! [`Formatter`]: struct.Formatter.html | |
28 | //! [`Style`]: struct.Style.html | |
29 | //! [`Builder::format`]: ../struct.Builder.html#method.format | |
30 | //! [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html | |
31 | ||
32 | use std::cell::RefCell; | |
33 | use std::fmt::Display; | |
34 | use std::io::prelude::*; | |
35 | use std::rc::Rc; | |
36 | use std::{fmt, io, mem}; | |
37 | ||
38 | use log::Record; | |
39 | ||
40 | mod humantime; | |
41 | pub(crate) mod writer; | |
42 | ||
43 | pub use self::humantime::glob::*; | |
44 | pub use self::writer::glob::*; | |
45 | ||
46 | use self::writer::{Buffer, Writer}; | |
47 | ||
48 | pub(crate) mod glob { | |
49 | pub use super::{Target, TimestampPrecision, WriteStyle}; | |
50 | } | |
51 | ||
52 | /// Formatting precision of timestamps. | |
53 | /// | |
54 | /// Seconds give precision of full seconds, milliseconds give thousands of a | |
55 | /// second (3 decimal digits), microseconds are millionth of a second (6 decimal | |
56 | /// digits) and nanoseconds are billionth of a second (9 decimal digits). | |
57 | #[derive(Copy, Clone, Debug)] | |
58 | pub enum TimestampPrecision { | |
59 | /// Full second precision (0 decimal digits) | |
60 | Seconds, | |
61 | /// Millisecond precision (3 decimal digits) | |
62 | Millis, | |
63 | /// Microsecond precision (6 decimal digits) | |
64 | Micros, | |
65 | /// Nanosecond precision (9 decimal digits) | |
66 | Nanos, | |
67 | } | |
68 | ||
69 | /// The default timestamp precision is seconds. | |
70 | impl Default for TimestampPrecision { | |
71 | fn default() -> Self { | |
72 | TimestampPrecision::Seconds | |
73 | } | |
74 | } | |
75 | ||
76 | /// A formatter to write logs into. | |
77 | /// | |
78 | /// `Formatter` implements the standard [`Write`] trait for writing log records. | |
79 | /// It also supports terminal colors, through the [`style`] method. | |
80 | /// | |
81 | /// # Examples | |
82 | /// | |
83 | /// Use the [`writeln`] macro to format a log record. | |
84 | /// An instance of a `Formatter` is passed to an `env_logger` format as `buf`: | |
85 | /// | |
86 | /// ``` | |
87 | /// use std::io::Write; | |
88 | /// | |
89 | /// let mut builder = env_logger::Builder::new(); | |
90 | /// | |
91 | /// builder.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args())); | |
92 | /// ``` | |
93 | /// | |
94 | /// [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html | |
95 | /// [`writeln`]: https://doc.rust-lang.org/stable/std/macro.writeln.html | |
96 | /// [`style`]: #method.style | |
97 | pub struct Formatter { | |
98 | buf: Rc<RefCell<Buffer>>, | |
99 | write_style: WriteStyle, | |
100 | } | |
101 | ||
102 | impl Formatter { | |
103 | pub(crate) fn new(writer: &Writer) -> Self { | |
104 | Formatter { | |
105 | buf: Rc::new(RefCell::new(writer.buffer())), | |
106 | write_style: writer.write_style(), | |
107 | } | |
108 | } | |
109 | ||
110 | pub(crate) fn write_style(&self) -> WriteStyle { | |
111 | self.write_style | |
112 | } | |
113 | ||
114 | pub(crate) fn print(&self, writer: &Writer) -> io::Result<()> { | |
115 | writer.print(&self.buf.borrow()) | |
116 | } | |
117 | ||
118 | pub(crate) fn clear(&mut self) { | |
119 | self.buf.borrow_mut().clear() | |
120 | } | |
121 | } | |
122 | ||
123 | impl Write for Formatter { | |
124 | fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | |
125 | self.buf.borrow_mut().write(buf) | |
126 | } | |
127 | ||
128 | fn flush(&mut self) -> io::Result<()> { | |
129 | self.buf.borrow_mut().flush() | |
130 | } | |
131 | } | |
132 | ||
133 | impl fmt::Debug for Formatter { | |
134 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
135 | f.debug_struct("Formatter").finish() | |
136 | } | |
137 | } | |
138 | ||
139 | pub(crate) type FormatFn = Box<dyn Fn(&mut Formatter, &Record) -> io::Result<()> + Sync + Send>; | |
140 | ||
141 | pub(crate) struct Builder { | |
142 | pub format_timestamp: Option<TimestampPrecision>, | |
143 | pub format_module_path: bool, | |
144 | pub format_level: bool, | |
145 | pub format_indent: Option<usize>, | |
146 | pub custom_format: Option<FormatFn>, | |
147 | pub format_suffix: &'static str, | |
148 | built: bool, | |
149 | } | |
150 | ||
151 | impl Default for Builder { | |
152 | fn default() -> Self { | |
153 | Builder { | |
154 | format_timestamp: Some(Default::default()), | |
155 | format_module_path: true, | |
156 | format_level: true, | |
157 | format_indent: Some(4), | |
158 | custom_format: None, | |
159 | format_suffix: "\n", | |
160 | built: false, | |
161 | } | |
162 | } | |
163 | } | |
164 | ||
165 | impl Builder { | |
166 | /// Convert the format into a callable function. | |
167 | /// | |
168 | /// If the `custom_format` is `Some`, then any `default_format` switches are ignored. | |
169 | /// If the `custom_format` is `None`, then a default format is returned. | |
170 | /// Any `default_format` switches set to `false` won't be written by the format. | |
171 | pub fn build(&mut self) -> FormatFn { | |
172 | assert!(!self.built, "attempt to re-use consumed builder"); | |
173 | ||
174 | let built = mem::replace( | |
175 | self, | |
176 | Builder { | |
177 | built: true, | |
178 | ..Default::default() | |
179 | }, | |
180 | ); | |
181 | ||
182 | if let Some(fmt) = built.custom_format { | |
183 | fmt | |
184 | } else { | |
185 | Box::new(move |buf, record| { | |
186 | let fmt = DefaultFormat { | |
187 | timestamp: built.format_timestamp, | |
188 | module_path: built.format_module_path, | |
189 | level: built.format_level, | |
190 | written_header_value: false, | |
191 | indent: built.format_indent, | |
192 | suffix: built.format_suffix, | |
193 | buf, | |
194 | }; | |
195 | ||
196 | fmt.write(record) | |
197 | }) | |
198 | } | |
199 | } | |
200 | } | |
201 | ||
202 | #[cfg(feature = "termcolor")] | |
203 | type SubtleStyle = StyledValue<'static, &'static str>; | |
204 | #[cfg(not(feature = "termcolor"))] | |
205 | type SubtleStyle = &'static str; | |
206 | ||
207 | /// The default format. | |
208 | /// | |
209 | /// This format needs to work with any combination of crate features. | |
210 | struct DefaultFormat<'a> { | |
211 | timestamp: Option<TimestampPrecision>, | |
212 | module_path: bool, | |
213 | level: bool, | |
214 | written_header_value: bool, | |
215 | indent: Option<usize>, | |
216 | buf: &'a mut Formatter, | |
217 | suffix: &'a str, | |
218 | } | |
219 | ||
220 | impl<'a> DefaultFormat<'a> { | |
221 | fn write(mut self, record: &Record) -> io::Result<()> { | |
222 | self.write_timestamp()?; | |
223 | self.write_level(record)?; | |
224 | self.write_module_path(record)?; | |
225 | self.finish_header()?; | |
226 | ||
227 | self.write_args(record) | |
228 | } | |
229 | ||
230 | fn subtle_style(&self, text: &'static str) -> SubtleStyle { | |
231 | #[cfg(feature = "termcolor")] | |
232 | { | |
233 | self.buf | |
234 | .style() | |
235 | .set_color(Color::Black) | |
236 | .set_intense(true) | |
237 | .clone() | |
238 | .into_value(text) | |
239 | } | |
240 | #[cfg(not(feature = "termcolor"))] | |
241 | { | |
242 | text | |
243 | } | |
244 | } | |
245 | ||
246 | fn write_header_value<T>(&mut self, value: T) -> io::Result<()> | |
247 | where | |
248 | T: Display, | |
249 | { | |
250 | if !self.written_header_value { | |
251 | self.written_header_value = true; | |
252 | ||
253 | let open_brace = self.subtle_style("["); | |
254 | write!(self.buf, "{}{}", open_brace, value) | |
255 | } else { | |
256 | write!(self.buf, " {}", value) | |
257 | } | |
258 | } | |
259 | ||
260 | fn write_level(&mut self, record: &Record) -> io::Result<()> { | |
261 | if !self.level { | |
262 | return Ok(()); | |
263 | } | |
264 | ||
265 | let level = { | |
266 | #[cfg(feature = "termcolor")] | |
267 | { | |
268 | self.buf.default_styled_level(record.level()) | |
269 | } | |
270 | #[cfg(not(feature = "termcolor"))] | |
271 | { | |
272 | record.level() | |
273 | } | |
274 | }; | |
275 | ||
276 | self.write_header_value(format_args!("{:<5}", level)) | |
277 | } | |
278 | ||
279 | fn write_timestamp(&mut self) -> io::Result<()> { | |
280 | #[cfg(feature = "humantime")] | |
281 | { | |
282 | use self::TimestampPrecision::*; | |
283 | let ts = match self.timestamp { | |
284 | None => return Ok(()), | |
285 | Some(Seconds) => self.buf.timestamp_seconds(), | |
286 | Some(Millis) => self.buf.timestamp_millis(), | |
287 | Some(Micros) => self.buf.timestamp_micros(), | |
288 | Some(Nanos) => self.buf.timestamp_nanos(), | |
289 | }; | |
290 | ||
291 | self.write_header_value(ts) | |
292 | } | |
293 | #[cfg(not(feature = "humantime"))] | |
294 | { | |
295 | // Trick the compiler to think we have used self.timestamp | |
296 | // Workaround for "field is never used: `timestamp`" compiler nag. | |
297 | let _ = self.timestamp; | |
298 | Ok(()) | |
299 | } | |
300 | } | |
301 | ||
302 | fn write_module_path(&mut self, record: &Record) -> io::Result<()> { | |
303 | if !self.module_path { | |
304 | return Ok(()); | |
305 | } | |
306 | ||
307 | if let Some(module_path) = record.module_path() { | |
308 | self.write_header_value(module_path) | |
309 | } else { | |
310 | Ok(()) | |
311 | } | |
312 | } | |
313 | ||
314 | fn finish_header(&mut self) -> io::Result<()> { | |
315 | if self.written_header_value { | |
316 | let close_brace = self.subtle_style("]"); | |
317 | write!(self.buf, "{} ", close_brace) | |
318 | } else { | |
319 | Ok(()) | |
320 | } | |
321 | } | |
322 | ||
323 | fn write_args(&mut self, record: &Record) -> io::Result<()> { | |
324 | match self.indent { | |
325 | // Fast path for no indentation | |
326 | None => write!(self.buf, "{}{}", record.args(), self.suffix), | |
327 | ||
328 | Some(indent_count) => { | |
329 | // Create a wrapper around the buffer only if we have to actually indent the message | |
330 | ||
331 | struct IndentWrapper<'a, 'b: 'a> { | |
332 | fmt: &'a mut DefaultFormat<'b>, | |
333 | indent_count: usize, | |
334 | } | |
335 | ||
336 | impl<'a, 'b> Write for IndentWrapper<'a, 'b> { | |
337 | fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | |
338 | let mut first = true; | |
339 | for chunk in buf.split(|&x| x == b'\n') { | |
340 | if !first { | |
341 | write!( | |
342 | self.fmt.buf, | |
343 | "{}{:width$}", | |
344 | self.fmt.suffix, | |
345 | "", | |
346 | width = self.indent_count | |
347 | )?; | |
348 | } | |
349 | self.fmt.buf.write_all(chunk)?; | |
350 | first = false; | |
351 | } | |
352 | ||
353 | Ok(buf.len()) | |
354 | } | |
355 | ||
356 | fn flush(&mut self) -> io::Result<()> { | |
357 | self.fmt.buf.flush() | |
358 | } | |
359 | } | |
360 | ||
361 | // The explicit scope here is just to make older versions of Rust happy | |
362 | { | |
363 | let mut wrapper = IndentWrapper { | |
364 | fmt: self, | |
365 | indent_count, | |
366 | }; | |
367 | write!(wrapper, "{}", record.args())?; | |
368 | } | |
369 | ||
370 | write!(self.buf, "{}", self.suffix)?; | |
371 | ||
372 | Ok(()) | |
373 | } | |
374 | } | |
375 | } | |
376 | } | |
377 | ||
378 | #[cfg(test)] | |
379 | mod tests { | |
380 | use super::*; | |
381 | ||
382 | use log::{Level, Record}; | |
383 | ||
384 | fn write(fmt: DefaultFormat) -> String { | |
385 | let buf = fmt.buf.buf.clone(); | |
386 | ||
387 | let record = Record::builder() | |
388 | .args(format_args!("log\nmessage")) | |
389 | .level(Level::Info) | |
390 | .file(Some("test.rs")) | |
391 | .line(Some(144)) | |
392 | .module_path(Some("test::path")) | |
393 | .build(); | |
394 | ||
395 | fmt.write(&record).expect("failed to write record"); | |
396 | ||
397 | let buf = buf.borrow(); | |
398 | String::from_utf8(buf.bytes().to_vec()).expect("failed to read record") | |
399 | } | |
400 | ||
401 | #[test] | |
402 | fn format_with_header() { | |
403 | let writer = writer::Builder::new() | |
404 | .write_style(WriteStyle::Never) | |
405 | .build(); | |
406 | ||
407 | let mut f = Formatter::new(&writer); | |
408 | ||
409 | let written = write(DefaultFormat { | |
410 | timestamp: None, | |
411 | module_path: true, | |
412 | level: true, | |
413 | written_header_value: false, | |
414 | indent: None, | |
415 | suffix: "\n", | |
416 | buf: &mut f, | |
417 | }); | |
418 | ||
419 | assert_eq!("[INFO test::path] log\nmessage\n", written); | |
420 | } | |
421 | ||
422 | #[test] | |
423 | fn format_no_header() { | |
424 | let writer = writer::Builder::new() | |
425 | .write_style(WriteStyle::Never) | |
426 | .build(); | |
427 | ||
428 | let mut f = Formatter::new(&writer); | |
429 | ||
430 | let written = write(DefaultFormat { | |
431 | timestamp: None, | |
432 | module_path: false, | |
433 | level: false, | |
434 | written_header_value: false, | |
435 | indent: None, | |
436 | suffix: "\n", | |
437 | buf: &mut f, | |
438 | }); | |
439 | ||
440 | assert_eq!("log\nmessage\n", written); | |
441 | } | |
442 | ||
443 | #[test] | |
444 | fn format_indent_spaces() { | |
445 | let writer = writer::Builder::new() | |
446 | .write_style(WriteStyle::Never) | |
447 | .build(); | |
448 | ||
449 | let mut f = Formatter::new(&writer); | |
450 | ||
451 | let written = write(DefaultFormat { | |
452 | timestamp: None, | |
453 | module_path: true, | |
454 | level: true, | |
455 | written_header_value: false, | |
456 | indent: Some(4), | |
457 | suffix: "\n", | |
458 | buf: &mut f, | |
459 | }); | |
460 | ||
461 | assert_eq!("[INFO test::path] log\n message\n", written); | |
462 | } | |
463 | ||
464 | #[test] | |
465 | fn format_indent_zero_spaces() { | |
466 | let writer = writer::Builder::new() | |
467 | .write_style(WriteStyle::Never) | |
468 | .build(); | |
469 | ||
470 | let mut f = Formatter::new(&writer); | |
471 | ||
472 | let written = write(DefaultFormat { | |
473 | timestamp: None, | |
474 | module_path: true, | |
475 | level: true, | |
476 | written_header_value: false, | |
477 | indent: Some(0), | |
478 | suffix: "\n", | |
479 | buf: &mut f, | |
480 | }); | |
481 | ||
482 | assert_eq!("[INFO test::path] log\nmessage\n", written); | |
483 | } | |
484 | ||
485 | #[test] | |
486 | fn format_indent_spaces_no_header() { | |
487 | let writer = writer::Builder::new() | |
488 | .write_style(WriteStyle::Never) | |
489 | .build(); | |
490 | ||
491 | let mut f = Formatter::new(&writer); | |
492 | ||
493 | let written = write(DefaultFormat { | |
494 | timestamp: None, | |
495 | module_path: false, | |
496 | level: false, | |
497 | written_header_value: false, | |
498 | indent: Some(4), | |
499 | suffix: "\n", | |
500 | buf: &mut f, | |
501 | }); | |
502 | ||
503 | assert_eq!("log\n message\n", written); | |
504 | } | |
505 | ||
506 | #[test] | |
507 | fn format_suffix() { | |
508 | let writer = writer::Builder::new() | |
509 | .write_style(WriteStyle::Never) | |
510 | .build(); | |
511 | ||
512 | let mut f = Formatter::new(&writer); | |
513 | ||
514 | let written = write(DefaultFormat { | |
515 | timestamp: None, | |
516 | module_path: false, | |
517 | level: false, | |
518 | written_header_value: false, | |
519 | indent: None, | |
520 | suffix: "\n\n", | |
521 | buf: &mut f, | |
522 | }); | |
523 | ||
524 | assert_eq!("log\nmessage\n\n", written); | |
525 | } | |
526 | ||
527 | #[test] | |
528 | fn format_suffix_with_indent() { | |
529 | let writer = writer::Builder::new() | |
530 | .write_style(WriteStyle::Never) | |
531 | .build(); | |
532 | ||
533 | let mut f = Formatter::new(&writer); | |
534 | ||
535 | let written = write(DefaultFormat { | |
536 | timestamp: None, | |
537 | module_path: false, | |
538 | level: false, | |
539 | written_header_value: false, | |
540 | indent: Some(4), | |
541 | suffix: "\n\n", | |
542 | buf: &mut f, | |
543 | }); | |
544 | ||
545 | assert_eq!("log\n\n message\n\n", written); | |
546 | } | |
547 | } |