env_logger/fmt/
mod.rs

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("Formatter").finish()
136    }
137}
138
139pub(crate) struct Builder {
140    pub format_timestamp: Option<TimestampPrecision>,
141    pub format_module_path: bool,
142    pub format_level: bool,
143    pub format_indent: Option<usize>,
144    #[allow(unknown_lints, bare_trait_objects)]
145    pub custom_format: Option<Box<Fn(&mut Formatter, &Record) -> io::Result<()> + Sync + Send>>,
146    built: bool,
147}
148
149impl Default for Builder {
150    fn default() -> Self {
151        Builder {
152            format_timestamp: Some(Default::default()),
153            format_module_path: true,
154            format_level: true,
155            format_indent: Some(4),
156            custom_format: None,
157            built: false,
158        }
159    }
160}
161
162impl Builder {
163    /// Convert the format into a callable function.
164    ///
165    /// If the `custom_format` is `Some`, then any `default_format` switches are ignored.
166    /// If the `custom_format` is `None`, then a default format is returned.
167    /// Any `default_format` switches set to `false` won't be written by the format.
168    #[allow(unknown_lints, bare_trait_objects)]
169    pub fn build(&mut self) -> Box<Fn(&mut Formatter, &Record) -> io::Result<()> + Sync + Send> {
170        assert!(!self.built, "attempt to re-use consumed builder");
171
172        let built = mem::replace(
173            self,
174            Builder {
175                built: true,
176                ..Default::default()
177            },
178        );
179
180        if let Some(fmt) = built.custom_format {
181            fmt
182        } else {
183            Box::new(move |buf, record| {
184                let fmt = DefaultFormat {
185                    timestamp: built.format_timestamp,
186                    module_path: built.format_module_path,
187                    level: built.format_level,
188                    written_header_value: false,
189                    indent: built.format_indent,
190                    buf,
191                };
192
193                fmt.write(record)
194            })
195        }
196    }
197}
198
199#[cfg(feature = "termcolor")]
200type SubtleStyle = StyledValue<'static, &'static str>;
201#[cfg(not(feature = "termcolor"))]
202type SubtleStyle = &'static str;
203
204/// The default format.
205///
206/// This format needs to work with any combination of crate features.
207struct DefaultFormat<'a> {
208    timestamp: Option<TimestampPrecision>,
209    module_path: bool,
210    level: bool,
211    written_header_value: bool,
212    indent: Option<usize>,
213    buf: &'a mut Formatter,
214}
215
216impl<'a> DefaultFormat<'a> {
217    fn write(mut self, record: &Record) -> io::Result<()> {
218        self.write_timestamp()?;
219        self.write_level(record)?;
220        self.write_module_path(record)?;
221        self.finish_header()?;
222
223        self.write_args(record)
224    }
225
226    fn subtle_style(&self, text: &'static str) -> SubtleStyle {
227        #[cfg(feature = "termcolor")]
228        {
229            self.buf
230                .style()
231                .set_color(Color::Black)
232                .set_intense(true)
233                .into_value(text)
234        }
235        #[cfg(not(feature = "termcolor"))]
236        {
237            text
238        }
239    }
240
241    fn write_header_value<T>(&mut self, value: T) -> io::Result<()>
242    where
243        T: Display,
244    {
245        if !self.written_header_value {
246            self.written_header_value = true;
247
248            let open_brace = self.subtle_style("[");
249            write!(self.buf, "{}{}", open_brace, value)
250        } else {
251            write!(self.buf, " {}", value)
252        }
253    }
254
255    fn write_level(&mut self, record: &Record) -> io::Result<()> {
256        if !self.level {
257            return Ok(());
258        }
259
260        let level = {
261            #[cfg(feature = "termcolor")]
262            {
263                self.buf.default_styled_level(record.level())
264            }
265            #[cfg(not(feature = "termcolor"))]
266            {
267                record.level()
268            }
269        };
270
271        self.write_header_value(format_args!("{:<5}", level))
272    }
273
274    fn write_timestamp(&mut self) -> io::Result<()> {
275        #[cfg(feature = "humantime")]
276        {
277            use self::TimestampPrecision::*;
278            let ts = match self.timestamp {
279                None => return Ok(()),
280                Some(Seconds) => self.buf.timestamp_seconds(),
281                Some(Millis) => self.buf.timestamp_millis(),
282                Some(Micros) => self.buf.timestamp_micros(),
283                Some(Nanos) => self.buf.timestamp_nanos(),
284            };
285
286            self.write_header_value(ts)
287        }
288        #[cfg(not(feature = "humantime"))]
289        {
290            // Trick the compiler to think we have used self.timestamp
291            // Workaround for "field is never used: `timestamp`" compiler nag.
292            let _ = self.timestamp;
293            Ok(())
294        }
295    }
296
297    fn write_module_path(&mut self, record: &Record) -> io::Result<()> {
298        if !self.module_path {
299            return Ok(());
300        }
301
302        if let Some(module_path) = record.module_path() {
303            self.write_header_value(module_path)
304        } else {
305            Ok(())
306        }
307    }
308
309    fn finish_header(&mut self) -> io::Result<()> {
310        if self.written_header_value {
311            let close_brace = self.subtle_style("]");
312            write!(self.buf, "{} ", close_brace)
313        } else {
314            Ok(())
315        }
316    }
317
318    fn write_args(&mut self, record: &Record) -> io::Result<()> {
319        match self.indent {
320            // Fast path for no indentation
321            None => writeln!(self.buf, "{}", record.args()),
322
323            Some(indent_count) => {
324                // Create a wrapper around the buffer only if we have to actually indent the message
325
326                struct IndentWrapper<'a, 'b: 'a> {
327                    fmt: &'a mut DefaultFormat<'b>,
328                    indent_count: usize,
329                }
330
331                impl<'a, 'b> Write for IndentWrapper<'a, 'b> {
332                    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
333                        let mut first = true;
334                        for chunk in buf.split(|&x| x == b'\n') {
335                            if !first {
336                                write!(self.fmt.buf, "\n{:width$}", "", width = self.indent_count)?;
337                            }
338                            self.fmt.buf.write_all(chunk)?;
339                            first = false;
340                        }
341
342                        Ok(buf.len())
343                    }
344
345                    fn flush(&mut self) -> io::Result<()> {
346                        self.fmt.buf.flush()
347                    }
348                }
349
350                // The explicit scope here is just to make older versions of Rust happy
351                {
352                    let mut wrapper = IndentWrapper {
353                        fmt: self,
354                        indent_count,
355                    };
356                    write!(wrapper, "{}", record.args())?;
357                }
358
359                writeln!(self.buf)?;
360
361                Ok(())
362            }
363        }
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370
371    use log::{Level, Record};
372
373    fn write(fmt: DefaultFormat) -> String {
374        let buf = fmt.buf.buf.clone();
375
376        let record = Record::builder()
377            .args(format_args!("log\nmessage"))
378            .level(Level::Info)
379            .file(Some("test.rs"))
380            .line(Some(144))
381            .module_path(Some("test::path"))
382            .build();
383
384        fmt.write(&record).expect("failed to write record");
385
386        let buf = buf.borrow();
387        String::from_utf8(buf.bytes().to_vec()).expect("failed to read record")
388    }
389
390    #[test]
391    fn format_with_header() {
392        let writer = writer::Builder::new()
393            .write_style(WriteStyle::Never)
394            .build();
395
396        let mut f = Formatter::new(&writer);
397
398        let written = write(DefaultFormat {
399            timestamp: None,
400            module_path: true,
401            level: true,
402            written_header_value: false,
403            indent: None,
404            buf: &mut f,
405        });
406
407        assert_eq!("[INFO  test::path] log\nmessage\n", written);
408    }
409
410    #[test]
411    fn format_no_header() {
412        let writer = writer::Builder::new()
413            .write_style(WriteStyle::Never)
414            .build();
415
416        let mut f = Formatter::new(&writer);
417
418        let written = write(DefaultFormat {
419            timestamp: None,
420            module_path: false,
421            level: false,
422            written_header_value: false,
423            indent: None,
424            buf: &mut f,
425        });
426
427        assert_eq!("log\nmessage\n", written);
428    }
429
430    #[test]
431    fn format_indent_spaces() {
432        let writer = writer::Builder::new()
433            .write_style(WriteStyle::Never)
434            .build();
435
436        let mut f = Formatter::new(&writer);
437
438        let written = write(DefaultFormat {
439            timestamp: None,
440            module_path: true,
441            level: true,
442            written_header_value: false,
443            indent: Some(4),
444            buf: &mut f,
445        });
446
447        assert_eq!("[INFO  test::path] log\n    message\n", written);
448    }
449
450    #[test]
451    fn format_indent_zero_spaces() {
452        let writer = writer::Builder::new()
453            .write_style(WriteStyle::Never)
454            .build();
455
456        let mut f = Formatter::new(&writer);
457
458        let written = write(DefaultFormat {
459            timestamp: None,
460            module_path: true,
461            level: true,
462            written_header_value: false,
463            indent: Some(0),
464            buf: &mut f,
465        });
466
467        assert_eq!("[INFO  test::path] log\nmessage\n", written);
468    }
469
470    #[test]
471    fn format_indent_spaces_no_header() {
472        let writer = writer::Builder::new()
473            .write_style(WriteStyle::Never)
474            .build();
475
476        let mut f = Formatter::new(&writer);
477
478        let written = write(DefaultFormat {
479            timestamp: None,
480            module_path: false,
481            level: false,
482            written_header_value: false,
483            indent: Some(4),
484            buf: &mut f,
485        });
486
487        assert_eq!("log\n    message\n", written);
488    }
489}