mirror of
https://github.com/chatmail/core.git
synced 2026-05-19 06:46:32 +03:00
Implement reopening of logfiles
When the file reaches more than 4Mb a new one will be opened.
This commit is contained in:
115
src/log.rs
115
src/log.rs
@@ -1,15 +1,20 @@
|
|||||||
//! # Logging support
|
//! # Logging support
|
||||||
|
|
||||||
|
use std::io;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fs;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
/// A logger for a [Context].
|
/// A logger for a [Context].
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Logger {
|
pub struct Logger {
|
||||||
logdir: PathBuf,
|
logdir: PathBuf,
|
||||||
logfile: std::ffi::OsString,
|
logfile: String,
|
||||||
file_handle: std::fs::File,
|
file_handle: fs::File,
|
||||||
|
max_files: u32,
|
||||||
|
max_filesize: usize,
|
||||||
|
bytes_written: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -43,30 +48,72 @@ impl fmt::Display for Callsite<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Logger {
|
impl Logger {
|
||||||
pub fn new(logdir: PathBuf) -> Result<Logger, std::io::Error> {
|
pub fn new(logdir: PathBuf) -> Result<Logger, io::Error> {
|
||||||
let fname = format!(
|
let (fname, file) = Self::open(&logdir)?;
|
||||||
"{}.log",
|
let max_files = 5;
|
||||||
chrono::offset::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
|
Self::prune(&logdir, max_files);
|
||||||
);
|
|
||||||
let file = std::fs::OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.truncate(true)
|
|
||||||
.open(logdir.join(&fname))?;
|
|
||||||
Ok(Logger {
|
Ok(Logger {
|
||||||
logdir,
|
logdir,
|
||||||
logfile: fname.into(),
|
logfile: fname,
|
||||||
file_handle: file,
|
file_handle: file,
|
||||||
|
max_files,
|
||||||
|
max_filesize: 4 * 1024 * 1024, // 4 Mb
|
||||||
|
bytes_written: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens a new logfile, returning a tuple of (file_name, file_handle).
|
||||||
|
///
|
||||||
|
/// This tries to create a new logfile based on the current time,
|
||||||
|
/// creating .0.log, .1.log etc if this file already exists (up to 32).
|
||||||
|
fn open(logdir: &Path) -> Result<(String, fs::File), io::Error> {
|
||||||
|
let basename =
|
||||||
|
chrono::offset::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
|
||||||
|
let mut fname = format!("{}.log", &basename);
|
||||||
|
let mut counter = 0;
|
||||||
|
loop {
|
||||||
|
match std::fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create_new(true)
|
||||||
|
.open(logdir.join(&fname))
|
||||||
|
{
|
||||||
|
Ok(file) => {
|
||||||
|
return Ok((fname, file));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
if counter >= 32 {
|
||||||
|
return Err(e);
|
||||||
|
} else {
|
||||||
|
counter += 1;
|
||||||
|
fname = format!("{}.{}.log", &basename, counter);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cleans up old logfiles.
|
||||||
|
fn prune(logdir: &Path, max_files: u32) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
pub fn log(
|
pub fn log(
|
||||||
&mut self,
|
&mut self,
|
||||||
level: LogLevel,
|
level: LogLevel,
|
||||||
callsite: Callsite,
|
callsite: Callsite,
|
||||||
msg: &str,
|
msg: &str,
|
||||||
) -> Result<(), std::io::Error> {
|
) -> Result<(), std::io::Error> {
|
||||||
write!(&mut self.file_handle, "{} [{}]: {}\n", level, callsite, msg)
|
if self.bytes_written > self.max_filesize {
|
||||||
|
let (fname, handle) = Self::open(&self.logdir)?;
|
||||||
|
self.logfile = fname;
|
||||||
|
self.file_handle = handle;
|
||||||
|
Self::prune(&self.logdir, self.max_files);
|
||||||
|
}
|
||||||
|
let msg = format!("{} [{}]: {}\n", level, callsite, msg);
|
||||||
|
self.file_handle.write_all(msg.as_bytes())?;
|
||||||
|
self.bytes_written += msg.len();
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flush(&mut self) -> Result<(), std::io::Error> {
|
pub fn flush(&mut self) -> Result<(), std::io::Error> {
|
||||||
@@ -137,8 +184,8 @@ mod tests {
|
|||||||
logger.log(LogLevel::Info, callsite!(), "foo").unwrap();
|
logger.log(LogLevel::Info, callsite!(), "foo").unwrap();
|
||||||
logger.log(LogLevel::Warning, callsite!(), "bar").unwrap();
|
logger.log(LogLevel::Warning, callsite!(), "bar").unwrap();
|
||||||
logger.log(LogLevel::Error, callsite!(), "baz").unwrap();
|
logger.log(LogLevel::Error, callsite!(), "baz").unwrap();
|
||||||
logger.flush();
|
logger.flush().unwrap();
|
||||||
let log = std::fs::read_to_string(logger.logdir.join(logger.logfile)).unwrap();
|
let log = fs::read_to_string(logger.logdir.join(logger.logfile)).unwrap();
|
||||||
println!("{}", log);
|
println!("{}", log);
|
||||||
let lines: Vec<&str> = log.lines().collect();
|
let lines: Vec<&str> = log.lines().collect();
|
||||||
|
|
||||||
@@ -157,4 +204,38 @@ mod tests {
|
|||||||
assert!(lines[2].contains("deltachat::log::tests"));
|
assert!(lines[2].contains("deltachat::log::tests"));
|
||||||
assert!(lines[2].contains("baz"));
|
assert!(lines[2].contains("baz"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_reopen_logfile() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let dir = tmp.path();
|
||||||
|
let mut logger = Logger::new(dir.to_path_buf()).unwrap();
|
||||||
|
logger.max_filesize = 5;
|
||||||
|
|
||||||
|
let fname0 = logger.logfile.clone();
|
||||||
|
assert!(fname0.ends_with(".log"));
|
||||||
|
logger
|
||||||
|
.log(LogLevel::Info, callsite!(), "more than 5 bytes are written")
|
||||||
|
.unwrap();
|
||||||
|
logger.log(LogLevel::Info, callsite!(), "2nd msg").unwrap();
|
||||||
|
let fname1 = logger.logfile.clone();
|
||||||
|
assert!(fname1.ends_with(".1.log"));
|
||||||
|
assert_ne!(fname0, fname1);
|
||||||
|
let log0 = fs::read_to_string(logger.logdir.join(&fname0)).unwrap();
|
||||||
|
assert!(log0.contains("more than 5 bytes are written"));
|
||||||
|
let log1 = fs::read_to_string(logger.logdir.join(&fname1)).unwrap();
|
||||||
|
assert!(log1.contains("2nd msg"));
|
||||||
|
|
||||||
|
let mut count = 0;
|
||||||
|
loop {
|
||||||
|
if count > 40 {
|
||||||
|
assert!(false, "Failed to find error");
|
||||||
|
}
|
||||||
|
count += 1;
|
||||||
|
match logger.log(LogLevel::Info, callsite!(), "more reopens please") {
|
||||||
|
Ok(_) => continue,
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user