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