//! Logging hooks for logfile output use super::*; use std::{ path::{ PathBuf, Path, }, sync::Arc, io::{ self, ErrorKind, }, }; use tokio::{ prelude::*, sync::{ mpsc, }, task, fs::{OpenOptions,File,}, }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Internal { stdout: PathBuf, stderr: PathBuf, use_local_time: bool, } #[derive(Debug)] struct Command { level: Level, trace: Trace, timestamp: Date, message: String, is_err: bool, } #[derive(Debug)] /// Logging hook that appends to logfiles pub struct LogFileHook { mask: Level, internal: Arc, sender: Option>, } impl Internal { //funcs we might both want to use fn valid_perms(&self) -> readable_perms::Permissions { //TODO: Add field for this readable_perms::Permissions::new() .add_mask(readable_perms::User::Owner, readable_perms::Bit::Read | readable_perms::Bit::Write) .add_mask(readable_perms::User::Group, readable_perms::Bit::Read) .add_mask(readable_perms::User::Owner, readable_perms::Bit::None) } /// Fix permissions on this file to what we want. fn fix_perms(&self, file: &mut tokio::fs::File) { use readable_perms::*; if let Err(e) = file.chmod(self.valid_perms()) { crate::warn!("Error fixing logfile permissions: {}", e); } } } impl LogFileHook { /// Create a new log file hook pub fn new(mask: L, path: P, error: E, use_local_time: bool) -> Self where L: Into, P: AsRef, E: AsRef { Self { mask: mask.into(), internal: Arc::new(Internal{ stderr: error.as_ref().to_owned(), stdout: path.as_ref().to_owned(), use_local_time, }), sender: None, } } } async fn write_log(file: &mut File, com: Command) -> std::io::Result<()> { let output = format!("{} <{}> [{}]: {}\n", com.timestamp, com.trace, com.level, com.message); file.write(output.as_bytes()).await?; Ok(()) } const BACKLOG: usize = 100; impl Hook for LogFileHook { fn initialise(&mut self, _: &Index) { let (tx, mut rx) = mpsc::channel(BACKLOG); let internal = Arc::clone(&self.internal); self.sender = Some(tx); task::spawn(async move { let (stdout, stderr) = (Path::new(&internal.stdout), Path::new(&internal.stderr)); while let Some(command) = rx.recv().await { let path = if command.is_err {stderr} else {stdout}; let mut file = match OpenOptions::new() .append(true) .create(true) .write(true) .open(path).await { Ok(file) => file, Err(err) => { crate::warn!("Could not open logfile {:?} for writing: {}", path, err); continue; }, }; internal.fix_perms(&mut file); if let Err(err) = write_log(&mut file, command).await { crate::warn!("Failed writing to logfile {:?}: {}", path, err); } } }); } fn finalise(&mut self, _: Index) { } fn println(&mut self, level: &dyn AsLevel, what: &dyn Display, trace: &Trace) -> io::Result<()> { let com = Command { timestamp: if self.internal.use_local_time {Date::now_local()} else {Date::now_utc()}, is_err: level.is_err(), trace: trace.clone(), message: what.to_string(), level: level.borrow().clone(), }; if let Some(sender) = &mut self.sender { if let Err(e) = sender.try_send(com) { return Err(io::Error::new(ErrorKind::BrokenPipe, format!("Failed to pass to worker: {}", e))); } } Ok(()) } }