slog_logfmt/
lib.rs

1#![allow(clippy::tabs_in_doc_comments)]
2//! `slog_logfmt` - a [`logfmt`](https://brandur.org/logfmt) formatter for slog.
3//!
4//! This crate exposes a `slog` drain that formats messages as logfmt.
5//!
6//! # Example
7//! ```rust
8//! use slog_logfmt::Logfmt;
9//! use slog::{debug, o, Drain, Logger};
10//! use std::io::stdout;
11//!
12//! let drain = Logfmt::new(stdout()).build().fuse();
13//! let drain = slog_async::Async::new(drain).build().fuse();
14//! let logger = Logger::root(drain, o!("logger" => "tests"));
15//! debug!(logger, #"tag", "hi there"; "foo" => "bar'baz\"");
16//! ```
17//!
18//! Writes:
19//! ```text
20//! DEBG | #tag	hi there	logger="tests" foo="bar\'baz\""
21//! ```
22//!
23
24use slog::{o, Error, Key, OwnedKVList, Record, Value, KV};
25use std::borrow::Cow;
26use std::cell::RefCell;
27use std::fmt::Arguments;
28use std::io;
29
30/// A decision on whether to print a key/value pair.
31pub enum Redaction {
32    /// Print the value as-is.
33    Plain,
34
35    /// Do not print the entry at all.
36    Skip,
37
38    /// Redact the value with the given function.
39    Redact(fn(&'_ dyn Value) -> Arguments),
40}
41
42struct Options {
43    prefix: fn(&mut dyn io::Write, &Record) -> slog::Result,
44    print_level: bool,
45    print_msg: bool,
46    print_tag: bool,
47    force_quotes: bool,
48    redactor: fn(&Key) -> Redaction,
49}
50
51impl Default for Options {
52    fn default() -> Self {
53        Options {
54            prefix: default_prefix,
55            print_level: false,
56            print_msg: false,
57            print_tag: false,
58            force_quotes: false,
59            redactor: |_| Redaction::Plain,
60        }
61    }
62}
63
64/// A drain & formatter for [logfmt](https://brandur.org/logfmt)-formatted messages.
65///
66/// # Format
67/// The default format looks like the somewhat-more-human-readable
68/// format in https://brandur.org/logfmt#human. You can customize it
69/// with the [`LogfmtBuilder`] method `set_prefix`.
70pub struct Logfmt<W: io::Write> {
71    io: RefCell<W>,
72    options: Options,
73}
74
75impl<W: io::Write> Logfmt<W> {
76    #[allow(clippy::new_ret_no_self)]
77    pub fn new(io: W) -> LogfmtBuilder<W> {
78        LogfmtBuilder {
79            io,
80            options: Default::default(),
81        }
82    }
83}
84
85/// A constructor for a [`Logfmt`] drain.
86pub struct LogfmtBuilder<W: io::Write> {
87    io: W,
88    options: Options,
89}
90
91impl<W: io::Write> LogfmtBuilder<W> {
92    /// Constructs the drain.
93    pub fn build(self) -> Logfmt<W> {
94        Logfmt {
95            io: RefCell::new(self.io),
96            options: self.options,
97        }
98    }
99
100    /// Set a function that prints a (not necessarily
101    /// logfmt-formatted) prefix to the output stream.
102    pub fn set_prefix(mut self, prefix: fn(&mut dyn io::Write, &Record) -> slog::Result) -> Self {
103        self.options.prefix = prefix;
104        self
105    }
106
107    /// Sets the logger up to print no prefix, effectively starting the line entirely
108    /// logfmt field-formatted.
109    pub fn no_prefix(mut self) -> Self {
110        self.options.prefix = |_, _| Ok(());
111        self
112    }
113
114    /// Sets a function that makes decisions on whether to log a field.
115    ///
116    /// This function must return a [`Redaction`] result, which has
117    /// two variants at the moment: `Redact::Skip` to not log the
118    /// field, and `Redact::Plain` to log the field value in plain
119    /// text.
120    pub fn redact(mut self, redact: fn(&Key) -> Redaction) -> Self {
121        self.options.redactor = redact;
122        self
123    }
124
125    /// Choose whether to print the log message.
126    ///
127    /// The default prefix already prints it, so the default is to skip.
128    pub fn print_msg(mut self, print: bool) -> Self {
129        self.options.print_msg = print;
130        self
131    }
132
133    /// Choose whether to print the log level.
134    ///
135    /// The default prefix already prints it, so the default is to skip.
136    pub fn print_level(mut self, print: bool) -> Self {
137        self.options.print_level = print;
138        self
139    }
140
141    /// Choose whether to print the log level.
142    ///
143    /// The default prefix already prints it, so the default is to skip.
144    pub fn print_tag(mut self, print: bool) -> Self {
145        self.options.print_tag = print;
146        self
147    }
148
149    /// Force quoting field values even if they don't contain quotable characters.
150    ///
151    /// Setting this option will surround values with quotes like `foo="bar"`.
152    pub fn force_quotes(mut self) -> Self {
153        self.options.force_quotes = true;
154        self
155    }
156}
157
158fn default_prefix(io: &mut dyn io::Write, rec: &Record) -> slog::Result {
159    let tag_prefix = if rec.tag() == "" { "" } else { "#" };
160    let tag_suffix = if rec.tag() == "" { "" } else { "\t" };
161    write!(
162        io,
163        "{level} | {tag_prefix}{tag}{tag_suffix}{msg}\t",
164        tag_prefix = tag_prefix,
165        tag = rec.tag(),
166        tag_suffix = tag_suffix,
167        level = rec.level().as_short_str(),
168        msg = rec.msg()
169    )?;
170    Ok(())
171}
172
173struct LogfmtSerializer<'a, W: io::Write> {
174    io: &'a mut W,
175    first: bool,
176    force_quotes: bool,
177    redactor: fn(&Key) -> Redaction,
178}
179
180impl<'a, W: io::Write> LogfmtSerializer<'a, W> {
181    fn next_field(&mut self) -> Result<(), io::Error> {
182        if self.first {
183            self.first = false;
184        } else {
185            write!(self.io, " ")?;
186        }
187        Ok(())
188    }
189}
190
191macro_rules! w(
192    ($s:expr, $k:expr, $v:expr) => {{
193        use Redaction::*;
194
195        let redact = $s.redactor;
196        let val = $v;
197        match redact(&$k) {
198            Skip => {return Ok(());}
199            Plain => {
200                $s.next_field()?;
201                write!($s.io, "{}={}", $k, val)?;
202                Ok(())
203            },
204            Redact(redactor) => {
205                $s.next_field()?;
206                let val = format!("{}", redactor(&val));
207                write!($s.io, "{}={}", $k, optionally_quote(&val, $s.force_quotes))?;
208                Ok(())
209            }
210        }
211    }};
212);
213
214fn can_skip_quoting(ch: char) -> bool {
215    (ch >= 'a' && ch <= 'z')
216        || (ch >= 'A' && ch <= 'Z')
217        || (ch >= '0' && ch <= '9')
218        || ch == '-'
219        || ch == '.'
220        || ch == '_'
221        || ch == '/'
222        || ch == '@'
223        || ch == '^'
224        || ch == '+'
225}
226
227fn optionally_quote(input: &str, force: bool) -> Cow<str> {
228    if !force && input.chars().all(can_skip_quoting) {
229        input.into()
230    } else {
231        format!("{:?}", input).into()
232    }
233}
234
235impl<'a, W> slog::Serializer for LogfmtSerializer<'a, W>
236where
237    W: io::Write,
238{
239    fn emit_usize(&mut self, key: slog::Key, val: usize) -> Result<(), Error> {
240        w!(self, key, val)
241    }
242
243    fn emit_isize(&mut self, key: slog::Key, val: isize) -> Result<(), Error> {
244        w!(self, key, val)
245    }
246
247    fn emit_bool(&mut self, key: slog::Key, val: bool) -> Result<(), Error> {
248        w!(self, key, val)
249    }
250
251    fn emit_char(&mut self, key: slog::Key, val: char) -> Result<(), Error> {
252        w!(self, key, val)
253    }
254
255    fn emit_u8(&mut self, key: slog::Key, val: u8) -> Result<(), Error> {
256        w!(self, key, val)
257    }
258
259    fn emit_i8(&mut self, key: slog::Key, val: i8) -> Result<(), Error> {
260        w!(self, key, val)
261    }
262
263    fn emit_u16(&mut self, key: slog::Key, val: u16) -> Result<(), Error> {
264        w!(self, key, val)
265    }
266
267    fn emit_i16(&mut self, key: slog::Key, val: i16) -> Result<(), Error> {
268        w!(self, key, val)
269    }
270
271    fn emit_u32(&mut self, key: slog::Key, val: u32) -> Result<(), Error> {
272        w!(self, key, val)
273    }
274
275    fn emit_i32(&mut self, key: slog::Key, val: i32) -> Result<(), Error> {
276        w!(self, key, val)
277    }
278
279    fn emit_f32(&mut self, key: slog::Key, val: f32) -> Result<(), Error> {
280        w!(self, key, val)
281    }
282
283    fn emit_u64(&mut self, key: slog::Key, val: u64) -> Result<(), Error> {
284        w!(self, key, val)
285    }
286
287    fn emit_i64(&mut self, key: slog::Key, val: i64) -> Result<(), Error> {
288        w!(self, key, val)
289    }
290
291    fn emit_f64(&mut self, key: slog::Key, val: f64) -> Result<(), Error> {
292        w!(self, key, val)
293    }
294
295    fn emit_u128(&mut self, key: slog::Key, val: u128) -> Result<(), Error> {
296        w!(self, key, val)
297    }
298
299    fn emit_i128(&mut self, key: slog::Key, val: i128) -> Result<(), Error> {
300        w!(self, key, val)
301    }
302
303    fn emit_str(&mut self, key: slog::Key, val: &str) -> Result<(), Error> {
304        let val = optionally_quote(val, self.force_quotes);
305        w!(self, key, &*val)
306    }
307
308    fn emit_unit(&mut self, key: slog::Key) -> Result<(), Error> {
309        w!(self, key, "()")
310    }
311
312    fn emit_none(&mut self, key: slog::Key) -> Result<(), Error> {
313        w!(self, key, "None")
314    }
315
316    fn emit_arguments<'b>(&mut self, key: slog::Key, val: &Arguments<'b>) -> Result<(), Error> {
317        let val = format!("{}", val);
318        let val = optionally_quote(&val, self.force_quotes);
319        w!(self, key, &*val)
320    }
321}
322
323impl<W> slog::Drain for Logfmt<W>
324where
325    W: io::Write,
326{
327    type Ok = ();
328    type Err = io::Error;
329
330    fn log<'a>(
331        &self,
332        record: &Record<'a>,
333        logger_values: &OwnedKVList,
334    ) -> Result<Self::Ok, Self::Err> {
335        let mut io = self.io.borrow_mut();
336        let prefix = self.options.prefix;
337        prefix(&mut *io, record)?;
338
339        let mut serializer = LogfmtSerializer {
340            io: &mut *io,
341            first: true,
342            force_quotes: self.options.force_quotes,
343            redactor: self.options.redactor,
344        };
345        if self.options.print_level {
346            let lvl = o!("level" => record.level().as_short_str());
347            lvl.serialize(record, &mut serializer)?;
348        }
349        if self.options.print_msg {
350            record.msg().serialize(
351                record,
352                #[allow(clippy::identity_conversion)] // necessary for dynamic-keys
353                "msg".into(),
354                &mut serializer,
355            )?;
356        }
357        if self.options.print_tag {
358            let tag = o!("level" => record.tag());
359            tag.serialize(record, &mut serializer)?;
360        }
361        logger_values.serialize(record, &mut serializer)?;
362        record.kv().serialize(record, &mut serializer)?;
363
364        io.write_all(b"\n")?;
365        io.flush()?;
366
367        Ok(())
368    }
369}