use log::Record;
use serde_json::json;
use time::format_description::well_known::Iso8601;
use time::OffsetDateTime;
pub enum RecordFormat {
Json,
Simple,
Custom(Box<dyn Sync + Send + Fn(&Record) -> String>),
}
pub struct LogSculptor {
pub record_format: RecordFormat,
}
impl LogSculptor {
pub fn new(record_format: RecordFormat) -> LogSculptor {
LogSculptor { record_format }
}
pub fn sculpt(&self, record: &Record) -> String {
let now = OffsetDateTime::now_utc()
.format(&Iso8601::DEFAULT)
.expect("Failed to format time as ISO 8601");
match &self.record_format {
RecordFormat::Json => json!({
"time": now,
"level": record.level(),
"target": record.target(),
"message": record.args(),
})
.to_string(),
RecordFormat::Simple => {
format!(
"{} [{}] {} - {}",
now,
record.target(),
record.level(),
record.args()
)
}
RecordFormat::Custom(f) => f(record),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use log::Level;
#[test]
fn sculpt_presets_return_non_empty() {
for fmt in vec![RecordFormat::Json, RecordFormat::Simple] {
let sculptor = LogSculptor::new(fmt);
let rec = Record::builder()
.args(format_args!("foo"))
.level(Level::Info)
.target("test")
.build();
assert!(!sculptor.sculpt(&rec).is_empty());
let rec = Record::builder()
.args(format_args!(""))
.level(Level::Info)
.target("")
.build();
assert!(!sculptor.sculpt(&rec).is_empty());
}
}
#[test]
fn sculpt_custom_formats_correctly() {
let test_cases = vec![
(RecordFormat::Custom(Box::new(|_| "".to_string())), ""),
(
RecordFormat::Custom(Box::new(|r| format!("{} {}", r.level(), r.args()))),
"INFO foo",
),
(
RecordFormat::Custom(Box::new(|r| {
format!("{} [{}] {}", r.level(), r.target(), r.args())
})),
"INFO [test] foo",
),
];
let rec = Record::builder()
.args(format_args!("foo"))
.level(Level::Info)
.target("test")
.build();
for (fmt, expected) in test_cases {
let sculptor = LogSculptor::new(fmt);
assert_eq!(sculptor.sculpt(&rec), expected);
}
}
}