Skip to main content

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