#![allow(clippy::tabs_in_doc_comments)]
use slog::{o, Error, Key, OwnedKVList, Record, Value, KV};
use std::borrow::Cow;
use std::cell::RefCell;
use std::fmt::Arguments;
use std::io;
pub enum Redaction {
Plain,
Skip,
Redact(fn(&'_ dyn Value) -> Arguments),
}
struct Options {
prefix: fn(&mut dyn io::Write, &Record) -> slog::Result,
print_level: bool,
print_msg: bool,
print_tag: bool,
force_quotes: bool,
redactor: fn(&Key) -> Redaction,
}
impl Default for Options {
fn default() -> Self {
Options {
prefix: default_prefix,
print_level: false,
print_msg: false,
print_tag: false,
force_quotes: false,
redactor: |_| Redaction::Plain,
}
}
}
pub struct Logfmt<W: io::Write> {
io: RefCell<W>,
options: Options,
}
impl<W: io::Write> Logfmt<W> {
#[allow(clippy::new_ret_no_self)]
pub fn new(io: W) -> LogfmtBuilder<W> {
LogfmtBuilder {
io,
options: Default::default(),
}
}
}
pub struct LogfmtBuilder<W: io::Write> {
io: W,
options: Options,
}
impl<W: io::Write> LogfmtBuilder<W> {
pub fn build(self) -> Logfmt<W> {
Logfmt {
io: RefCell::new(self.io),
options: self.options,
}
}
pub fn set_prefix(mut self, prefix: fn(&mut dyn io::Write, &Record) -> slog::Result) -> Self {
self.options.prefix = prefix;
self
}
pub fn no_prefix(mut self) -> Self {
self.options.prefix = |_, _| Ok(());
self
}
pub fn redact(mut self, redact: fn(&Key) -> Redaction) -> Self {
self.options.redactor = redact;
self
}
pub fn print_msg(mut self, print: bool) -> Self {
self.options.print_msg = print;
self
}
pub fn print_level(mut self, print: bool) -> Self {
self.options.print_level = print;
self
}
pub fn print_tag(mut self, print: bool) -> Self {
self.options.print_tag = print;
self
}
pub fn force_quotes(mut self) -> Self {
self.options.force_quotes = true;
self
}
}
fn default_prefix(io: &mut dyn io::Write, rec: &Record) -> slog::Result {
let tag_prefix = if rec.tag() == "" { "" } else { "#" };
let tag_suffix = if rec.tag() == "" { "" } else { "\t" };
write!(
io,
"{level} | {tag_prefix}{tag}{tag_suffix}{msg}\t",
tag_prefix = tag_prefix,
tag = rec.tag(),
tag_suffix = tag_suffix,
level = rec.level().as_short_str(),
msg = rec.msg()
)?;
Ok(())
}
struct LogfmtSerializer<'a, W: io::Write> {
io: &'a mut W,
first: bool,
force_quotes: bool,
redactor: fn(&Key) -> Redaction,
}
impl<'a, W: io::Write> LogfmtSerializer<'a, W> {
fn next_field(&mut self) -> Result<(), io::Error> {
if self.first {
self.first = false;
} else {
write!(self.io, " ")?;
}
Ok(())
}
}
macro_rules! w(
($s:expr, $k:expr, $v:expr) => {{
use Redaction::*;
let redact = $s.redactor;
let val = $v;
match redact(&$k) {
Skip => {return Ok(());}
Plain => {
$s.next_field()?;
write!($s.io, "{}={}", $k, val)?;
Ok(())
},
Redact(redactor) => {
$s.next_field()?;
let val = format!("{}", redactor(&val));
write!($s.io, "{}={}", $k, optionally_quote(&val, $s.force_quotes))?;
Ok(())
}
}
}};
);
fn can_skip_quoting(ch: char) -> bool {
(ch >= 'a' && ch <= 'z')
|| (ch >= 'A' && ch <= 'Z')
|| (ch >= '0' && ch <= '9')
|| ch == '-'
|| ch == '.'
|| ch == '_'
|| ch == '/'
|| ch == '@'
|| ch == '^'
|| ch == '+'
}
fn optionally_quote(input: &str, force: bool) -> Cow<str> {
if !force && input.chars().all(can_skip_quoting) {
input.into()
} else {
format!("{:?}", input).into()
}
}
impl<'a, W> slog::Serializer for LogfmtSerializer<'a, W>
where
W: io::Write,
{
fn emit_usize(&mut self, key: slog::Key, val: usize) -> Result<(), Error> {
w!(self, key, val)
}
fn emit_isize(&mut self, key: slog::Key, val: isize) -> Result<(), Error> {
w!(self, key, val)
}
fn emit_bool(&mut self, key: slog::Key, val: bool) -> Result<(), Error> {
w!(self, key, val)
}
fn emit_char(&mut self, key: slog::Key, val: char) -> Result<(), Error> {
w!(self, key, val)
}
fn emit_u8(&mut self, key: slog::Key, val: u8) -> Result<(), Error> {
w!(self, key, val)
}
fn emit_i8(&mut self, key: slog::Key, val: i8) -> Result<(), Error> {
w!(self, key, val)
}
fn emit_u16(&mut self, key: slog::Key, val: u16) -> Result<(), Error> {
w!(self, key, val)
}
fn emit_i16(&mut self, key: slog::Key, val: i16) -> Result<(), Error> {
w!(self, key, val)
}
fn emit_u32(&mut self, key: slog::Key, val: u32) -> Result<(), Error> {
w!(self, key, val)
}
fn emit_i32(&mut self, key: slog::Key, val: i32) -> Result<(), Error> {
w!(self, key, val)
}
fn emit_f32(&mut self, key: slog::Key, val: f32) -> Result<(), Error> {
w!(self, key, val)
}
fn emit_u64(&mut self, key: slog::Key, val: u64) -> Result<(), Error> {
w!(self, key, val)
}
fn emit_i64(&mut self, key: slog::Key, val: i64) -> Result<(), Error> {
w!(self, key, val)
}
fn emit_f64(&mut self, key: slog::Key, val: f64) -> Result<(), Error> {
w!(self, key, val)
}
fn emit_u128(&mut self, key: slog::Key, val: u128) -> Result<(), Error> {
w!(self, key, val)
}
fn emit_i128(&mut self, key: slog::Key, val: i128) -> Result<(), Error> {
w!(self, key, val)
}
fn emit_str(&mut self, key: slog::Key, val: &str) -> Result<(), Error> {
let val = optionally_quote(val, self.force_quotes);
w!(self, key, &*val)
}
fn emit_unit(&mut self, key: slog::Key) -> Result<(), Error> {
w!(self, key, "()")
}
fn emit_none(&mut self, key: slog::Key) -> Result<(), Error> {
w!(self, key, "None")
}
fn emit_arguments<'b>(&mut self, key: slog::Key, val: &Arguments<'b>) -> Result<(), Error> {
let val = format!("{}", val);
let val = optionally_quote(&val, self.force_quotes);
w!(self, key, &*val)
}
}
impl<W> slog::Drain for Logfmt<W>
where
W: io::Write,
{
type Ok = ();
type Err = io::Error;
fn log<'a>(
&self,
record: &Record<'a>,
logger_values: &OwnedKVList,
) -> Result<Self::Ok, Self::Err> {
let mut io = self.io.borrow_mut();
let prefix = self.options.prefix;
prefix(&mut *io, record)?;
let mut serializer = LogfmtSerializer {
io: &mut *io,
first: true,
force_quotes: self.options.force_quotes,
redactor: self.options.redactor,
};
if self.options.print_level {
let lvl = o!("level" => record.level().as_short_str());
lvl.serialize(record, &mut serializer)?;
}
if self.options.print_msg {
record.msg().serialize(
record,
#[allow(clippy::identity_conversion)]
"msg".into(),
&mut serializer,
)?;
}
if self.options.print_tag {
let tag = o!("level" => record.tag());
tag.serialize(record, &mut serializer)?;
}
logger_values.serialize(record, &mut serializer)?;
record.kv().serialize(record, &mut serializer)?;
io.write_all(b"\n")?;
io.flush()?;
Ok(())
}
}