You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

168 lines
3.5 KiB

//! 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<Internal>,
sender: Option<mpsc::Sender<Command>>,
}
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<L,P,E>(mask: L, path: P, error: E, use_local_time: bool) -> Self
where L: Into<Level>,
P: AsRef<Path>,
E: AsRef<Path>
{
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(())
}
}