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
32use std::cell::RefCell;
33use std::fmt::Display;
34use std::io::prelude::*;
35use std::rc::Rc;
36use std::{fmt, io, mem};
37
38#[cfg(feature = "color")]
39use log::Level;
40use log::Record;
41
42#[cfg(feature = "humantime")]
43mod humantime;
44pub(crate) mod writer;
45
46#[cfg(feature = "color")]
47mod style;
48#[cfg(feature = "color")]
49pub use style::{Color, Style, StyledValue};
50
51#[cfg(feature = "humantime")]
52pub use self::humantime::Timestamp;
53pub use self::writer::glob::*;
54
55use self::writer::{Buffer, Writer};
56
57pub(crate) mod glob {
58 pub use super::{Target, TimestampPrecision, WriteStyle};
59}
60
61/// Formatting precision of timestamps.
62///
63/// Seconds give precision of full seconds, milliseconds give thousands of a
64/// second (3 decimal digits), microseconds are millionth of a second (6 decimal
65/// digits) and nanoseconds are billionth of a second (9 decimal digits).
66#[derive(Copy, Clone, Debug)]
67pub enum TimestampPrecision {
68 /// Full second precision (0 decimal digits)
69 Seconds,
70 /// Millisecond precision (3 decimal digits)
71 Millis,
72 /// Microsecond precision (6 decimal digits)
73 Micros,
74 /// Nanosecond precision (9 decimal digits)
75 Nanos,
76}
77
78/// The default timestamp precision is seconds.
79impl Default for TimestampPrecision {
80 fn default() -> Self {
81 TimestampPrecision::Seconds
82 }
83}
84
85/// A formatter to write logs into.
86///
87/// `Formatter` implements the standard [`Write`] trait for writing log records.
88/// It also supports terminal colors, through the [`style`] method.
89///
90/// # Examples
91///
92/// Use the [`writeln`] macro to format a log record.
93/// An instance of a `Formatter` is passed to an `env_logger` format as `buf`:
94///
95/// ```
96/// use std::io::Write;
97///
98/// let mut builder = env_logger::Builder::new();
99///
100/// builder.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args()));
101/// ```
102///
103/// [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html
104/// [`writeln`]: https://doc.rust-lang.org/stable/std/macro.writeln.html
105/// [`style`]: #method.style
106pub struct Formatter {
107 buf: Rc<RefCell<Buffer>>,
108 write_style: WriteStyle,
109}
110
111impl Formatter {
112 pub(crate) fn new(writer: &Writer) -> Self {
113 Formatter {
114 buf: Rc::new(RefCell::new(writer.buffer())),
115 write_style: writer.write_style(),
116 }
117 }
118
119 pub(crate) fn write_style(&self) -> WriteStyle {
120 self.write_style
121 }
122
123 pub(crate) fn print(&self, writer: &Writer) -> io::Result<()> {
124 writer.print(&self.buf.borrow())
125 }
126
127 pub(crate) fn clear(&mut self) {
128 self.buf.borrow_mut().clear()
129 }
130}
131
132#[cfg(feature = "color")]
133impl Formatter {
134 /// Begin a new [`Style`].
135 ///
136 /// # Examples
137 ///
138 /// Create a bold, red colored style and use it to print the log level:
139 ///
140 /// ```
141 /// use std::io::Write;
142 /// use env_logger::fmt::Color;
143 ///
144 /// let mut builder = env_logger::Builder::new();
145 ///
146 /// builder.format(|buf, record| {
147 /// let mut level_style = buf.style();
148 ///
149 /// level_style.set_color(Color::Red).set_bold(true);
150 ///
151 /// writeln!(buf, "{}: {}",
152 /// level_style.value(record.level()),
153 /// record.args())
154 /// });
155 /// ```
156 ///
157 /// [`Style`]: struct.Style.html
158 pub fn style(&self) -> Style {
159 Style {
160 buf: self.buf.clone(),
161 spec: termcolor::ColorSpec::new(),
162 }
163 }
164
165 /// Get the default [`Style`] for the given level.
166 ///
167 /// The style can be used to print other values besides the level.
168 pub fn default_level_style(&self, level: Level) -> Style {
169 let mut level_style = self.style();
170 match level {
171 Level::Trace => level_style.set_color(Color::Cyan),
172 Level::Debug => level_style.set_color(Color::Blue),
173 Level::Info => level_style.set_color(Color::Green),
174 Level::Warn => level_style.set_color(Color::Yellow),
175 Level::Error => level_style.set_color(Color::Red).set_bold(true),
176 };
177 level_style
178 }
179
180 /// Get a printable [`Style`] for the given level.
181 ///
182 /// The style can only be used to print the level.
183 pub fn default_styled_level(&self, level: Level) -> StyledValue<'static, Level> {
184 self.default_level_style(level).into_value(level)
185 }
186}
187
188impl Write for Formatter {
189 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
190 self.buf.borrow_mut().write(buf)
191 }
192
193 fn flush(&mut self) -> io::Result<()> {
194 self.buf.borrow_mut().flush()
195 }
196}
197
198impl fmt::Debug for Formatter {
199 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
200 f.debug_struct(name:"Formatter").finish()
201 }
202}
203
204pub(crate) type FormatFn = Box<dyn Fn(&mut Formatter, &Record) -> io::Result<()> + Sync + Send>;
205
206pub(crate) struct Builder {
207 pub format_timestamp: Option<TimestampPrecision>,
208 pub format_module_path: bool,
209 pub format_target: bool,
210 pub format_level: bool,
211 pub format_indent: Option<usize>,
212 pub custom_format: Option<FormatFn>,
213 pub format_suffix: &'static str,
214 built: bool,
215}
216
217impl Builder {
218 /// Convert the format into a callable function.
219 ///
220 /// If the `custom_format` is `Some`, then any `default_format` switches are ignored.
221 /// If the `custom_format` is `None`, then a default format is returned.
222 /// Any `default_format` switches set to `false` won't be written by the format.
223 pub fn build(&mut self) -> FormatFn {
224 assert!(!self.built, "attempt to re-use consumed builder");
225
226 let built = mem::replace(
227 self,
228 Builder {
229 built: true,
230 ..Default::default()
231 },
232 );
233
234 if let Some(fmt) = built.custom_format {
235 fmt
236 } else {
237 Box::new(move |buf, record| {
238 let fmt = DefaultFormat {
239 timestamp: built.format_timestamp,
240 module_path: built.format_module_path,
241 target: built.format_target,
242 level: built.format_level,
243 written_header_value: false,
244 indent: built.format_indent,
245 suffix: built.format_suffix,
246 buf,
247 };
248
249 fmt.write(record)
250 })
251 }
252 }
253}
254
255impl Default for Builder {
256 fn default() -> Self {
257 Builder {
258 format_timestamp: Some(Default::default()),
259 format_module_path: false,
260 format_target: true,
261 format_level: true,
262 format_indent: Some(4),
263 custom_format: None,
264 format_suffix: "\n",
265 built: false,
266 }
267 }
268}
269
270#[cfg(feature = "color")]
271type SubtleStyle = StyledValue<'static, &'static str>;
272#[cfg(not(feature = "color"))]
273type SubtleStyle = &'static str;
274
275/// The default format.
276///
277/// This format needs to work with any combination of crate features.
278struct DefaultFormat<'a> {
279 timestamp: Option<TimestampPrecision>,
280 module_path: bool,
281 target: bool,
282 level: bool,
283 written_header_value: bool,
284 indent: Option<usize>,
285 buf: &'a mut Formatter,
286 suffix: &'a str,
287}
288
289impl<'a> DefaultFormat<'a> {
290 fn write(mut self, record: &Record) -> io::Result<()> {
291 self.write_timestamp()?;
292 self.write_level(record)?;
293 self.write_module_path(record)?;
294 self.write_target(record)?;
295 self.finish_header()?;
296
297 self.write_args(record)
298 }
299
300 fn subtle_style(&self, text: &'static str) -> SubtleStyle {
301 #[cfg(feature = "color")]
302 {
303 self.buf
304 .style()
305 .set_color(Color::Black)
306 .set_intense(true)
307 .clone()
308 .into_value(text)
309 }
310 #[cfg(not(feature = "color"))]
311 {
312 text
313 }
314 }
315
316 fn write_header_value<T>(&mut self, value: T) -> io::Result<()>
317 where
318 T: Display,
319 {
320 if !self.written_header_value {
321 self.written_header_value = true;
322
323 let open_brace = self.subtle_style("[");
324 write!(self.buf, "{}{}", open_brace, value)
325 } else {
326 write!(self.buf, " {}", value)
327 }
328 }
329
330 fn write_level(&mut self, record: &Record) -> io::Result<()> {
331 if !self.level {
332 return Ok(());
333 }
334
335 let level = {
336 #[cfg(feature = "color")]
337 {
338 self.buf.default_styled_level(record.level())
339 }
340 #[cfg(not(feature = "color"))]
341 {
342 record.level()
343 }
344 };
345
346 self.write_header_value(format_args!("{:<5}", level))
347 }
348
349 fn write_timestamp(&mut self) -> io::Result<()> {
350 #[cfg(feature = "humantime")]
351 {
352 use self::TimestampPrecision::*;
353 let ts = match self.timestamp {
354 None => return Ok(()),
355 Some(Seconds) => self.buf.timestamp_seconds(),
356 Some(Millis) => self.buf.timestamp_millis(),
357 Some(Micros) => self.buf.timestamp_micros(),
358 Some(Nanos) => self.buf.timestamp_nanos(),
359 };
360
361 self.write_header_value(ts)
362 }
363 #[cfg(not(feature = "humantime"))]
364 {
365 // Trick the compiler to think we have used self.timestamp
366 // Workaround for "field is never used: `timestamp`" compiler nag.
367 let _ = self.timestamp;
368 Ok(())
369 }
370 }
371
372 fn write_module_path(&mut self, record: &Record) -> io::Result<()> {
373 if !self.module_path {
374 return Ok(());
375 }
376
377 if let Some(module_path) = record.module_path() {
378 self.write_header_value(module_path)
379 } else {
380 Ok(())
381 }
382 }
383
384 fn write_target(&mut self, record: &Record) -> io::Result<()> {
385 if !self.target {
386 return Ok(());
387 }
388
389 match record.target() {
390 "" => Ok(()),
391 target => self.write_header_value(target),
392 }
393 }
394
395 fn finish_header(&mut self) -> io::Result<()> {
396 if self.written_header_value {
397 let close_brace = self.subtle_style("]");
398 write!(self.buf, "{} ", close_brace)
399 } else {
400 Ok(())
401 }
402 }
403
404 fn write_args(&mut self, record: &Record) -> io::Result<()> {
405 match self.indent {
406 // Fast path for no indentation
407 None => write!(self.buf, "{}{}", record.args(), self.suffix),
408
409 Some(indent_count) => {
410 // Create a wrapper around the buffer only if we have to actually indent the message
411
412 struct IndentWrapper<'a, 'b: 'a> {
413 fmt: &'a mut DefaultFormat<'b>,
414 indent_count: usize,
415 }
416
417 impl<'a, 'b> Write for IndentWrapper<'a, 'b> {
418 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
419 let mut first = true;
420 for chunk in buf.split(|&x| x == b'\n') {
421 if !first {
422 write!(
423 self.fmt.buf,
424 "{}{:width$}",
425 self.fmt.suffix,
426 "",
427 width = self.indent_count
428 )?;
429 }
430 self.fmt.buf.write_all(chunk)?;
431 first = false;
432 }
433
434 Ok(buf.len())
435 }
436
437 fn flush(&mut self) -> io::Result<()> {
438 self.fmt.buf.flush()
439 }
440 }
441
442 // The explicit scope here is just to make older versions of Rust happy
443 {
444 let mut wrapper = IndentWrapper {
445 fmt: self,
446 indent_count,
447 };
448 write!(wrapper, "{}", record.args())?;
449 }
450
451 write!(self.buf, "{}", self.suffix)?;
452
453 Ok(())
454 }
455 }
456 }
457}
458
459#[cfg(test)]
460mod tests {
461 use super::*;
462
463 use log::{Level, Record};
464
465 fn write_record(record: Record, fmt: DefaultFormat) -> String {
466 let buf = fmt.buf.buf.clone();
467
468 fmt.write(&record).expect("failed to write record");
469
470 let buf = buf.borrow();
471 String::from_utf8(buf.as_bytes().to_vec()).expect("failed to read record")
472 }
473
474 fn write_target(target: &str, fmt: DefaultFormat) -> String {
475 write_record(
476 Record::builder()
477 .args(format_args!("log\nmessage"))
478 .level(Level::Info)
479 .file(Some("test.rs"))
480 .line(Some(144))
481 .module_path(Some("test::path"))
482 .target(target)
483 .build(),
484 fmt,
485 )
486 }
487
488 fn write(fmt: DefaultFormat) -> String {
489 write_target("", fmt)
490 }
491
492 #[test]
493 fn format_with_header() {
494 let writer = writer::Builder::new()
495 .write_style(WriteStyle::Never)
496 .build();
497
498 let mut f = Formatter::new(&writer);
499
500 let written = write(DefaultFormat {
501 timestamp: None,
502 module_path: true,
503 target: false,
504 level: true,
505 written_header_value: false,
506 indent: None,
507 suffix: "\n",
508 buf: &mut f,
509 });
510
511 assert_eq!("[INFO test::path] log\nmessage\n", written);
512 }
513
514 #[test]
515 fn format_no_header() {
516 let writer = writer::Builder::new()
517 .write_style(WriteStyle::Never)
518 .build();
519
520 let mut f = Formatter::new(&writer);
521
522 let written = write(DefaultFormat {
523 timestamp: None,
524 module_path: false,
525 target: false,
526 level: false,
527 written_header_value: false,
528 indent: None,
529 suffix: "\n",
530 buf: &mut f,
531 });
532
533 assert_eq!("log\nmessage\n", written);
534 }
535
536 #[test]
537 fn format_indent_spaces() {
538 let writer = writer::Builder::new()
539 .write_style(WriteStyle::Never)
540 .build();
541
542 let mut f = Formatter::new(&writer);
543
544 let written = write(DefaultFormat {
545 timestamp: None,
546 module_path: true,
547 target: false,
548 level: true,
549 written_header_value: false,
550 indent: Some(4),
551 suffix: "\n",
552 buf: &mut f,
553 });
554
555 assert_eq!("[INFO test::path] log\n message\n", written);
556 }
557
558 #[test]
559 fn format_indent_zero_spaces() {
560 let writer = writer::Builder::new()
561 .write_style(WriteStyle::Never)
562 .build();
563
564 let mut f = Formatter::new(&writer);
565
566 let written = write(DefaultFormat {
567 timestamp: None,
568 module_path: true,
569 target: false,
570 level: true,
571 written_header_value: false,
572 indent: Some(0),
573 suffix: "\n",
574 buf: &mut f,
575 });
576
577 assert_eq!("[INFO test::path] log\nmessage\n", written);
578 }
579
580 #[test]
581 fn format_indent_spaces_no_header() {
582 let writer = writer::Builder::new()
583 .write_style(WriteStyle::Never)
584 .build();
585
586 let mut f = Formatter::new(&writer);
587
588 let written = write(DefaultFormat {
589 timestamp: None,
590 module_path: false,
591 target: false,
592 level: false,
593 written_header_value: false,
594 indent: Some(4),
595 suffix: "\n",
596 buf: &mut f,
597 });
598
599 assert_eq!("log\n message\n", written);
600 }
601
602 #[test]
603 fn format_suffix() {
604 let writer = writer::Builder::new()
605 .write_style(WriteStyle::Never)
606 .build();
607
608 let mut f = Formatter::new(&writer);
609
610 let written = write(DefaultFormat {
611 timestamp: None,
612 module_path: false,
613 target: false,
614 level: false,
615 written_header_value: false,
616 indent: None,
617 suffix: "\n\n",
618 buf: &mut f,
619 });
620
621 assert_eq!("log\nmessage\n\n", written);
622 }
623
624 #[test]
625 fn format_suffix_with_indent() {
626 let writer = writer::Builder::new()
627 .write_style(WriteStyle::Never)
628 .build();
629
630 let mut f = Formatter::new(&writer);
631
632 let written = write(DefaultFormat {
633 timestamp: None,
634 module_path: false,
635 target: false,
636 level: false,
637 written_header_value: false,
638 indent: Some(4),
639 suffix: "\n\n",
640 buf: &mut f,
641 });
642
643 assert_eq!("log\n\n message\n\n", written);
644 }
645
646 #[test]
647 fn format_target() {
648 let writer = writer::Builder::new()
649 .write_style(WriteStyle::Never)
650 .build();
651
652 let mut f = Formatter::new(&writer);
653
654 let written = write_target(
655 "target",
656 DefaultFormat {
657 timestamp: None,
658 module_path: true,
659 target: true,
660 level: true,
661 written_header_value: false,
662 indent: None,
663 suffix: "\n",
664 buf: &mut f,
665 },
666 );
667
668 assert_eq!("[INFO test::path target] log\nmessage\n", written);
669 }
670
671 #[test]
672 fn format_empty_target() {
673 let writer = writer::Builder::new()
674 .write_style(WriteStyle::Never)
675 .build();
676
677 let mut f = Formatter::new(&writer);
678
679 let written = write(DefaultFormat {
680 timestamp: None,
681 module_path: true,
682 target: true,
683 level: true,
684 written_header_value: false,
685 indent: None,
686 suffix: "\n",
687 buf: &mut f,
688 });
689
690 assert_eq!("[INFO test::path] log\nmessage\n", written);
691 }
692
693 #[test]
694 fn format_no_target() {
695 let writer = writer::Builder::new()
696 .write_style(WriteStyle::Never)
697 .build();
698
699 let mut f = Formatter::new(&writer);
700
701 let written = write_target(
702 "target",
703 DefaultFormat {
704 timestamp: None,
705 module_path: true,
706 target: false,
707 level: true,
708 written_header_value: false,
709 indent: None,
710 suffix: "\n",
711 buf: &mut f,
712 },
713 );
714
715 assert_eq!("[INFO test::path] log\nmessage\n", written);
716 }
717}
718