Skip to content

Commit

Permalink
feat(kv): Change KV format to allow arbitrary function.
Browse files Browse the repository at this point in the history
  • Loading branch information
tmccombs committed Jan 23, 2024
1 parent 5277afa commit 66a66cf
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 136 deletions.
45 changes: 45 additions & 0 deletions src/fmt/kv.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use std::io::{self, Write};

use super::Formatter;
use log::kv::{source::Source, Error, Key, Value, Visitor};

/// Format function for serializing key/value pairs
///
/// This trait determines how key/value pairs for structured logs are serialized within the default
/// format.
pub(crate) type KvFormatFn = dyn Fn(&mut Formatter, &dyn Source) -> io::Result<()> + Sync + Send;

/// Null Key Value Format
///
/// This function is intended to be passed to [`env_logger::Builder::format_key_values`].
///
/// This key value format simply ignores any key/value fields and doesn't include them in the
/// output.
pub fn hidden_kv_format(_formatter: &mut Formatter, _fields: &dyn Source) -> io::Result<()> {
Ok(())
}

/// Defualt Key Value Format
///
/// This function is intended to be passed to [`env_logger::Builder::format_key_values`].
///
/// This is the default key/value format. Which uses an "=" as the separator between the key and
/// value and a " " between each pair.
///
/// For example: `ip=127.0.0.1 port=123456 path=/example`
pub fn default_kv_format(formatter: &mut Formatter, fields: &dyn Source) -> io::Result<()> {
fields
.visit(&mut DefaultVisitor(formatter))
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
}

struct DefaultVisitor<'a>(&'a mut Formatter);

impl<'a, 'kvs> Visitor<'kvs> for DefaultVisitor<'a> {
fn visit_pair(&mut self, key: Key, value: Value<'kvs>) -> Result<(), Error> {
// TODO: add styling
// tracing-subscriber uses italic for the key and dimmed for the =
write!(self.0, " {}={}", key, value)?;
Ok(())
}
}
160 changes: 26 additions & 134 deletions src/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,18 @@ use std::io::prelude::*;
use std::rc::Rc;
use std::{fmt, io, mem};

#[cfg(feature = "unstable-kv")]
use log::kv;
use log::Record;

#[cfg(feature = "humantime")]
mod humantime;
#[cfg(feature = "unstable-kv")]
mod kv;
pub(crate) mod writer;

#[cfg(feature = "humantime")]
pub use self::humantime::Timestamp;
#[cfg(feature = "unstable-kv")]
pub use self::kv::*;
pub use self::writer::glob::*;

use self::writer::{Buffer, Writer};
Expand Down Expand Up @@ -200,7 +202,7 @@ pub(crate) struct Builder {
pub custom_format: Option<FormatFn>,
pub format_suffix: &'static str,
#[cfg(feature = "unstable-kv")]
pub key_value_style: KVStyle,
pub kv_format: Option<Box<KvFormatFn>>,
built: bool,
}

Expand Down Expand Up @@ -234,7 +236,7 @@ impl Builder {
indent: built.format_indent,
suffix: built.format_suffix,
#[cfg(feature = "unstable-kv")]
key_value_style: built.key_value_style,
kv_format: built.kv_format.as_deref().unwrap_or(&default_kv_format),
buf,
};

Expand All @@ -255,7 +257,7 @@ impl Default for Builder {
custom_format: None,
format_suffix: "\n",
#[cfg(feature = "unstable-kv")]
key_value_style: KVStyle::Multiline,
kv_format: None,
built: false,
}
}
Expand All @@ -279,7 +281,7 @@ struct DefaultFormat<'a> {
buf: &'a mut Formatter,
suffix: &'a str,
#[cfg(feature = "unstable-kv")]
key_value_style: KVStyle,
kv_format: &'a KvFormatFn,
}

impl<'a> DefaultFormat<'a> {
Expand Down Expand Up @@ -454,55 +456,8 @@ impl<'a> DefaultFormat<'a> {

#[cfg(feature = "unstable-kv")]
fn write_kv(&mut self, record: &Record) -> io::Result<()> {
match self.key_value_style {
KVStyle::Hidden => Ok(()),
KVStyle::Multiline => record
.key_values()
.visit(&mut KVMultilineVisitor(self))
.map_err(|e| io::Error::new(io::ErrorKind::Other, e)),
KVStyle::Inline => {
let kvs = record.key_values();
if kvs.count() > 0 {
write!(self.buf, " {}", self.subtle_style("{"))?;

kvs.visit(&mut KVInlineVisitor(self.buf))
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
write!(self.buf, "{}", self.subtle_style(" }"))?;
}
Ok(())
}
}
}
}

#[cfg(feature = "unstable-kv")]
struct KVInlineVisitor<'a>(&'a mut Formatter);

#[cfg(feature = "unstable-kv")]
impl<'a, 'kvs> kv::Visitor<'kvs> for KVInlineVisitor<'a> {
fn visit_pair(&mut self, key: kv::Key<'kvs>, value: kv::Value<'kvs>) -> Result<(), kv::Error> {
write!(self.0, " {}={}", key, value).map_err(|e| e.into())
}
}

#[cfg(feature = "unstable-kv")]
struct KVMultilineVisitor<'a, 'fmt>(&'a mut DefaultFormat<'fmt>);

#[cfg(feature = "unstable-kv")]
impl<'a, 'kvs, 'fmt> kv::Visitor<'kvs> for KVMultilineVisitor<'a, 'fmt> {
fn visit_pair(&mut self, key: kv::Key<'kvs>, value: kv::Value<'kvs>) -> Result<(), kv::Error> {
let indent = self.0.indent.unwrap_or(0);
write!(
self.0.buf,
// Always use a newline here, because suffix could be something else
// to separate entire records.
"\n{:width$}{}: {}",
"",
key,
value,
width = indent
)?;
Ok(())
let format = self.kv_format;
format(self.buf, record.key_values())
}
}

Expand Down Expand Up @@ -557,7 +512,7 @@ mod tests {
target: false,
level: true,
#[cfg(feature = "unstable-kv")]
key_value_style: KVStyle::Hidden,
kv_format: &hidden_kv_format,
written_header_value: false,
indent: None,
suffix: "\n",
Expand All @@ -577,7 +532,7 @@ mod tests {
target: false,
level: false,
#[cfg(feature = "unstable-kv")]
key_value_style: KVStyle::Hidden,
kv_format: &hidden_kv_format,
written_header_value: false,
indent: None,
suffix: "\n",
Expand All @@ -597,7 +552,7 @@ mod tests {
target: false,
level: true,
#[cfg(feature = "unstable-kv")]
key_value_style: KVStyle::Hidden,
kv_format: &hidden_kv_format,
written_header_value: false,
indent: Some(4),
suffix: "\n",
Expand All @@ -617,7 +572,7 @@ mod tests {
target: false,
level: true,
#[cfg(feature = "unstable-kv")]
key_value_style: KVStyle::Hidden,
kv_format: &hidden_kv_format,
written_header_value: false,
indent: Some(0),
suffix: "\n",
Expand All @@ -637,7 +592,7 @@ mod tests {
target: false,
level: false,
#[cfg(feature = "unstable-kv")]
key_value_style: KVStyle::Hidden,
kv_format: &hidden_kv_format,
written_header_value: false,
indent: Some(4),
suffix: "\n",
Expand All @@ -657,7 +612,7 @@ mod tests {
target: false,
level: false,
#[cfg(feature = "unstable-kv")]
key_value_style: KVStyle::Hidden,
kv_format: &hidden_kv_format,
written_header_value: false,
indent: None,
suffix: "\n\n",
Expand All @@ -677,7 +632,7 @@ mod tests {
target: false,
level: false,
#[cfg(feature = "unstable-kv")]
key_value_style: KVStyle::Hidden,
kv_format: &hidden_kv_format,
written_header_value: false,
indent: Some(4),
suffix: "\n\n",
Expand All @@ -699,7 +654,7 @@ mod tests {
target: true,
level: true,
#[cfg(feature = "unstable-kv")]
key_value_style: KVStyle::Hidden,
kv_format: &hidden_kv_format,
written_header_value: false,
indent: None,
suffix: "\n",
Expand All @@ -720,7 +675,7 @@ mod tests {
target: true,
level: true,
#[cfg(feature = "unstable-kv")]
key_value_style: KVStyle::Hidden,
kv_format: &hidden_kv_format,
written_header_value: false,
indent: None,
suffix: "\n",
Expand All @@ -742,7 +697,7 @@ mod tests {
target: false,
level: true,
#[cfg(feature = "unstable-kv")]
key_value_style: KVStyle::Hidden,
kv_format: &hidden_kv_format,
written_header_value: false,
indent: None,
suffix: "\n",
Expand All @@ -755,7 +710,7 @@ mod tests {

#[cfg(feature = "unstable-kv")]
#[test]
fn format_kv_trailer() {
fn format_kv_default() {
let kvs = &[("a", 1u32), ("b", 2u32)][..];
let mut f = formatter();
let record = Record::builder()
Expand All @@ -772,20 +727,20 @@ mod tests {
module_path: false,
target: false,
level: true,
key_value_style: KVStyle::Inline,
kv_format: &default_kv_format,
written_header_value: false,
indent: None,
suffix: "\n",
buf: &mut f,
},
);

assert_eq!("[INFO ] log message { a=1 b=2 }\n", written);
assert_eq!("[INFO ] log message a=1 b=2\n", written);
}

#[cfg(feature = "unstable-kv")]
#[test]
fn format_kv_trailer_full() {
fn format_kv_default_full() {
let kvs = &[("a", 1u32), ("b", 2u32)][..];
let mut f = formatter();
let record = Record::builder()
Expand All @@ -805,77 +760,14 @@ mod tests {
module_path: true,
target: true,
level: true,
key_value_style: KVStyle::Inline,
kv_format: &default_kv_format,
written_header_value: false,
indent: None,
suffix: "\n",
buf: &mut f,
},
);

assert_eq!(
"[INFO test::path target] log\nmessage { a=1 b=2 }\n",
written
);
}

#[cfg(feature = "unstable-kv")]
#[test]
fn format_kv_multiline() {
let kvs = &[("a", 1u32), ("b", 2u32)][..];
let mut f = formatter();
let record = Record::builder()
.args(format_args!("log\nmessage"))
.level(Level::Info)
.module_path(Some("test::path"))
.key_values(&kvs)
.build();

let written = write_record(
record,
DefaultFormat {
timestamp: None,
module_path: false,
target: false,
level: true,
key_value_style: KVStyle::Multiline,
written_header_value: false,
indent: None,
suffix: "\n",
buf: &mut f,
},
);

assert_eq!("[INFO ] log\nmessage\na: 1\nb: 2\n", written);
}

#[cfg(feature = "unstable-kv")]
#[test]
fn format_kv_multiline_indented() {
let kvs = &[("a", 1u32), ("b", 2u32)][..];
let mut f = formatter();
let record = Record::builder()
.args(format_args!("log\nmessage"))
.level(Level::Info)
.module_path(Some("test::path"))
.key_values(&kvs)
.build();

let written = write_record(
record,
DefaultFormat {
timestamp: None,
module_path: false,
target: false,
level: true,
key_value_style: KVStyle::Multiline,
written_header_value: false,
indent: Some(2),
suffix: "\n",
buf: &mut f,
},
);

assert_eq!("[INFO ] log\n message\n a: 1\n b: 2\n", written);
assert_eq!("[INFO test::path target] log\nmessage a=1 b=2\n", written);
}
}
7 changes: 5 additions & 2 deletions src/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,11 @@ impl Builder {

/// Configure the style for writing key/value pairs
#[cfg(feature = "unstable-kv")]
pub fn format_key_value_style(&mut self, style: fmt::KVStyle) -> &mut Self {
self.format.key_value_style = style;
pub fn format_key_values<F: 'static>(&mut self, format: F) -> &mut Self
where
F: Fn(&mut Formatter, &dyn log::kv::source::Source) -> io::Result<()> + Sync + Send,
{
self.format.kv_format = Some(Box::new(format));
self
}

Expand Down

0 comments on commit 66a66cf

Please sign in to comment.