diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs index e608a5c..bd2a2b8 100644 --- a/src/fmt/mod.rs +++ b/src/fmt/mod.rs @@ -200,20 +200,25 @@ impl fmt::Debug for Formatter { } } -pub(crate) type FormatFn = Box) -> io::Result<()> + Sync + Send>; +pub(crate) trait RecordFormat { + fn format(&self, formatter: &mut Formatter, record: &Record<'_>) -> io::Result<()>; +} + +impl RecordFormat for F +where + F: Fn(&mut Formatter, &Record<'_>) -> io::Result<()>, +{ + fn format(&self, formatter: &mut Formatter, record: &Record<'_>) -> io::Result<()> { + (self)(formatter, record) + } +} +pub(crate) type FormatFn = Box; + +#[derive(Default)] pub(crate) struct Builder { - pub(crate) format_timestamp: Option, - pub(crate) format_module_path: bool, - pub(crate) format_target: bool, - pub(crate) format_level: bool, - pub(crate) format_indent: Option, + pub(crate) default_format: ConfigurableFormat, pub(crate) custom_format: Option, - pub(crate) format_suffix: &'static str, - pub(crate) format_file: bool, - pub(crate) format_line_number: bool, - #[cfg(feature = "kv")] - pub(crate) kv_format: Option>, built: bool, } @@ -237,43 +242,7 @@ impl Builder { if let Some(fmt) = built.custom_format { fmt } else { - Box::new(move |buf, record| { - let fmt = DefaultFormat { - timestamp: built.format_timestamp, - module_path: built.format_module_path, - target: built.format_target, - level: built.format_level, - written_header_value: false, - indent: built.format_indent, - suffix: built.format_suffix, - source_file: built.format_file, - source_line_number: built.format_line_number, - #[cfg(feature = "kv")] - kv_format: built.kv_format.as_deref().unwrap_or(&default_kv_format), - buf, - }; - - fmt.write(record) - }) - } - } -} - -impl Default for Builder { - fn default() -> Self { - Builder { - format_timestamp: Some(Default::default()), - format_module_path: false, - format_target: true, - format_level: true, - format_file: false, - format_line_number: false, - format_indent: Some(4), - custom_format: None, - format_suffix: "\n", - #[cfg(feature = "kv")] - kv_format: None, - built: false, + Box::new(built.default_format) } } } @@ -307,25 +276,139 @@ impl Display for StyledValue { #[cfg(not(feature = "color"))] type StyledValue = T; +/// A [custom format][crate::Builder::format] with settings for which fields to show +pub struct ConfigurableFormat { + // This format needs to work with any combination of crate features. + pub(crate) timestamp: Option, + pub(crate) module_path: bool, + pub(crate) target: bool, + pub(crate) level: bool, + pub(crate) source_file: bool, + pub(crate) source_line_number: bool, + pub(crate) indent: Option, + pub(crate) suffix: &'static str, + #[cfg(feature = "kv")] + pub(crate) kv_format: Option>, +} + +impl ConfigurableFormat { + /// Format the [`Record`] as configured for outputting + pub fn format(&self, formatter: &mut Formatter, record: &Record<'_>) -> io::Result<()> { + let fmt = ConfigurableFormatWriter { + format: self, + buf: formatter, + written_header_value: false, + }; + + fmt.write(record) + } +} + +impl ConfigurableFormat { + /// Whether or not to write the level in the default format. + pub fn level(&mut self, write: bool) -> &mut Self { + self.level = write; + self + } + + /// Whether or not to write the source file path in the default format. + pub fn file(&mut self, write: bool) -> &mut Self { + self.source_file = write; + self + } + + /// Whether or not to write the source line number path in the default format. + /// + /// Only has effect if `format_file` is also enabled + pub fn line_number(&mut self, write: bool) -> &mut Self { + self.source_line_number = write; + self + } + + /// Whether or not to write the module path in the default format. + pub fn module_path(&mut self, write: bool) -> &mut Self { + self.module_path = write; + self + } + + /// Whether or not to write the target in the default format. + pub fn target(&mut self, write: bool) -> &mut Self { + self.target = write; + self + } + + /// Configures the amount of spaces to use to indent multiline log records. + /// A value of `None` disables any kind of indentation. + pub fn indent(&mut self, indent: Option) -> &mut Self { + self.indent = indent; + self + } + + /// Configures if timestamp should be included and in what precision. + pub fn timestamp(&mut self, timestamp: Option) -> &mut Self { + self.timestamp = timestamp; + self + } + + /// Configures the end of line suffix. + pub fn suffix(&mut self, suffix: &'static str) -> &mut Self { + self.suffix = suffix; + self + } + + /// Set the format for structured key/value pairs in the log record + /// + /// With the default format, this function is called for each record and should format + /// the structured key-value pairs as returned by [`log::Record::key_values`]. + /// + /// The format function is expected to output the string directly to the `Formatter` so that + /// implementations can use the [`std::fmt`] macros, similar to the main format function. + /// + /// The default format uses a space to separate each key-value pair, with an "=" between + /// the key and value. + #[cfg(feature = "kv")] + pub fn key_values(&mut self, format: F) -> &mut Self + where + F: Fn(&mut Formatter, &dyn log::kv::Source) -> io::Result<()> + Sync + Send + 'static, + { + self.kv_format = Some(Box::new(format)); + self + } +} + +impl Default for ConfigurableFormat { + fn default() -> Self { + Self { + timestamp: Some(Default::default()), + module_path: false, + target: true, + level: true, + source_file: false, + source_line_number: false, + indent: Some(4), + suffix: "\n", + #[cfg(feature = "kv")] + kv_format: None, + } + } +} + +impl RecordFormat for ConfigurableFormat { + fn format(&self, formatter: &mut Formatter, record: &Record<'_>) -> io::Result<()> { + self.format(formatter, record) + } +} + /// The default format. /// /// This format needs to work with any combination of crate features. -struct DefaultFormat<'a> { - timestamp: Option, - module_path: bool, - target: bool, - level: bool, - source_file: bool, - source_line_number: bool, - written_header_value: bool, - indent: Option, +struct ConfigurableFormatWriter<'a> { + format: &'a ConfigurableFormat, buf: &'a mut Formatter, - suffix: &'a str, - #[cfg(feature = "kv")] - kv_format: &'a KvFormatFn, + written_header_value: bool, } -impl DefaultFormat<'_> { +impl ConfigurableFormatWriter<'_> { fn write(mut self, record: &Record<'_>) -> io::Result<()> { self.write_timestamp()?; self.write_level(record)?; @@ -337,7 +420,7 @@ impl DefaultFormat<'_> { self.write_args(record)?; #[cfg(feature = "kv")] self.write_kv(record)?; - write!(self.buf, "{}", self.suffix) + write!(self.buf, "{}", self.format.suffix) } fn subtle_style(&self, text: &'static str) -> SubtleStyle { @@ -373,7 +456,7 @@ impl DefaultFormat<'_> { } fn write_level(&mut self, record: &Record<'_>) -> io::Result<()> { - if !self.level { + if !self.format.level { return Ok(()); } @@ -399,7 +482,7 @@ impl DefaultFormat<'_> { #[cfg(feature = "humantime")] { use self::TimestampPrecision::{Micros, Millis, Nanos, Seconds}; - let ts = match self.timestamp { + let ts = match self.format.timestamp { None => return Ok(()), Some(Seconds) => self.buf.timestamp_seconds(), Some(Millis) => self.buf.timestamp_millis(), @@ -413,13 +496,13 @@ impl DefaultFormat<'_> { { // Trick the compiler to think we have used self.timestamp // Workaround for "field is never used: `timestamp`" compiler nag. - let _ = self.timestamp; + let _ = self.format.timestamp; Ok(()) } } fn write_module_path(&mut self, record: &Record<'_>) -> io::Result<()> { - if !self.module_path { + if !self.format.module_path { return Ok(()); } @@ -431,12 +514,16 @@ impl DefaultFormat<'_> { } fn write_source_location(&mut self, record: &Record<'_>) -> io::Result<()> { - if !self.source_file { + if !self.format.source_file { return Ok(()); } if let Some(file_path) = record.file() { - let line = self.source_line_number.then(|| record.line()).flatten(); + let line = self + .format + .source_line_number + .then(|| record.line()) + .flatten(); match line { Some(line) => self.write_header_value(format_args!("{file_path}:{line}")), None => self.write_header_value(file_path), @@ -447,7 +534,7 @@ impl DefaultFormat<'_> { } fn write_target(&mut self, record: &Record<'_>) -> io::Result<()> { - if !self.target { + if !self.format.target { return Ok(()); } @@ -467,7 +554,7 @@ impl DefaultFormat<'_> { } fn write_args(&mut self, record: &Record<'_>) -> io::Result<()> { - match self.indent { + match self.format.indent { // Fast path for no indentation None => write!(self.buf, "{}", record.args()), @@ -475,7 +562,7 @@ impl DefaultFormat<'_> { // Create a wrapper around the buffer only if we have to actually indent the message struct IndentWrapper<'a, 'b> { - fmt: &'a mut DefaultFormat<'b>, + fmt: &'a mut ConfigurableFormatWriter<'b>, indent_count: usize, } @@ -487,7 +574,7 @@ impl DefaultFormat<'_> { write!( self.fmt.buf, "{}{:width$}", - self.fmt.suffix, + self.fmt.format.suffix, "", width = self.indent_count )?; @@ -520,7 +607,11 @@ impl DefaultFormat<'_> { #[cfg(feature = "kv")] fn write_kv(&mut self, record: &Record<'_>) -> io::Result<()> { - let format = self.kv_format; + let format = self + .format + .kv_format + .as_deref() + .unwrap_or(&default_kv_format); format(self.buf, record.key_values()) } } @@ -531,7 +622,7 @@ mod tests { use log::{Level, Record}; - fn write_record(record: Record<'_>, fmt: DefaultFormat<'_>) -> String { + fn write_record(record: Record<'_>, fmt: ConfigurableFormatWriter<'_>) -> String { let buf = fmt.buf.buf.clone(); fmt.write(&record).expect("failed to write record"); @@ -540,7 +631,7 @@ mod tests { String::from_utf8(buf.as_bytes().to_vec()).expect("failed to read record") } - fn write_target(target: &str, fmt: DefaultFormat<'_>) -> String { + fn write_target(target: &str, fmt: ConfigurableFormatWriter<'_>) -> String { write_record( Record::builder() .args(format_args!("log\nmessage")) @@ -554,7 +645,7 @@ mod tests { ) } - fn write(fmt: DefaultFormat<'_>) -> String { + fn write(fmt: ConfigurableFormatWriter<'_>) -> String { write_target("", fmt) } @@ -570,18 +661,20 @@ mod tests { fn format_with_header() { let mut f = formatter(); - let written = write(DefaultFormat { - timestamp: None, - module_path: true, - target: false, - level: true, - source_file: false, - source_line_number: false, - #[cfg(feature = "kv")] - kv_format: &hidden_kv_format, + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: true, + target: false, + level: true, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: None, + suffix: "\n", + }, written_header_value: false, - indent: None, - suffix: "\n", buf: &mut f, }); @@ -592,18 +685,20 @@ mod tests { fn format_no_header() { let mut f = formatter(); - let written = write(DefaultFormat { - timestamp: None, - module_path: false, - target: false, - level: false, - source_file: false, - source_line_number: false, - #[cfg(feature = "kv")] - kv_format: &hidden_kv_format, + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: false, + target: false, + level: false, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: None, + suffix: "\n", + }, written_header_value: false, - indent: None, - suffix: "\n", buf: &mut f, }); @@ -614,18 +709,20 @@ mod tests { fn format_indent_spaces() { let mut f = formatter(); - let written = write(DefaultFormat { - timestamp: None, - module_path: true, - target: false, - level: true, - source_file: false, - source_line_number: false, - #[cfg(feature = "kv")] - kv_format: &hidden_kv_format, + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: true, + target: false, + level: true, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: Some(4), + suffix: "\n", + }, written_header_value: false, - indent: Some(4), - suffix: "\n", buf: &mut f, }); @@ -636,18 +733,20 @@ mod tests { fn format_indent_zero_spaces() { let mut f = formatter(); - let written = write(DefaultFormat { - timestamp: None, - module_path: true, - target: false, - level: true, - source_file: false, - source_line_number: false, - #[cfg(feature = "kv")] - kv_format: &hidden_kv_format, + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: true, + target: false, + level: true, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: Some(0), + suffix: "\n", + }, written_header_value: false, - indent: Some(0), - suffix: "\n", buf: &mut f, }); @@ -658,18 +757,20 @@ mod tests { fn format_indent_spaces_no_header() { let mut f = formatter(); - let written = write(DefaultFormat { - timestamp: None, - module_path: false, - target: false, - level: false, - source_file: false, - source_line_number: false, - #[cfg(feature = "kv")] - kv_format: &hidden_kv_format, + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: false, + target: false, + level: false, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: Some(4), + suffix: "\n", + }, written_header_value: false, - indent: Some(4), - suffix: "\n", buf: &mut f, }); @@ -680,18 +781,20 @@ mod tests { fn format_suffix() { let mut f = formatter(); - let written = write(DefaultFormat { - timestamp: None, - module_path: false, - target: false, - level: false, - source_file: false, - source_line_number: false, - #[cfg(feature = "kv")] - kv_format: &hidden_kv_format, + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: false, + target: false, + level: false, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: None, + suffix: "\n\n", + }, written_header_value: false, - indent: None, - suffix: "\n\n", buf: &mut f, }); @@ -702,18 +805,20 @@ mod tests { fn format_suffix_with_indent() { let mut f = formatter(); - let written = write(DefaultFormat { - timestamp: None, - module_path: false, - target: false, - level: false, - source_file: false, - source_line_number: false, - #[cfg(feature = "kv")] - kv_format: &hidden_kv_format, + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: false, + target: false, + level: false, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: Some(4), + suffix: "\n\n", + }, written_header_value: false, - indent: Some(4), - suffix: "\n\n", buf: &mut f, }); @@ -726,18 +831,20 @@ mod tests { let written = write_target( "target", - DefaultFormat { - timestamp: None, - module_path: true, - target: true, - level: true, - source_file: false, - source_line_number: false, - #[cfg(feature = "kv")] - kv_format: &hidden_kv_format, + ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: true, + target: true, + level: true, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: None, + suffix: "\n", + }, written_header_value: false, - indent: None, - suffix: "\n", buf: &mut f, }, ); @@ -749,18 +856,20 @@ mod tests { fn format_empty_target() { let mut f = formatter(); - let written = write(DefaultFormat { - timestamp: None, - module_path: true, - target: true, - level: true, - source_file: false, - source_line_number: false, - #[cfg(feature = "kv")] - kv_format: &hidden_kv_format, + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: true, + target: true, + level: true, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: None, + suffix: "\n", + }, written_header_value: false, - indent: None, - suffix: "\n", buf: &mut f, }); @@ -773,18 +882,20 @@ mod tests { let written = write_target( "target", - DefaultFormat { - timestamp: None, - module_path: true, - target: false, - level: true, - source_file: false, - source_line_number: false, - #[cfg(feature = "kv")] - kv_format: &hidden_kv_format, + ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: true, + target: false, + level: true, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: None, + suffix: "\n", + }, written_header_value: false, - indent: None, - suffix: "\n", buf: &mut f, }, ); @@ -796,18 +907,20 @@ mod tests { fn format_with_source_file_and_line_number() { let mut f = formatter(); - let written = write(DefaultFormat { - timestamp: None, - module_path: false, - target: false, - level: true, - source_file: true, - source_line_number: true, - #[cfg(feature = "kv")] - kv_format: &hidden_kv_format, + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: false, + target: false, + level: true, + source_file: true, + source_line_number: true, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: None, + suffix: "\n", + }, written_header_value: false, - indent: None, - suffix: "\n", buf: &mut f, }); @@ -828,17 +941,19 @@ mod tests { let written = write_record( record, - DefaultFormat { - timestamp: None, - module_path: false, - target: false, - level: true, - source_file: false, - source_line_number: false, - kv_format: &default_kv_format, + ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: false, + target: false, + level: true, + source_file: false, + source_line_number: false, + kv_format: Some(Box::new(default_kv_format)), + indent: None, + suffix: "\n", + }, written_header_value: false, - indent: None, - suffix: "\n", buf: &mut f, }, ); @@ -863,17 +978,19 @@ mod tests { let written = write_record( record, - DefaultFormat { - timestamp: None, - module_path: true, - target: true, - level: true, - source_file: true, - source_line_number: true, - kv_format: &default_kv_format, + ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: true, + target: true, + level: true, + source_file: true, + source_line_number: true, + kv_format: Some(Box::new(default_kv_format)), + indent: None, + suffix: "\n", + }, written_header_value: false, - indent: None, - suffix: "\n", buf: &mut f, }, ); diff --git a/src/logger.rs b/src/logger.rs index ed8100d..bd4d13b 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -258,13 +258,13 @@ impl Builder { /// Whether or not to write the level in the default format. pub fn format_level(&mut self, write: bool) -> &mut Self { - self.format.format_level = write; + self.format.default_format.level(write); self } /// Whether or not to write the source file path in the default format. pub fn format_file(&mut self, write: bool) -> &mut Self { - self.format.format_file = write; + self.format.default_format.file(write); self } @@ -272,7 +272,7 @@ impl Builder { /// /// Only has effect if `format_file` is also enabled pub fn format_line_number(&mut self, write: bool) -> &mut Self { - self.format.format_line_number = write; + self.format.default_format.line_number(write); self } @@ -287,26 +287,26 @@ impl Builder { /// Whether or not to write the module path in the default format. pub fn format_module_path(&mut self, write: bool) -> &mut Self { - self.format.format_module_path = write; + self.format.default_format.module_path(write); self } /// Whether or not to write the target in the default format. pub fn format_target(&mut self, write: bool) -> &mut Self { - self.format.format_target = write; + self.format.default_format.target(write); self } /// Configures the amount of spaces to use to indent multiline log records. /// A value of `None` disables any kind of indentation. pub fn format_indent(&mut self, indent: Option) -> &mut Self { - self.format.format_indent = indent; + self.format.default_format.indent(indent); self } /// Configures if timestamp should be included and in what precision. pub fn format_timestamp(&mut self, timestamp: Option) -> &mut Self { - self.format.format_timestamp = timestamp; + self.format.default_format.timestamp(timestamp); self } @@ -332,7 +332,7 @@ impl Builder { /// Configures the end of line suffix. pub fn format_suffix(&mut self, suffix: &'static str) -> &mut Self { - self.format.format_suffix = suffix; + self.format.default_format.suffix(suffix); self } @@ -351,7 +351,7 @@ impl Builder { where F: Fn(&mut Formatter, &dyn log::kv::Source) -> io::Result<()> + Sync + Send + 'static, { - self.format.kv_format = Some(Box::new(format)); + self.format.default_format.key_values(format); self } @@ -664,8 +664,10 @@ impl Log for Logger { } let print = |formatter: &mut Formatter, record: &Record<'_>| { - let _ = - (self.format)(formatter, record).and_then(|_| formatter.print(&self.writer)); + let _ = self + .format + .format(formatter, record) + .and_then(|_| formatter.print(&self.writer)); // Always clear the buffer afterwards formatter.clear();