1#[macro_use]
27extern crate serde_derive;
28#[allow(unused_imports)]
29#[macro_use]
30extern crate serde_json;
31
32extern crate chrono;
33extern crate serde;
34
35use chrono::prelude::*;
36use serde_json::Value;
37use std::collections::HashMap;
38
39#[derive(Serialize, Deserialize)]
46pub struct Entry {
47 #[serde(rename = "timestamp")]
48 time: DateTime<Utc>,
49 host: String,
50 app: String,
51 #[serde(rename = "type")]
52 msgtype: String,
53 msg: HashMap<String, Value>,
54}
55
56pub trait Formatter {
64 fn format(&self, _: Entry) {
65 panic!("No formatter specified");
66 }
67
68 fn format_mut(&mut self, entry: Entry) {
69 self.format(entry);
70 }
71}
72
73pub struct Logger<F>
75where
76 F: Formatter,
77{
78 formatter: F,
79 app: String,
80 host: String,
81}
82
83impl<F> Logger<F>
84where
85 F: Formatter,
86{
87 pub fn new(formatter: F, host: &str, app: &str) -> Logger<F> {
91 Logger {
92 formatter: formatter,
93 app: app.to_string(),
94 host: host.to_string(),
95 }
96 }
97
98 pub fn log(&self, msgtype: &str, msg: HashMap<String, Value>) {
101 let entry = Entry {
102 time: Utc::now(),
103 host: self.host.clone(),
104 app: self.app.clone(),
105 msgtype: msgtype.to_string(),
106 msg,
107 };
108 self.formatter.format(entry);
109 }
110
111 pub fn log_mut(&mut self, msgtype: &str, msg: HashMap<String, Value>) {
113 let entry = Entry {
114 time: Utc::now(),
115 host: self.host.clone(),
116 app: self.app.clone(),
117 msgtype: msgtype.to_string(),
118 msg,
119 };
120 self.formatter.format_mut(entry);
121 }
122}
123
124impl<F> Formatter for F
125where
126 F: Fn(Entry),
127{
128 fn format(&self, entry: Entry) {
129 (*self)(entry);
130 }
131}
132
133pub fn json_stdout(entry: Entry) {
135 match serde_json::to_string(&entry) {
136 Ok(s) => println!("{}", s),
137 Err(err) => println!("{{ error: \"{}\" }}", err.to_string()),
138 }
139}
140
141#[cfg(test)]
142mod test {
143 use super::*;
144
145 #[test]
146 fn it_logs_a_message() {
147 let logger = Logger::new(json_stdout, "my-host", "my-app");
148 let mut msg: HashMap<String, Value> = HashMap::new();
149 msg.insert("field1".to_string(), json!("value1"));
150 msg.insert("field2".to_string(), json!(12.5));
151 logger.log("test-message", msg);
152 }
153
154 #[test]
155 fn it_formats_log_messages_correctly() {
156 let mut msg: HashMap<String, Value> = HashMap::new();
157 msg.insert("field1".to_string(), json!("value1"));
158 msg.insert("field2".to_string(), json!(12.5));
159
160 let time = Utc::now();
161
162 let entry = Entry {
163 time,
164 host: "a-host".to_string(),
165 app: "an-app".to_string(),
166 msgtype: "a-message".to_string(),
167 msg,
168 };
169
170 let js_str = serde_json::to_string(&entry).expect("how can json encoding fail?");
171 println!("[it_formats] {}", js_str);
172 match serde_json::from_str(&js_str) {
174 Err(err) => panic!(err),
175 Ok(Value::Object(obj)) => {
176 assert_eq!(obj.get("host"), Some(&json!("a-host")));
180 assert_eq!(obj.get("app"), Some(&json!("an-app")));
181 assert_eq!(obj.get("type"), Some(&json!("a-message")));
182 assert_eq!(
183 obj.get("msg").and_then(|o| o.get("field1")),
184 Some(&json!("value1"))
185 );
186 assert_eq!(
187 obj.get("msg").and_then(|o| o.get("field2")),
188 Some(&json!(12.5))
189 );
190 }
191 Ok(_) => panic!("json deserialized incorrectly"),
192 }
193 }
194
195 #[test]
196 fn it_can_decode_log_entries() {
197 let mut msg: HashMap<String, Value> = HashMap::new();
198 msg.insert("field1".to_string(), json!("value1"));
199 msg.insert("field2".to_string(), json!(12.5));
200
201 let time = Utc::now();
202
203 let entry = Entry {
204 time,
205 host: "a-host".to_string(),
206 app: "an-app".to_string(),
207 msgtype: "a-message".to_string(),
208 msg,
209 };
210
211 let js_str = serde_json::to_string(&entry).expect("how can json encoding fail?");
212
213 let res: Result<Entry, serde_json::Error> = serde_json::from_str(&js_str);
214 match res {
215 Ok(entry_) => {
216 assert_eq!(entry_.time, time);
217 assert_eq!(entry_.host, "a-host");
218 assert_eq!(entry_.app, "an-app");
219 assert_eq!(entry_.msgtype, "a-message");
220 assert_eq!(entry_.msg.get("field1"), Some(&json!("value1")));
221 assert_eq!(entry_.msg.get("field1"), Some(&json!("value1")));
222 assert_eq!(entry_.msg.get("field2"), Some(&json!(12.5)));
223 }
224 Err(error) => panic!(error),
225 }
226 }
227
228 struct CustomFormatter {
229 prefix: String,
230 output: Vec<String>,
231 }
232
233 impl Formatter for CustomFormatter {
234 fn format_mut(&mut self, entry: Entry) {
235 self.output
236 .push(format!("{}, {}", self.prefix, entry.time.to_rfc3339()));
237 }
238 }
239
240 #[test]
241 fn it_can_handle_a_custom_formatter() {
242 let msg: HashMap<String, Value> = HashMap::new();
243
244 let formatter = CustomFormatter {
245 prefix: String::from("a-prefix"),
246 output: Vec::new(),
247 };
248 let mut logger = Logger::new(formatter, "my-host", "my-app");
249 logger.log_mut("generic", msg);
250 println!("results: {:?}", logger.formatter.output);
251 assert_eq!(logger.formatter.output.len(), 1);
252 assert!(logger.formatter.output[0].starts_with("a-prefix,"));
253 }
254}