rs_utils/log/kv/
format.rs

1//! Custom env_logger formats
2
3use std;
4use env_logger;
5use log;
6
7pub struct EnvLoggerFormatConfig {
8  pub thread : bool,
9  pub target : bool,
10  pub file   : bool
11}
12
13/// Formats log messages as a json object string
14pub fn env_logger_json_formatter (config : EnvLoggerFormatConfig) -> impl
15  Fn(&mut env_logger::fmt::Formatter, &log::Record<'_>) -> std::io::Result<()>
16{
17  use std::io::Write;
18  #[derive(Default)]
19  struct KVVisitor(pub String);
20  impl <'kvs> log::kv::VisitSource <'kvs> for KVVisitor {
21    fn visit_pair (&mut self,
22      key : log::kv::Key <'kvs>, value : log::kv::Value <'kvs>
23    ) -> Result <(), log::kv::Error> {
24      self.0 += format!(",\"{}\":{}", key, serde_json::to_string(&value).unwrap())
25        .as_str();
26      Ok(())
27    }
28  }
29  move |buf : &mut env_logger::fmt::Formatter, record : &log::Record|{
30    let thread_string = if config.thread {
31      std::thread::current().name()
32        .map (|name| format!(",\"thread\":\"{}\"", name))
33        .unwrap_or_else (||
34          format!(",\"thread\":\"{:?}\"",
35            std::thread::current().id()).replace ("ThreadId", "unnamed"))
36    } else {
37      "".to_string()
38    };
39    let target_string = if config.target {
40      format!(",\"target\":\"{}\"", record.target())
41    } else {
42      "".to_string()
43    };
44    let file_string = if config.file {
45      format!(",\"file\":\"{}:{}\"",
46        record.file().unwrap(), record.line().unwrap())
47    } else {
48      "".to_string()
49    };
50    let mut kvv = KVVisitor::default();
51    record.key_values().visit (&mut kvv).unwrap();
52    writeln!(buf,
53      "{{\"ts\":\"{}\",\"level\":\"{}\"{}{}{},\"msg\":\"{}\"{}}}",
54      buf.timestamp(), record.level(), thread_string, target_string,
55      file_string, record.args(), kvv.0
56    )
57  }
58}
59
60/// A custom `env_logger` format adding thread, target, and file information:
61/// ```text
62/// <ts> <level> <thread> <target> <file>: <msg> [ <key>=<value>]
63/// ```
64pub fn env_logger_custom_formatter (config : EnvLoggerFormatConfig) -> impl
65  Fn(&mut env_logger::fmt::Formatter, &log::Record<'_>) -> std::io::Result<()>
66{
67  use std::io::Write;
68  #[derive(Default)]
69  struct KVVisitor(pub String);
70  impl <'kvs> log::kv::VisitSource <'kvs> for KVVisitor {
71    fn visit_pair (&mut self,
72      key : log::kv::Key <'kvs>, value : log::kv::Value <'kvs>
73    ) -> Result <(), log::kv::Error> {
74      let mut value_string = serde_json::to_string (&value).unwrap();
75      let mut value_str = value_string.as_str();
76      if !value_str.contains (char::is_whitespace) {
77        // if there is no whitespace we can remove outer quotes and un-escape
78        // any internal quotes
79        value_string = value_str.replace("\\\"", "\"");
80        value_str = if value_string.chars().next() == Some('"') {
81          &value_string[1..value_string.len()-1]
82        } else {
83          &value_string[..]
84        };
85      }
86      self.0 += format!(" {}={}", key, value_str).as_str();
87      Ok(())
88    }
89  }
90  move |buf : &mut env_logger::fmt::Formatter, record : &log::Record|{
91    let mut kvv = KVVisitor::default();
92    record.key_values().visit (&mut kvv).unwrap();
93    let kvs = if kvv.0.is_empty() {
94      "".to_string()
95    } else {
96      format!(" {}", kvv.0)
97    };
98    if !config.file && !config.thread && !config.target {
99      let level_string = format!("{}:", record.level());
100      writeln!(buf,
101        "{} {:6} {}{}", buf.timestamp(), level_string, record.args(), kvs)
102    } else {
103      let thread_string = if config.thread {
104        std::thread::current().name().map (|name| format!(" {}", name))
105          .unwrap_or_else (||
106            format!(" {:?}",
107              std::thread::current().id()).replace ("ThreadId", "unnamed"))
108      } else {
109        "".to_string()
110      };
111      let target_string = if config.target {
112        format!(" {}", record.target())
113      } else {
114        "".to_string()
115      };
116      let file_string = if config.file {
117        format!(" {}:{}", record.file().unwrap(), record.line().unwrap())
118      } else {
119        "".to_string()
120      };
121      writeln!(buf,
122        "{} {:5}{}{}{}: {}{}",
123        buf.timestamp(), record.level(), thread_string, target_string,
124        file_string, record.args(), kvs)
125    }
126  }
127}
128
129impl Default for EnvLoggerFormatConfig {
130  fn default() -> Self {
131    EnvLoggerFormatConfig {
132      thread: true,
133      target: true,
134      file:   true
135    }
136  }
137}