Skip to main content

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