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 => styleStyle::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: Ref<'_, Buffer> = self.buf.borrow();
196 f&mut DebugStruct<'_, '_>.debug_struct("Formatter")
197 .field("buf", &buf)
198 .field(name:"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: Builder = mem::replace(
235 self,
236 src:Builder {
237 built: true,
238 ..Default::default()
239 },
240 );
241
242 if let Some(fmt: Box) = 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: 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<'_> = 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}
1004