1#![allow(clippy::tabs_in_doc_comments)]
2use slog::{o, Error, Key, OwnedKVList, Record, Value, KV};
25use std::borrow::Cow;
26use std::cell::RefCell;
27use std::fmt::Arguments;
28use std::io;
29
30pub enum Redaction {
32 Plain,
34
35 Skip,
37
38 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
64pub 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
85pub struct LogfmtBuilder<W: io::Write> {
87 io: W,
88 options: Options,
89}
90
91impl<W: io::Write> LogfmtBuilder<W> {
92 pub fn build(self) -> Logfmt<W> {
94 Logfmt {
95 io: RefCell::new(self.io),
96 options: self.options,
97 }
98 }
99
100 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 pub fn no_prefix(mut self) -> Self {
110 self.options.prefix = |_, _| Ok(());
111 self
112 }
113
114 pub fn redact(mut self, redact: fn(&Key) -> Redaction) -> Self {
121 self.options.redactor = redact;
122 self
123 }
124
125 pub fn print_msg(mut self, print: bool) -> Self {
129 self.options.print_msg = print;
130 self
131 }
132
133 pub fn print_level(mut self, print: bool) -> Self {
137 self.options.print_level = print;
138 self
139 }
140
141 pub fn print_tag(mut self, print: bool) -> Self {
145 self.options.print_tag = print;
146 self
147 }
148
149 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)] "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}