mirror of
https://git.proxmox.com/git/proxmox-backup
synced 2025-05-24 14:50:50 +00:00

writing to a file can explode quite easily. time formatting to rfc3339 should be more robust, but it has a few conditions where it could fail, so catch that too (and only really do it if required). The writes to stdout are left as is, it normally is redirected to journal which is in memory, and thus breaks later than most stuff, and at that point we probably do not care anymore anyway. It could make sense to actually return a result here.. Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
142 lines
4.5 KiB
Rust
142 lines
4.5 KiB
Rust
use anyhow::Error;
|
|
use std::io::Write;
|
|
|
|
/// Log messages with optional automatically added timestamps into files
|
|
///
|
|
/// Logs messages to file, and optionally to standard output.
|
|
///
|
|
///
|
|
/// #### Example:
|
|
/// ```
|
|
/// #[macro_use] extern crate proxmox_backup;
|
|
/// # use anyhow::{bail, format_err, Error};
|
|
/// use proxmox_backup::tools::{FileLogger, FileLogOptions};
|
|
///
|
|
/// # std::fs::remove_file("test.log");
|
|
/// let options = FileLogOptions {
|
|
/// to_stdout: true,
|
|
/// exclusive: true,
|
|
/// ..Default::default()
|
|
/// };
|
|
/// let mut log = FileLogger::new("test.log", options).unwrap();
|
|
/// flog!(log, "A simple log: {}", "Hello!");
|
|
/// ```
|
|
|
|
#[derive(Debug, Default)]
|
|
/// Options to control the behavior of a ['FileLogger'] instance
|
|
pub struct FileLogOptions {
|
|
/// Open underlying log file in append mode, useful when multiple concurrent processes
|
|
/// want to log to the same file (e.g., HTTP access log). Note that it is only atomic
|
|
/// for writes smaller than the PIPE_BUF (4k on Linux).
|
|
/// Inside the same process you may need to still use an mutex, for shared access.
|
|
pub append: bool,
|
|
/// Open underlying log file as readable
|
|
pub read: bool,
|
|
/// If set, ensure that the file is newly created or error out if already existing.
|
|
pub exclusive: bool,
|
|
/// Duplicate logged messages to STDOUT, like tee
|
|
pub to_stdout: bool,
|
|
/// Prefix messages logged to the file with the current local time as RFC 3339
|
|
pub prefix_time: bool,
|
|
/// if set, the file is tried to be chowned by the backup:backup user/group
|
|
/// Note, this is not designed race free as anybody could set it to another user afterwards
|
|
/// anyway. It must thus be used by all processes which doe not run as backup uid/gid.
|
|
pub owned_by_backup: bool,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct FileLogger {
|
|
file: std::fs::File,
|
|
file_name: std::path::PathBuf,
|
|
options: FileLogOptions,
|
|
}
|
|
|
|
/// Log messages to [FileLogger](tools/struct.FileLogger.html)
|
|
#[macro_export]
|
|
macro_rules! flog {
|
|
($log:expr, $($arg:tt)*) => ({
|
|
$log.log(format!($($arg)*));
|
|
})
|
|
}
|
|
|
|
impl FileLogger {
|
|
pub fn new<P: AsRef<std::path::Path>>(
|
|
file_name: P,
|
|
options: FileLogOptions,
|
|
) -> Result<Self, Error> {
|
|
let file = Self::open(&file_name, &options)?;
|
|
|
|
let file_name: std::path::PathBuf = file_name.as_ref().to_path_buf();
|
|
|
|
Ok(Self { file, file_name, options })
|
|
}
|
|
|
|
pub fn reopen(&mut self) -> Result<&Self, Error> {
|
|
let file = Self::open(&self.file_name, &self.options)?;
|
|
self.file = file;
|
|
Ok(self)
|
|
}
|
|
|
|
fn open<P: AsRef<std::path::Path>>(
|
|
file_name: P,
|
|
options: &FileLogOptions,
|
|
) -> Result<std::fs::File, Error> {
|
|
let file = std::fs::OpenOptions::new()
|
|
.read(options.read)
|
|
.write(true)
|
|
.append(options.append)
|
|
.create_new(options.exclusive)
|
|
.create(!options.exclusive)
|
|
.open(&file_name)?;
|
|
|
|
if options.owned_by_backup {
|
|
let backup_user = crate::backup::backup_user()?;
|
|
nix::unistd::chown(file_name.as_ref(), Some(backup_user.uid), Some(backup_user.gid))?;
|
|
}
|
|
|
|
Ok(file)
|
|
}
|
|
|
|
pub fn log<S: AsRef<str>>(&mut self, msg: S) {
|
|
let msg = msg.as_ref();
|
|
|
|
if self.options.to_stdout {
|
|
let mut stdout = std::io::stdout();
|
|
stdout.write_all(msg.as_bytes()).unwrap();
|
|
stdout.write_all(b"\n").unwrap();
|
|
}
|
|
|
|
let line = if self.options.prefix_time {
|
|
let now = proxmox::tools::time::epoch_i64();
|
|
let rfc3339 = match proxmox::tools::time::epoch_to_rfc3339(now) {
|
|
Ok(rfc3339) => rfc3339,
|
|
Err(_) => "1970-01-01T00:00:00Z".into(), // for safety, should really not happen!
|
|
};
|
|
format!("{}: {}\n", rfc3339, msg)
|
|
} else {
|
|
format!("{}\n", msg)
|
|
};
|
|
if let Err(err) = self.file.write_all(line.as_bytes()) {
|
|
// avoid panicking, log methods should not do that
|
|
// FIXME: or, return result???
|
|
eprintln!("error writing to log file - {}", err);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::io::Write for FileLogger {
|
|
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
|
|
if self.options.to_stdout {
|
|
let _ = std::io::stdout().write(buf);
|
|
}
|
|
self.file.write(buf)
|
|
}
|
|
|
|
fn flush(&mut self) -> Result<(), std::io::Error> {
|
|
if self.options.to_stdout {
|
|
let _ = std::io::stdout().flush();
|
|
}
|
|
self.file.flush()
|
|
}
|
|
}
|