#[macro_use]
extern crate serde_derive;
#[allow(unused_imports)]
#[macro_use]
extern crate serde_json;
extern crate chrono;
extern crate serde;
use chrono::prelude::*;
use serde_json::Value;
use std::collections::HashMap;
#[derive(Serialize, Deserialize)]
pub struct Entry {
#[serde(rename = "timestamp")]
time: DateTime<Utc>,
host: String,
app: String,
#[serde(rename = "type")]
msgtype: String,
msg: HashMap<String, Value>,
}
pub trait Formatter {
fn format(&self, _: Entry) {
panic!("No formatter specified");
}
fn format_mut(&mut self, entry: Entry) {
self.format(entry);
}
}
pub struct Logger<F>
where
F: Formatter,
{
formatter: F,
app: String,
host: String,
}
impl<F> Logger<F>
where
F: Formatter,
{
pub fn new(formatter: F, host: &str, app: &str) -> Logger<F> {
Logger {
formatter: formatter,
app: app.to_string(),
host: host.to_string(),
}
}
pub fn log(&self, msgtype: &str, msg: HashMap<String, Value>) {
let entry = Entry {
time: Utc::now(),
host: self.host.clone(),
app: self.app.clone(),
msgtype: msgtype.to_string(),
msg,
};
self.formatter.format(entry);
}
pub fn log_mut(&mut self, msgtype: &str, msg: HashMap<String, Value>) {
let entry = Entry {
time: Utc::now(),
host: self.host.clone(),
app: self.app.clone(),
msgtype: msgtype.to_string(),
msg,
};
self.formatter.format_mut(entry);
}
}
impl<F> Formatter for F
where
F: Fn(Entry),
{
fn format(&self, entry: Entry) {
(*self)(entry);
}
}
pub fn json_stdout(entry: Entry) {
match serde_json::to_string(&entry) {
Ok(s) => println!("{}", s),
Err(err) => println!("{{ error: \"{}\" }}", err.to_string()),
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn it_logs_a_message() {
let logger = Logger::new(json_stdout, "my-host", "my-app");
let mut msg: HashMap<String, Value> = HashMap::new();
msg.insert("field1".to_string(), json!("value1"));
msg.insert("field2".to_string(), json!(12.5));
logger.log("test-message", msg);
}
#[test]
fn it_formats_log_messages_correctly() {
let mut msg: HashMap<String, Value> = HashMap::new();
msg.insert("field1".to_string(), json!("value1"));
msg.insert("field2".to_string(), json!(12.5));
let time = Utc::now();
let entry = Entry {
time,
host: "a-host".to_string(),
app: "an-app".to_string(),
msgtype: "a-message".to_string(),
msg,
};
let js_str = serde_json::to_string(&entry).expect("how can json encoding fail?");
println!("[it_formats] {}", js_str);
match serde_json::from_str(&js_str) {
Err(err) => panic!(err),
Ok(Value::Object(obj)) => {
assert_eq!(obj.get("host"), Some(&json!("a-host")));
assert_eq!(obj.get("app"), Some(&json!("an-app")));
assert_eq!(obj.get("type"), Some(&json!("a-message")));
assert_eq!(
obj.get("msg").and_then(|o| o.get("field1")),
Some(&json!("value1"))
);
assert_eq!(
obj.get("msg").and_then(|o| o.get("field2")),
Some(&json!(12.5))
);
}
Ok(_) => panic!("json deserialized incorrectly"),
}
}
#[test]
fn it_can_decode_log_entries() {
let mut msg: HashMap<String, Value> = HashMap::new();
msg.insert("field1".to_string(), json!("value1"));
msg.insert("field2".to_string(), json!(12.5));
let time = Utc::now();
let entry = Entry {
time,
host: "a-host".to_string(),
app: "an-app".to_string(),
msgtype: "a-message".to_string(),
msg,
};
let js_str = serde_json::to_string(&entry).expect("how can json encoding fail?");
let res: Result<Entry, serde_json::Error> = serde_json::from_str(&js_str);
match res {
Ok(entry_) => {
assert_eq!(entry_.time, time);
assert_eq!(entry_.host, "a-host");
assert_eq!(entry_.app, "an-app");
assert_eq!(entry_.msgtype, "a-message");
assert_eq!(entry_.msg.get("field1"), Some(&json!("value1")));
assert_eq!(entry_.msg.get("field1"), Some(&json!("value1")));
assert_eq!(entry_.msg.get("field2"), Some(&json!(12.5)));
}
Err(error) => panic!(error),
}
}
struct CustomFormatter {
prefix: String,
output: Vec<String>,
}
impl Formatter for CustomFormatter {
fn format_mut(&mut self, entry: Entry) {
self.output
.push(format!("{}, {}", self.prefix, entry.time.to_rfc3339()));
}
}
#[test]
fn it_can_handle_a_custom_formatter() {
let msg: HashMap<String, Value> = HashMap::new();
let formatter = CustomFormatter {
prefix: String::from("a-prefix"),
output: Vec::new(),
};
let mut logger = Logger::new(formatter, "my-host", "my-app");
logger.log_mut("generic", msg);
println!("results: {:?}", logger.formatter.output);
assert_eq!(logger.formatter.output.len(), 1);
assert!(logger.formatter.output[0].starts_with("a-prefix,"));
}
}