Skip to main content

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