]>
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, | |
923072b8 | 144 | pub format_target: bool, |
3c0e092e XL |
145 | pub format_level: bool, |
146 | pub format_indent: Option<usize>, | |
147 | pub custom_format: Option<FormatFn>, | |
148 | pub format_suffix: &'static str, | |
149 | built: bool, | |
150 | } | |
151 | ||
152 | impl Default for Builder { | |
153 | fn default() -> Self { | |
154 | Builder { | |
155 | format_timestamp: Some(Default::default()), | |
923072b8 FG |
156 | format_module_path: false, |
157 | format_target: true, | |
3c0e092e XL |
158 | format_level: true, |
159 | format_indent: Some(4), | |
160 | custom_format: None, | |
161 | format_suffix: "\n", | |
162 | built: false, | |
163 | } | |
164 | } | |
165 | } | |
166 | ||
167 | impl Builder { | |
168 | /// Convert the format into a callable function. | |
169 | /// | |
170 | /// If the `custom_format` is `Some`, then any `default_format` switches are ignored. | |
171 | /// If the `custom_format` is `None`, then a default format is returned. | |
172 | /// Any `default_format` switches set to `false` won't be written by the format. | |
173 | pub fn build(&mut self) -> FormatFn { | |
174 | assert!(!self.built, "attempt to re-use consumed builder"); | |
175 | ||
176 | let built = mem::replace( | |
177 | self, | |
178 | Builder { | |
179 | built: true, | |
180 | ..Default::default() | |
181 | }, | |
182 | ); | |
183 | ||
184 | if let Some(fmt) = built.custom_format { | |
185 | fmt | |
186 | } else { | |
187 | Box::new(move |buf, record| { | |
188 | let fmt = DefaultFormat { | |
189 | timestamp: built.format_timestamp, | |
190 | module_path: built.format_module_path, | |
923072b8 | 191 | target: built.format_target, |
3c0e092e XL |
192 | level: built.format_level, |
193 | written_header_value: false, | |
194 | indent: built.format_indent, | |
195 | suffix: built.format_suffix, | |
196 | buf, | |
197 | }; | |
198 | ||
199 | fmt.write(record) | |
200 | }) | |
201 | } | |
202 | } | |
203 | } | |
204 | ||
205 | #[cfg(feature = "termcolor")] | |
206 | type SubtleStyle = StyledValue<'static, &'static str>; | |
207 | #[cfg(not(feature = "termcolor"))] | |
208 | type SubtleStyle = &'static str; | |
209 | ||
210 | /// The default format. | |
211 | /// | |
212 | /// This format needs to work with any combination of crate features. | |
213 | struct DefaultFormat<'a> { | |
214 | timestamp: Option<TimestampPrecision>, | |
215 | module_path: bool, | |
923072b8 | 216 | target: bool, |
3c0e092e XL |
217 | level: bool, |
218 | written_header_value: bool, | |
219 | indent: Option<usize>, | |
220 | buf: &'a mut Formatter, | |
221 | suffix: &'a str, | |
222 | } | |
223 | ||
224 | impl<'a> DefaultFormat<'a> { | |
225 | fn write(mut self, record: &Record) -> io::Result<()> { | |
226 | self.write_timestamp()?; | |
227 | self.write_level(record)?; | |
228 | self.write_module_path(record)?; | |
923072b8 | 229 | self.write_target(record)?; |
3c0e092e XL |
230 | self.finish_header()?; |
231 | ||
232 | self.write_args(record) | |
233 | } | |
234 | ||
235 | fn subtle_style(&self, text: &'static str) -> SubtleStyle { | |
236 | #[cfg(feature = "termcolor")] | |
237 | { | |
238 | self.buf | |
239 | .style() | |
240 | .set_color(Color::Black) | |
241 | .set_intense(true) | |
242 | .clone() | |
243 | .into_value(text) | |
244 | } | |
245 | #[cfg(not(feature = "termcolor"))] | |
246 | { | |
247 | text | |
248 | } | |
249 | } | |
250 | ||
251 | fn write_header_value<T>(&mut self, value: T) -> io::Result<()> | |
252 | where | |
253 | T: Display, | |
254 | { | |
255 | if !self.written_header_value { | |
256 | self.written_header_value = true; | |
257 | ||
258 | let open_brace = self.subtle_style("["); | |
259 | write!(self.buf, "{}{}", open_brace, value) | |
260 | } else { | |
261 | write!(self.buf, " {}", value) | |
262 | } | |
263 | } | |
264 | ||
265 | fn write_level(&mut self, record: &Record) -> io::Result<()> { | |
266 | if !self.level { | |
267 | return Ok(()); | |
268 | } | |
269 | ||
270 | let level = { | |
271 | #[cfg(feature = "termcolor")] | |
272 | { | |
273 | self.buf.default_styled_level(record.level()) | |
274 | } | |
275 | #[cfg(not(feature = "termcolor"))] | |
276 | { | |
277 | record.level() | |
278 | } | |
279 | }; | |
280 | ||
281 | self.write_header_value(format_args!("{:<5}", level)) | |
282 | } | |
283 | ||
284 | fn write_timestamp(&mut self) -> io::Result<()> { | |
285 | #[cfg(feature = "humantime")] | |
286 | { | |
287 | use self::TimestampPrecision::*; | |
288 | let ts = match self.timestamp { | |
289 | None => return Ok(()), | |
290 | Some(Seconds) => self.buf.timestamp_seconds(), | |
291 | Some(Millis) => self.buf.timestamp_millis(), | |
292 | Some(Micros) => self.buf.timestamp_micros(), | |
293 | Some(Nanos) => self.buf.timestamp_nanos(), | |
294 | }; | |
295 | ||
296 | self.write_header_value(ts) | |
297 | } | |
298 | #[cfg(not(feature = "humantime"))] | |
299 | { | |
300 | // Trick the compiler to think we have used self.timestamp | |
301 | // Workaround for "field is never used: `timestamp`" compiler nag. | |
302 | let _ = self.timestamp; | |
303 | Ok(()) | |
304 | } | |
305 | } | |
306 | ||
307 | fn write_module_path(&mut self, record: &Record) -> io::Result<()> { | |
308 | if !self.module_path { | |
309 | return Ok(()); | |
310 | } | |
311 | ||
312 | if let Some(module_path) = record.module_path() { | |
313 | self.write_header_value(module_path) | |
314 | } else { | |
315 | Ok(()) | |
316 | } | |
317 | } | |
318 | ||
923072b8 FG |
319 | fn write_target(&mut self, record: &Record) -> io::Result<()> { |
320 | if !self.target { | |
321 | return Ok(()); | |
322 | } | |
323 | ||
324 | match record.target() { | |
325 | "" => Ok(()), | |
326 | target => self.write_header_value(target), | |
327 | } | |
328 | } | |
329 | ||
3c0e092e XL |
330 | fn finish_header(&mut self) -> io::Result<()> { |
331 | if self.written_header_value { | |
332 | let close_brace = self.subtle_style("]"); | |
333 | write!(self.buf, "{} ", close_brace) | |
334 | } else { | |
335 | Ok(()) | |
336 | } | |
337 | } | |
338 | ||
339 | fn write_args(&mut self, record: &Record) -> io::Result<()> { | |
340 | match self.indent { | |
341 | // Fast path for no indentation | |
342 | None => write!(self.buf, "{}{}", record.args(), self.suffix), | |
343 | ||
344 | Some(indent_count) => { | |
345 | // Create a wrapper around the buffer only if we have to actually indent the message | |
346 | ||
347 | struct IndentWrapper<'a, 'b: 'a> { | |
348 | fmt: &'a mut DefaultFormat<'b>, | |
349 | indent_count: usize, | |
350 | } | |
351 | ||
352 | impl<'a, 'b> Write for IndentWrapper<'a, 'b> { | |
353 | fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | |
354 | let mut first = true; | |
355 | for chunk in buf.split(|&x| x == b'\n') { | |
356 | if !first { | |
357 | write!( | |
358 | self.fmt.buf, | |
359 | "{}{:width$}", | |
360 | self.fmt.suffix, | |
361 | "", | |
362 | width = self.indent_count | |
363 | )?; | |
364 | } | |
365 | self.fmt.buf.write_all(chunk)?; | |
366 | first = false; | |
367 | } | |
368 | ||
369 | Ok(buf.len()) | |
370 | } | |
371 | ||
372 | fn flush(&mut self) -> io::Result<()> { | |
373 | self.fmt.buf.flush() | |
374 | } | |
375 | } | |
376 | ||
377 | // The explicit scope here is just to make older versions of Rust happy | |
378 | { | |
379 | let mut wrapper = IndentWrapper { | |
380 | fmt: self, | |
381 | indent_count, | |
382 | }; | |
383 | write!(wrapper, "{}", record.args())?; | |
384 | } | |
385 | ||
386 | write!(self.buf, "{}", self.suffix)?; | |
387 | ||
388 | Ok(()) | |
389 | } | |
390 | } | |
391 | } | |
392 | } | |
393 | ||
394 | #[cfg(test)] | |
395 | mod tests { | |
396 | use super::*; | |
397 | ||
398 | use log::{Level, Record}; | |
399 | ||
923072b8 | 400 | fn write_record(record: Record, fmt: DefaultFormat) -> String { |
3c0e092e XL |
401 | let buf = fmt.buf.buf.clone(); |
402 | ||
3c0e092e XL |
403 | fmt.write(&record).expect("failed to write record"); |
404 | ||
405 | let buf = buf.borrow(); | |
406 | String::from_utf8(buf.bytes().to_vec()).expect("failed to read record") | |
407 | } | |
408 | ||
923072b8 FG |
409 | fn write_target<'a>(target: &'a str, fmt: DefaultFormat) -> String { |
410 | write_record( | |
411 | Record::builder() | |
412 | .args(format_args!("log\nmessage")) | |
413 | .level(Level::Info) | |
414 | .file(Some("test.rs")) | |
415 | .line(Some(144)) | |
416 | .module_path(Some("test::path")) | |
417 | .target(target) | |
418 | .build(), | |
419 | fmt, | |
420 | ) | |
421 | } | |
422 | ||
423 | fn write(fmt: DefaultFormat) -> String { | |
424 | write_target("", fmt) | |
425 | } | |
426 | ||
3c0e092e XL |
427 | #[test] |
428 | fn format_with_header() { | |
429 | let writer = writer::Builder::new() | |
430 | .write_style(WriteStyle::Never) | |
431 | .build(); | |
432 | ||
433 | let mut f = Formatter::new(&writer); | |
434 | ||
435 | let written = write(DefaultFormat { | |
436 | timestamp: None, | |
437 | module_path: true, | |
923072b8 | 438 | target: false, |
3c0e092e XL |
439 | level: true, |
440 | written_header_value: false, | |
441 | indent: None, | |
442 | suffix: "\n", | |
443 | buf: &mut f, | |
444 | }); | |
445 | ||
446 | assert_eq!("[INFO test::path] log\nmessage\n", written); | |
447 | } | |
448 | ||
449 | #[test] | |
450 | fn format_no_header() { | |
451 | let writer = writer::Builder::new() | |
452 | .write_style(WriteStyle::Never) | |
453 | .build(); | |
454 | ||
455 | let mut f = Formatter::new(&writer); | |
456 | ||
457 | let written = write(DefaultFormat { | |
458 | timestamp: None, | |
459 | module_path: false, | |
923072b8 | 460 | target: false, |
3c0e092e XL |
461 | level: false, |
462 | written_header_value: false, | |
463 | indent: None, | |
464 | suffix: "\n", | |
465 | buf: &mut f, | |
466 | }); | |
467 | ||
468 | assert_eq!("log\nmessage\n", written); | |
469 | } | |
470 | ||
471 | #[test] | |
472 | fn format_indent_spaces() { | |
473 | let writer = writer::Builder::new() | |
474 | .write_style(WriteStyle::Never) | |
475 | .build(); | |
476 | ||
477 | let mut f = Formatter::new(&writer); | |
478 | ||
479 | let written = write(DefaultFormat { | |
480 | timestamp: None, | |
481 | module_path: true, | |
923072b8 | 482 | target: false, |
3c0e092e XL |
483 | level: true, |
484 | written_header_value: false, | |
485 | indent: Some(4), | |
486 | suffix: "\n", | |
487 | buf: &mut f, | |
488 | }); | |
489 | ||
490 | assert_eq!("[INFO test::path] log\n message\n", written); | |
491 | } | |
492 | ||
493 | #[test] | |
494 | fn format_indent_zero_spaces() { | |
495 | let writer = writer::Builder::new() | |
496 | .write_style(WriteStyle::Never) | |
497 | .build(); | |
498 | ||
499 | let mut f = Formatter::new(&writer); | |
500 | ||
501 | let written = write(DefaultFormat { | |
502 | timestamp: None, | |
503 | module_path: true, | |
923072b8 | 504 | target: false, |
3c0e092e XL |
505 | level: true, |
506 | written_header_value: false, | |
507 | indent: Some(0), | |
508 | suffix: "\n", | |
509 | buf: &mut f, | |
510 | }); | |
511 | ||
512 | assert_eq!("[INFO test::path] log\nmessage\n", written); | |
513 | } | |
514 | ||
515 | #[test] | |
516 | fn format_indent_spaces_no_header() { | |
517 | let writer = writer::Builder::new() | |
518 | .write_style(WriteStyle::Never) | |
519 | .build(); | |
520 | ||
521 | let mut f = Formatter::new(&writer); | |
522 | ||
523 | let written = write(DefaultFormat { | |
524 | timestamp: None, | |
525 | module_path: false, | |
923072b8 | 526 | target: false, |
3c0e092e XL |
527 | level: false, |
528 | written_header_value: false, | |
529 | indent: Some(4), | |
530 | suffix: "\n", | |
531 | buf: &mut f, | |
532 | }); | |
533 | ||
534 | assert_eq!("log\n message\n", written); | |
535 | } | |
536 | ||
537 | #[test] | |
538 | fn format_suffix() { | |
539 | let writer = writer::Builder::new() | |
540 | .write_style(WriteStyle::Never) | |
541 | .build(); | |
542 | ||
543 | let mut f = Formatter::new(&writer); | |
544 | ||
545 | let written = write(DefaultFormat { | |
546 | timestamp: None, | |
547 | module_path: false, | |
923072b8 | 548 | target: false, |
3c0e092e XL |
549 | level: false, |
550 | written_header_value: false, | |
551 | indent: None, | |
552 | suffix: "\n\n", | |
553 | buf: &mut f, | |
554 | }); | |
555 | ||
556 | assert_eq!("log\nmessage\n\n", written); | |
557 | } | |
558 | ||
559 | #[test] | |
560 | fn format_suffix_with_indent() { | |
561 | let writer = writer::Builder::new() | |
562 | .write_style(WriteStyle::Never) | |
563 | .build(); | |
564 | ||
565 | let mut f = Formatter::new(&writer); | |
566 | ||
567 | let written = write(DefaultFormat { | |
568 | timestamp: None, | |
569 | module_path: false, | |
923072b8 | 570 | target: false, |
3c0e092e XL |
571 | level: false, |
572 | written_header_value: false, | |
573 | indent: Some(4), | |
574 | suffix: "\n\n", | |
575 | buf: &mut f, | |
576 | }); | |
577 | ||
578 | assert_eq!("log\n\n message\n\n", written); | |
579 | } | |
923072b8 FG |
580 | |
581 | #[test] | |
582 | fn format_target() { | |
583 | let writer = writer::Builder::new() | |
584 | .write_style(WriteStyle::Never) | |
585 | .build(); | |
586 | ||
587 | let mut f = Formatter::new(&writer); | |
588 | ||
589 | let written = write_target( | |
590 | "target", | |
591 | DefaultFormat { | |
592 | timestamp: None, | |
593 | module_path: true, | |
594 | target: true, | |
595 | level: true, | |
596 | written_header_value: false, | |
597 | indent: None, | |
598 | suffix: "\n", | |
599 | buf: &mut f, | |
600 | }, | |
601 | ); | |
602 | ||
603 | assert_eq!("[INFO test::path target] log\nmessage\n", written); | |
604 | } | |
605 | ||
606 | #[test] | |
607 | fn format_empty_target() { | |
608 | let writer = writer::Builder::new() | |
609 | .write_style(WriteStyle::Never) | |
610 | .build(); | |
611 | ||
612 | let mut f = Formatter::new(&writer); | |
613 | ||
614 | let written = write(DefaultFormat { | |
615 | timestamp: None, | |
616 | module_path: true, | |
617 | target: true, | |
618 | level: true, | |
619 | written_header_value: false, | |
620 | indent: None, | |
621 | suffix: "\n", | |
622 | buf: &mut f, | |
623 | }); | |
624 | ||
625 | assert_eq!("[INFO test::path] log\nmessage\n", written); | |
626 | } | |
627 | ||
628 | #[test] | |
629 | fn format_no_target() { | |
630 | let writer = writer::Builder::new() | |
631 | .write_style(WriteStyle::Never) | |
632 | .build(); | |
633 | ||
634 | let mut f = Formatter::new(&writer); | |
635 | ||
636 | let written = write_target( | |
637 | "target", | |
638 | DefaultFormat { | |
639 | timestamp: None, | |
640 | module_path: true, | |
641 | target: false, | |
642 | level: true, | |
643 | written_header_value: false, | |
644 | indent: None, | |
645 | suffix: "\n", | |
646 | buf: &mut f, | |
647 | }, | |
648 | ); | |
649 | ||
650 | assert_eq!("[INFO test::path] log\nmessage\n", written); | |
651 | } | |
3c0e092e | 652 | } |