1use 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#[allow(clippy::exhaustive_enums)] #[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 Seconds,
97 Millis,
99 Micros,
101 Nanos,
103}
104
105impl Default for TimestampPrecision {
107 fn default() -> Self {
108 TimestampPrecision::Seconds
109 }
110}
111
112pub 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 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 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#[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 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
278pub struct ConfigurableFormat {
280 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 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 pub fn level(&mut self, write: bool) -> &mut Self {
309 self.level = write;
310 self
311 }
312
313 pub fn file(&mut self, write: bool) -> &mut Self {
315 self.source_file = write;
316 self
317 }
318
319 pub fn line_number(&mut self, write: bool) -> &mut Self {
323 self.source_line_number = write;
324 self
325 }
326
327 pub fn module_path(&mut self, write: bool) -> &mut Self {
329 self.module_path = write;
330 self
331 }
332
333 pub fn target(&mut self, write: bool) -> &mut Self {
335 self.target = write;
336 self
337 }
338
339 pub fn indent(&mut self, indent: Option<usize>) -> &mut Self {
342 self.indent = indent;
343 self
344 }
345
346 pub fn timestamp(&mut self, timestamp: Option<TimestampPrecision>) -> &mut Self {
348 self.timestamp = timestamp;
349 self
350 }
351
352 pub fn suffix(&mut self, suffix: &'static str) -> &mut Self {
354 self.suffix = suffix;
355 self
356 }
357
358 #[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
401struct 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 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 None => self.buf.write_fmt(format_args!("{0}", record.args()))write!(self.buf, "{}", record.args()),
559
560 Some(indent_count) => {
561 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 {
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}