use crate::no_color_support::style;
use ansi_term::Colour;
use handlebars::Handlebars;
use maplit::btreemap;
use std::borrow::ToOwned;
use std::collections::BTreeMap;
use std::io::Write;
pub struct LogSettings {
pub message_keys: Vec<String>,
pub time_keys: Vec<String>,
pub level_keys: Vec<String>,
pub additional_values: Vec<String>,
pub dump_all: bool,
pub inspect: bool,
pub with_prefix: bool,
}
impl LogSettings {
pub fn new_default_settings() -> LogSettings {
LogSettings {
message_keys: vec!["short_message".to_string(), "msg".to_string(), "message".to_string()],
time_keys: vec!["timestamp".to_string(), "time".to_string(), "@timestamp".to_string()],
level_keys: vec!["level".to_string(), "severity".to_string(), "log.level".to_string(), "loglevel".to_string()],
additional_values: vec![],
dump_all: false,
inspect: false,
with_prefix: false,
}
}
pub fn add_additional_values(&mut self, mut additional_values: Vec<String>) {
self.additional_values.append(&mut additional_values);
}
pub fn add_message_keys(&mut self, mut message_keys: Vec<String>) {
message_keys.append(&mut self.message_keys);
self.message_keys = message_keys;
}
pub fn add_time_keys(&mut self, mut time_keys: Vec<String>) {
time_keys.append(&mut self.time_keys);
self.time_keys = time_keys;
}
pub fn add_level_keys(&mut self, mut level_keys: Vec<String>) {
level_keys.append(&mut self.level_keys);
self.level_keys = level_keys;
}
}
pub fn print_log_line(
out: &mut dyn Write,
maybe_prefix: Option<&str>,
log_entry: &BTreeMap<String, String>,
log_settings: &LogSettings,
handlebars: &Handlebars<'static>,
) {
let level = get_string_value_or_default(log_entry, &log_settings.level_keys, "unknown");
let formatted_prefix = maybe_prefix.map(|p| format!(" {}", p)).unwrap_or_else(|| "".to_owned());
let message = get_string_value_or_default(log_entry, &log_settings.message_keys, "");
let timestamp = get_string_value_or_default(log_entry, &log_settings.time_keys, "");
let mut handle_bar_input: BTreeMap<String, String> = BTreeMap::new();
handle_bar_input.clone_from(log_entry);
handle_bar_input.insert("fblog_timestamp".to_string(), timestamp);
handle_bar_input.insert("fblog_level".to_string(), level);
handle_bar_input.insert("fblog_message".to_string(), message);
handle_bar_input.insert("fblog_prefix".to_string(), formatted_prefix);
let write_result = match handlebars.render("main_line", &handle_bar_input) {
Ok(string) => writeln!(out, "{}", string),
Err(e) => writeln!(out, "{} Failed to process line: {}", style(&Colour::Red.bold(), "??? >"), e),
};
if write_result.is_err() {
std::process::exit(14);
}
if log_settings.dump_all {
let all_values: Vec<String> = log_entry.keys().map(ToOwned::to_owned).collect();
write_additional_values(out, log_entry, &all_values, handlebars);
} else {
write_additional_values(out, log_entry, &log_settings.additional_values, handlebars);
}
}
fn get_string_value(value: &BTreeMap<String, String>, keys: &[String]) -> Option<String> {
keys
.iter()
.fold(None::<String>, |maybe_match, key| maybe_match.or_else(|| value.get(key).map(ToOwned::to_owned)))
}
fn get_string_value_or_default(value: &BTreeMap<String, String>, keys: &[String], default: &str) -> String {
get_string_value(value, keys).unwrap_or_else(|| default.to_string())
}
fn write_additional_values(out: &mut dyn Write, log_entry: &BTreeMap<String, String>, additional_values: &[String], handlebars: &Handlebars<'static>) {
for additional_value in additional_values {
if let Some(value) = get_string_value(log_entry, &[additional_value.to_string()]) {
let variables = btreemap! {"key".to_string() =>additional_value.to_string(), "value".to_string() => value.to_string()};
let write_result = match handlebars.render("additional_value", &variables) {
Ok(string) => writeln!(out, "{}", string),
Err(e) => writeln!(out, "{} Failed to process additional value: {}", style(&Colour::Red.bold(), " ??? >"), e),
};
if write_result.is_err() {
std::process::exit(14);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::template;
use maplit::btreemap;
use regex::Regex;
fn fblog_handlebar_registry_default_format() -> Handlebars<'static> {
let main_line_format = template::DEFAULT_MAIN_LINE_FORMAT.to_string();
let additional_value_format = template::DEFAULT_ADDITIONAL_VALUE_FORMAT.to_string();
template::fblog_handlebar_registry(main_line_format, additional_value_format)
}
fn out_to_string(out: Vec<u8>) -> String {
let regex = Regex::new("\u{001B}\\[[\\d;]*[^\\d;]").expect("Regex should be valid");
let out_with_style = String::from_utf8_lossy(&out).into_owned();
let result = regex.replace_all(&out_with_style, "").into_owned();
result
}
#[test]
fn write_log_entry() {
let handlebars = fblog_handlebar_registry_default_format();
let log_settings = LogSettings::new_default_settings();
let mut out: Vec<u8> = Vec::new();
let log_entry: BTreeMap<String, String> = btreemap! {"message".to_string() => "something happend".to_string(),
"time".to_string() => "2017-07-06T15:21:16".to_string(),
"process".to_string() => "rust".to_string(),
"level".to_string() => "info".to_string()};
print_log_line(&mut out, None, &log_entry, &log_settings, &handlebars);
assert_eq!(out_to_string(out), "2017-07-06T15:21:16 INFO: something happend\n");
}
#[test]
fn write_log_entry_with_prefix() {
let handlebars = fblog_handlebar_registry_default_format();
let log_settings = LogSettings::new_default_settings();
let mut out: Vec<u8> = Vec::new();
let prefix = "abc";
let log_entry: BTreeMap<String, String> = btreemap! {"message".to_string() => "something happend".to_string(),
"time".to_string() => "2017-07-06T15:21:16".to_string(),
"process".to_string() => "rust".to_string(),
"level".to_string() => "info".to_string()};
print_log_line(&mut out, Some(prefix), &log_entry, &log_settings, &handlebars);
assert_eq!(out_to_string(out), "2017-07-06T15:21:16 INFO: abc something happend\n");
}
#[test]
fn write_log_entry_with_additional_field() {
let handlebars = fblog_handlebar_registry_default_format();
let mut out: Vec<u8> = Vec::new();
let log_entry: BTreeMap<String, String> = btreemap! {"message".to_string() => "something happend".to_string(),
"time".to_string() => "2017-07-06T15:21:16".to_string(),
"process".to_string() => "rust".to_string(),
"fu".to_string() => "bower".to_string(),
"level".to_string() => "info".to_string()};
let mut log_settings = LogSettings::new_default_settings();
log_settings.add_additional_values(vec!["process".to_string(), "fu".to_string()]);
print_log_line(&mut out, None, &log_entry, &log_settings, &handlebars);
assert_eq!(
out_to_string(out),
"\
2017-07-06T15:21:16 INFO: something happend
process: rust
fu: bower
"
);
}
#[test]
fn write_log_entry_with_additional_field_and_prefix() {
let handlebars = fblog_handlebar_registry_default_format();
let mut out: Vec<u8> = Vec::new();
let log_entry: BTreeMap<String, String> = btreemap! {"message".to_string() => "something happend".to_string(),
"time".to_string() => "2017-07-06T15:21:16".to_string(),
"process".to_string() => "rust".to_string(),
"fu".to_string() => "bower".to_string(),
"level".to_string() => "info".to_string()};
let prefix = "abc";
let mut log_settings = LogSettings::new_default_settings();
log_settings.add_additional_values(vec!["process".to_string(), "fu".to_string()]);
print_log_line(&mut out, Some(prefix), &log_entry, &log_settings, &handlebars);
assert_eq!(
out_to_string(out),
"\
2017-07-06T15:21:16 INFO: abc something happend
process: rust
fu: bower
"
);
}
#[test]
fn write_log_entry_dump_all() {
let handlebars = fblog_handlebar_registry_default_format();
let mut out: Vec<u8> = Vec::new();
let log_entry: BTreeMap<String, String> = btreemap! {"message".to_string() => "something happend".to_string(),
"time".to_string() => "2017-07-06T15:21:16".to_string(),
"process".to_string() => "rust".to_string(),
"fu".to_string() => "bower".to_string(),
"level".to_string() => "info".to_string()};
let mut log_settings = LogSettings::new_default_settings();
log_settings.dump_all = true;
print_log_line(&mut out, None, &log_entry, &log_settings, &handlebars);
assert_eq!(
out_to_string(out),
"\
2017-07-06T15:21:16 INFO: something happend
fu: bower
level: info
message: something happend
process: rust
time: 2017-07-06T15:21:16
"
);
}
#[test]
fn write_log_entry_with_exotic_fields() {
let handlebars = fblog_handlebar_registry_default_format();
let mut log_settings = LogSettings::new_default_settings();
let mut out: Vec<u8> = Vec::new();
let log_entry: BTreeMap<String, String> = btreemap! {"message".to_string() => "something happend".to_string(),
"time".to_string() => "2017-07-06T15:21:16".to_string(),
"process".to_string() => "rust".to_string(),
"moep".to_string() => "moep".to_string(),
"hugo".to_string() => "hugo".to_string(),
"level".to_string() => "info".to_string()};
log_settings.add_message_keys(vec!["process".to_string()]);
log_settings.add_time_keys(vec!["moep".to_string()]);
log_settings.add_level_keys(vec!["hugo".to_string()]);
print_log_line(&mut out, None, &log_entry, &log_settings, &handlebars);
assert_eq!(out_to_string(out), " moep HUGO: rust\n");
}
}