From 0770042f305cbb6b4c980e424a144ec73286fa47 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Wed, 20 Nov 2019 22:59:56 +0100 Subject: [PATCH] Very basic logging infrastructure We can open a logfile and write things to it with a callsite. --- src/log.rs | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/src/log.rs b/src/log.rs index 31537eb19..4c6d58baf 100644 --- a/src/log.rs +++ b/src/log.rs @@ -1,4 +1,89 @@ -//! # Logging macros +//! # Logging support + +use std::fmt; +use std::io::prelude::*; +use std::path::PathBuf; + +/// A logger for a [Context]. +#[derive(Debug)] +pub struct Logger { + logdir: PathBuf, + logfile: std::ffi::OsString, + file_handle: std::fs::File, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum LogLevel { + Info, + Warning, + Error, +} + +impl fmt::Display for LogLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + LogLevel::Info => write!(f, "I"), + LogLevel::Warning => write!(f, "W"), + LogLevel::Error => write!(f, "E"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Callsite<'a> { + pub module: &'a str, + pub file: &'a str, + pub line: u32, +} + +impl fmt::Display for Callsite<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{} in {}", self.file, self.line, self.module) + } +} + +impl Logger { + pub fn new(logdir: PathBuf) -> Result { + let fname = format!( + "{}.log", + chrono::offset::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true) + ); + let file = std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(logdir.join(&fname))?; + Ok(Logger { + logdir, + logfile: fname.into(), + file_handle: file, + }) + } + + pub fn log( + &mut self, + level: LogLevel, + callsite: Callsite, + msg: &str, + ) -> Result<(), std::io::Error> { + write!(&mut self.file_handle, "{} [{}]: {}\n", level, callsite, msg) + } + + pub fn flush(&mut self) -> Result<(), std::io::Error> { + self.file_handle.flush() + } +} + +#[macro_export] +macro_rules! callsite { + () => { + $crate::log::Callsite { + module: module_path!(), + file: file!(), + line: line!(), + } + }; +} #[macro_export] macro_rules! info { @@ -39,3 +124,37 @@ macro_rules! emit_event { $ctx.call_cb($event); }; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_basic_logging() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path(); + let mut logger = Logger::new(dir.to_path_buf()).unwrap(); + logger.log(LogLevel::Info, callsite!(), "foo").unwrap(); + logger.log(LogLevel::Warning, callsite!(), "bar").unwrap(); + logger.log(LogLevel::Error, callsite!(), "baz").unwrap(); + logger.flush(); + let log = std::fs::read_to_string(logger.logdir.join(logger.logfile)).unwrap(); + println!("{}", log); + let lines: Vec<&str> = log.lines().collect(); + + assert!(lines[0].starts_with("I")); + assert!(lines[0].contains("src/log.rs")); + assert!(lines[0].contains("deltachat::log::tests")); + assert!(lines[0].contains("foo")); + + assert!(lines[1].starts_with("W")); + assert!(lines[1].contains("src/log.rs")); + assert!(lines[1].contains("deltachat::log::tests")); + assert!(lines[1].contains("bar")); + + assert!(lines[2].starts_with("E")); + assert!(lines[2].contains("src/log.rs")); + assert!(lines[2].contains("deltachat::log::tests")); + assert!(lines[2].contains("baz")); + } +}