[pbs-devel] [PATCH proxmox v7 1/4] proxmox-log: add tracing infrastructure

Gabriel Goller g.goller at proxmox.com
Thu Jul 4 11:24:28 CEST 2024


On 03.07.2024 16:05, Wolfgang Bumiller wrote:
>On Mon, Jul 01, 2024 at 11:26:30AM GMT, Gabriel Goller wrote:
>> diff --git a/proxmox-log/src/file_logger.rs b/proxmox-log/src/file_logger.rs
>> +/// Log messages with optional automatically added timestamps into files
>> +///
>> +/// #### Example:
>> +/// ```
>> +/// # use anyhow::{bail, format_err, Error};
>> +/// use proxmox_log::{flog, 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!");
>> +/// # std::fs::remove_file("test.log");
>> +/// ```
>> +pub struct FileLogger {
>> +    file: std::fs::File,
>> +    file_name: std::path::PathBuf,
>> +    options: FileLogOptions,
>> +}
>> +
>> +/// Log messages to [FileLogger] - ``println`` like macro
>> +#[macro_export]
>> +macro_rules! flog {
>
>AFAICT this is completely unused and I don't think we'll actually be
>using FileLogger like this anyway, so I'd say we can drop this.

I agree, removed it and adjusted the comment above.

>> +    ($log:expr, $($arg:tt)*) => ({
>> +        $log.log(format!($($arg)*));
>> +    })
>> +}
>> +
>> +impl FileLogger {
>> +    /// Create a new FileLogger. This opens the specified file and
>> +    /// stores a file descriptor.
>> +    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,
>> +        })
>> +    }
>> +
>> +    /// Reopen logfile.
>> +    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 mut flags = OFlag::O_CLOEXEC;
>> +
>> +        if options.read {
>> +            flags |= OFlag::O_RDWR;
>> +        } else {
>> +            flags |= OFlag::O_WRONLY;
>> +        }
>> +
>> +        if options.append {
>> +            flags |= OFlag::O_APPEND;
>> +        }
>> +        if options.exclusive {
>> +            flags |= OFlag::O_EXCL;
>> +        }
>> +
>> +        let file =
>> +            atomic_open_or_create_file(&file_name, flags, &[], options.file_opts.clone(), false)?;
>> +
>> +        Ok(file)
>> +    }
>> +
>> +    /// Writes `msg` to the logfile with a timestamp and to stdout if
>> +    /// specified.
>> +    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();
>> +            let _ = stdout.write_all(msg.as_bytes());
>> +            let _ = stdout.write_all(b"\n");
>> +        }
>> +
>> +        let line = if self.options.prefix_time {
>> +            let now = proxmox_time::epoch_i64();
>> +            let rfc3339 = match proxmox_time::epoch_to_rfc3339(now) {
>> +                Ok(rfc3339) => rfc3339,
>> +                Err(_) => "1970-01-01T00:00:00Z".into(), // for safety, should really not happen!
>> +            };
>> +            format!("{rfc3339}: {msg}\n")
>> +        } else {
>> +            format!("{msg}\n")
>> +        };
>> +        // Note: we ignore the potential error here because log methods
>> +        // shouldn't panic. We also can't log an error, because that
>> +        // would lead to recursion.
>> +        let _ = self.file.write_all(line.as_bytes());
>> +    }
>> +}
>> +
>> +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()
>> +    }
>> +}
>> diff --git a/proxmox-log/src/lib.rs b/proxmox-log/src/lib.rs
>> new file mode 100644
>> index 000000000000..5fd7ac1c4636
>> --- /dev/null
>> +++ b/proxmox-log/src/lib.rs
>> @@ -0,0 +1,52 @@
>> +use std::{
>> +    cell::{Cell, RefCell},
>> +    env,
>> +};
>> +
>> +use tracing::Level;
>> +use tracing_log::{AsLog, LogTracer};
>> +use tracing_subscriber::filter::{filter_fn, LevelFilter};
>> +use tracing_subscriber::prelude::*;
>> +
>> +use tasklog_layer::TasklogLayer;
>> +
>> +mod file_logger;
>> +pub use file_logger::{FileLogOptions, FileLogger};
>> +
>> +mod tasklog_layer;
>> +
>> +tokio::task_local! {
>> +    pub static LOGGER: RefCell<FileLogger>;
>> +    pub static WARN_COUNTER: Cell<u64>;
>> +}
>> +
>> +pub fn init_logger(
>> +    env_var_name: &str,
>> +    default_log_level: LevelFilter,
>> +    _application_name: &str,
>> +) -> Result<(), anyhow::Error> {
>> +    let mut log_level = default_log_level;
>> +    if let Ok(v) = env::var(env_var_name) {
>> +        if let Ok(l) = v.parse::<LevelFilter>() {
>> +            log_level = l;
>> +        }
>> +    }
>> +    let registry = tracing_subscriber::registry()
>> +        .with(
>> +            tracing_journald::layer()
>> +                .expect("Unable to open syslog")
>> +                .with_filter(log_level)
>> +                .with_filter(filter_fn(|metadata| {
>> +                    LOGGER.try_with(|_| {}).is_err() || *metadata.level() == Level::ERROR
>> +                })),
>> +        )
>> +        .with(
>> +            TasklogLayer {}
>> +                .with_filter(log_level)
>> +                .with_filter(filter_fn(|_| LOGGER.try_with(|_| {}).is_ok())),
>> +        );
>> +
>> +    tracing::subscriber::set_global_default(registry)?;
>> +    LogTracer::init_with_filter(log_level.as_log())?;
>> +    Ok(())
>> +}
>> diff --git a/proxmox-log/src/tasklog_layer.rs b/proxmox-log/src/tasklog_layer.rs
>> new file mode 100644
>> index 000000000000..e25ded8cbcd4
>> --- /dev/null
>> +++ b/proxmox-log/src/tasklog_layer.rs
>> @@ -0,0 +1,59 @@
>> +use std::fmt::Write as _;
>> +
>> +use tracing::field::Field;
>> +use tracing::field::Visit;
>> +use tracing::Event;
>> +use tracing::Level;
>> +use tracing::Subscriber;
>> +use tracing_subscriber::layer::Context;
>> +use tracing_subscriber::Layer;
>> +
>> +use crate::FileLogger;
>> +use crate::LOGGER;
>> +use crate::WARN_COUNTER;
>> +
>> +pub struct TasklogLayer;
>> +
>> +impl<S: Subscriber> Layer<S> for TasklogLayer {
>> +    fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
>> +        let mut buf = String::new();
>> +        event.record(&mut EventVisitor::new(&mut buf));
>> +        let level = event.metadata().level();
>
>I actually mentioned this in a very early version: The string should be
>built within the try_with closure below, since if we don't have a
>LOGGER we'll only be throwing it away.

Oops, yeah this is my bad. It somehow sneaked in again in the last
version :(

>This does also make me wonder if we really need the filter on the layer
>to test whether LOGGER is usable... not sure how efficient the
>implementation is on the tracing crate side if the on_event method is
>entered.

I removed it from the task-log layer and did some quick benchmarking:

     Benchmark 1: ./target/debug/slow
       Time (mean ± σ):      30.9 ms ±   4.9 ms    [User: 22.1 ms, System: 8.3 ms]
       Range (min … max):    27.6 ms …  74.6 ms    10000 runs

     Benchmark 2: ./target/debug/rust_test
       Time (mean ± σ):      26.7 ms ±   4.5 ms    [User: 18.6 ms, System: 7.6 ms]
       Range (min … max):    24.0 ms …  82.9 ms    10000 runs

     Summary
       './target/debug/rust_test' ran
         1.16 ± 0.27 times faster than './target/debug/slow'

Note the last line—without the LOGGER check in the filter_fn we are
1.16 times faster!

>> diff --git a/proxmox-rest-server/Cargo.toml b/proxmox-rest-server/Cargo.toml
>> index 5ffde0d6f210..20f43a69a4d0 100644
>> --- a/proxmox-rest-server/Cargo.toml
>> +++ b/proxmox-rest-server/Cargo.toml
>
>This hunk should be in the 2nd patch.

Done. Thanks for the review!





More information about the pbs-devel mailing list