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
38use log::Record;
39
40mod humantime;
41pub(crate) mod writer;
42
43pub use self::humantime::glob::*;
44pub use self::writer::glob::*;
45
46use self::writer::{Buffer, Writer};
47
48pub(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)]
58pub 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.
70impl 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
97pub struct Formatter {
98 buf: Rc<RefCell<Buffer>>,
99 write_style: WriteStyle,
100}
101
102impl 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
123impl 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
133impl fmt::Debug for Formatter {
134 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135 f.debug_struct(name:"Formatter").finish()
136 }
137}
138
139pub(crate) type FormatFn = Box<dyn Fn(&mut Formatter, &Record) -> io::Result<()> + Sync + Send>;
140
141pub(crate) struct Builder {
142 pub format_timestamp: Option<TimestampPrecision>,
143 pub format_module_path: bool,
144 pub format_target: bool,
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
152impl Default for Builder {
153 fn default() -> Self {
154 Builder {
155 format_timestamp: Some(Default::default()),
156 format_module_path: false,
157 format_target: true,
158 format_level: true,
159 format_indent: Some(4),
160 custom_format: None,
161 format_suffix: "\n",
162 built: false,
163 }
164 }
165}
166
167impl 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,
191 target: built.format_target,
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 = "color")]
206type SubtleStyle = StyledValue<'static, &'static str>;
207#[cfg(not(feature = "color"))]
208type SubtleStyle = &'static str;
209
210/// The default format.
211///
212/// This format needs to work with any combination of crate features.
213struct DefaultFormat<'a> {
214 timestamp: Option<TimestampPrecision>,
215 module_path: bool,
216 target: bool,
217 level: bool,
218 written_header_value: bool,
219 indent: Option<usize>,
220 buf: &'a mut Formatter,
221 suffix: &'a str,
222}
223
224impl<'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)?;
229 self.write_target(record)?;
230 self.finish_header()?;
231
232 self.write_args(record)
233 }
234
235 fn subtle_style(&self, text: &'static str) -> SubtleStyle {
236 #[cfg(feature = "color")]
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 = "color"))]
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 = "color")]
272 {
273 self.buf.default_styled_level(record.level())
274 }
275 #[cfg(not(feature = "color"))]
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
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
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)]
395mod tests {
396 use super::*;
397
398 use log::{Level, Record};
399
400 fn write_record(record: Record, fmt: DefaultFormat) -> String {
401 let buf = fmt.buf.buf.clone();
402
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
409 fn write_target(target: &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
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,
438 target: false,
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,
460 target: false,
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,
482 target: false,
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,
504 target: false,
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,
526 target: false,
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,
548 target: false,
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,
570 target: false,
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 }
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 }
652}
653