1#![warn(missing_docs)]
23
24use slog::{o, FnValue, Level, Record};
25
26use std::io;
27
28const DEFAULT_NAME: &str = "slog-rs";
29
30fn get_hostname() -> String {
31 match hostname::get() {
32 Ok(h) => h.to_string_lossy().into_owned(),
33 Err(_) => "n/a".to_string(),
34 }
35}
36
37enum BunyanLevel {
38 Fatal = 60,
39 Error = 50,
40 Warn = 40,
41 Info = 30,
42 Debug = 20,
43 Trace = 10,
44}
45
46impl From<Level> for BunyanLevel {
47 fn from(l: Level) -> Self {
48 match l {
49 Level::Critical => BunyanLevel::Fatal,
50 Level::Error => BunyanLevel::Error,
51 Level::Warning => BunyanLevel::Warn,
52 Level::Info => BunyanLevel::Info,
53 Level::Debug => BunyanLevel::Debug,
54 Level::Trace => BunyanLevel::Trace,
55 }
56 }
57}
58
59fn new_with_ts_fn<F, W>(name: &'static str, io: W, ts_f: F) -> slog_json::JsonBuilder<W>
60where
61 F: Fn(&Record) -> Option<String> + Send + Sync + std::panic::RefUnwindSafe + 'static,
62 W: io::Write,
63{
64 slog_json::Json::new(io).add_key_value(o!(
65 "pid" => ::std::process::id(),
66 "hostname" => get_hostname(),
67 "time" => FnValue(ts_f),
68 "level" => FnValue(|rinfo : &Record| {
69 BunyanLevel::from(rinfo.level()) as i8
70 }),
71 "name" => name,
72 "v" => 0usize,
73 "msg" => FnValue(|rinfo : &Record| {
74 rinfo.msg().to_string()
75 })
76 ))
77}
78
79pub fn new<W>(io: W) -> slog_json::JsonBuilder<W>
81where
82 W: io::Write,
83{
84 with_name(DEFAULT_NAME, io)
85}
86
87pub fn default<W>(io: W) -> slog_json::Json<W>
89where
90 W: io::Write,
91{
92 with_name(DEFAULT_NAME, io).build()
93}
94
95pub fn with_name<W>(name: &'static str, io: W) -> slog_json::JsonBuilder<W>
99where
100 W: io::Write,
101{
102 new_with_ts_fn(name, io, |_: &Record| {
103 time::OffsetDateTime::now_local()
104 .unwrap_or_else(|_| time::OffsetDateTime::now_utc())
105 .format(&time::format_description::well_known::Rfc3339)
106 .ok()
107 })
108}
109
110#[cfg(test)]
111mod test {
112 use super::get_hostname;
113 use super::new_with_ts_fn;
114 use super::DEFAULT_NAME;
115 use slog::{b, o};
116 use slog::{Drain, Level, Logger};
117 use slog::{Record, RecordLocation, RecordStatic};
118 use std::io;
119 use std::sync::{Arc, Mutex};
120 use time::macros::datetime;
121
122 struct V(Arc<Mutex<Vec<u8>>>);
123
124 impl io::Write for V {
125 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
126 self.0.lock().unwrap().write(buf)
127 }
128 fn flush(&mut self) -> io::Result<()> {
129 self.0.lock().unwrap().flush()
130 }
131 }
132
133 #[test]
134 fn trivial() {
135 let v = Arc::new(Mutex::new(vec![]));
136 {
137 let v = V(v.clone());
138 let drain = new_with_ts_fn(DEFAULT_NAME, v, |_: &Record| {
139 datetime!(2014-07-08 09:10:11 UTC)
140 .format(&time::format_description::well_known::Rfc3339)
141 .ok()
142 })
143 .build();
144
145 let rs = RecordStatic {
146 level: Level::Info,
147 location: &RecordLocation {
148 file: "filepath",
149 line: 11192,
150 column: 0,
151 function: "",
152 module: "modulepath",
153 },
154 tag: "target",
155 };
156
157 let log = Logger::root(Mutex::new(drain).fuse(), o!());
158 log.log(&Record::new(&rs, &format_args!("message"), b!()));
159 }
160
161 assert_eq!(
162 String::from_utf8_lossy(&(*v).lock().unwrap()),
163 "{".to_string()
164 + "\"msg\":\"message\","
165 + "\"v\":0,"
166 + "\"name\":\"slog-rs\","
167 + "\"level\":30,"
168 + "\"time\":\"2014-07-08T09:10:11Z\","
169 + "\"hostname\":\""
170 + &get_hostname()
171 + "\","
172 + "\"pid\":"
173 + &::std::process::id().to_string()
174 + "}\n"
175 );
176 }
177
178 #[test]
179 fn custom_name_field() {
180 let v = Arc::new(Mutex::new(vec![]));
181 {
182 let v = V(v.clone());
183 let name = "test-name-123";
184 let drain = new_with_ts_fn(name, v, |_: &Record| {
185 datetime!(2014-07-08 09:10:11 UTC)
186 .format(&time::format_description::well_known::Rfc3339)
187 .ok()
188 })
189 .build();
190
191 let rs = RecordStatic {
192 level: Level::Info,
193 location: &RecordLocation {
194 file: "filepath",
195 line: 11192,
196 column: 0,
197 function: "",
198 module: "modulepath",
199 },
200 tag: "target",
201 };
202
203 let log = Logger::root(Mutex::new(drain).fuse(), o!());
204 log.log(&Record::new(&rs, &format_args!("message"), b!()));
205 }
206
207 assert_eq!(
208 String::from_utf8_lossy(&(*v).lock().unwrap()),
209 "{".to_string()
210 + "\"msg\":\"message\","
211 + "\"v\":0,"
212 + "\"name\":\"test-name-123\","
213 + "\"level\":30,"
214 + "\"time\":\"2014-07-08T09:10:11Z\","
215 + "\"hostname\":\""
216 + &get_hostname()
217 + "\","
218 + "\"pid\":"
219 + &::std::process::id().to_string()
220 + "}\n"
221 );
222 }
223}