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