public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Thomas Ellmenreich <t.ellmenreich@proxmox.com>
To: pdm-devel@lists.proxmox.com
Cc: Thomas Ellmenreich <t.ellmenreich@proxmox.com>
Subject: [PATCH proxmox 4/5] log: return the logger configuration after initialisation
Date: Mon, 21 Sep 2026 11:51:57 +0200	[thread overview]
Message-ID: <20260921095210.229315-6-t.ellmenreich@proxmox.com> (raw)
In-Reply-To: <20260921095210.229315-2-t.ellmenreich@proxmox.com>

The logger configuration is returned when the logger is initialised, so
that it can be referenced for other configurations or loggers.

Signed-off-by: Thomas Ellmenreich <t.ellmenreich@proxmox.com>
---
 proxmox-log/src/builder.rs | 135 +++++++++++++++++++++++++++++++++----
 proxmox-log/src/lib.rs     |   6 +-
 2 files changed, 126 insertions(+), 15 deletions(-)

diff --git a/proxmox-log/src/builder.rs b/proxmox-log/src/builder.rs
index 05da3643..e591fecc 100644
--- a/proxmox-log/src/builder.rs
+++ b/proxmox-log/src/builder.rs
@@ -57,7 +57,7 @@ impl<S> Filter<S> for NoWorkerTask {
 /// # func().expect("failed to init logger");
 /// ```
 pub struct Logger {
-    global_log_level: EnvFilter,
+    config: LoggerConfig,
     layer: Vec<Box<dyn Layer<Registry> + Send + Sync + 'static>>,
 }
 
@@ -66,10 +66,8 @@ impl Logger {
     /// variable. If the env variable cannot be retrieved or the content is not parsable,
     /// fallback to the default_log_level passed.
     pub fn from_env(env_var: &str, default_log_level: LevelFilter) -> Logger {
-        let var_content = std::env::var(env_var).ok();
-        let log_level = Self::apply_default_log_level(var_content, default_log_level);
         Logger {
-            global_log_level: log_level,
+            config: LoggerConfig::from_env(env_var, default_log_level),
             layer: vec![],
         }
     }
@@ -136,13 +134,14 @@ impl Logger {
     /// Inits the tracing logger with the previously configured layers.
     ///
     /// Also configures the `LogTracer` which will convert all `log` events to tracing events.
-    pub fn init(self) -> Result<(), anyhow::Error> {
+    pub fn init(self) -> Result<LoggerConfig, anyhow::Error> {
+        let config = self.config.clone();
         let registry = self.create_subscriber();
 
         tracing::subscriber::set_global_default(registry)?;
 
         LogTracer::init()?;
-        Ok(())
+        Ok(config)
     }
 
     /// Creates the subscriber to be used for the Logger
@@ -151,7 +150,76 @@ impl Logger {
     fn create_subscriber(self) -> impl Subscriber {
         tracing_subscriber::registry()
             .with(self.layer)
-            .with(self.global_log_level)
+            .with(self.config.global_log_level)
+    }
+}
+
+/// Struct containing the configuration of the logger
+pub struct LoggerConfig {
+    global_log_level: EnvFilter,
+}
+
+/// TODO: With version 0.3.20 of tracing_subscriber this [`Clone`]
+/// implementation can be replaced with a simple derive
+impl Clone for LoggerConfig {
+    fn clone(&self) -> Self {
+        let global_log_level = self.clone_global_filter();
+        Self { global_log_level }
+    }
+}
+
+impl LoggerConfig {
+    /// Tries to read the environemnt variable with the provided name and
+    /// parse its contents as a [`EnvFilter`]
+    pub fn from_env(env_var: &str, default_log_level: LevelFilter) -> LoggerConfig {
+        let var_content = std::env::var(env_var).ok();
+        Self::from_string(var_content, default_log_level)
+    }
+
+    /// Tries to parse the provided string as a [`EnvFilter`] if present. Falls
+    /// back to the default in all other cases
+    pub fn from_string(
+        env_filter_str: Option<String>,
+        default_log_level: LevelFilter,
+    ) -> LoggerConfig {
+        let global_log_level = Self::apply_default_log_level(env_filter_str, default_log_level);
+        Self { global_log_level }
+    }
+
+    pub fn global_filter(&self) -> EnvFilter {
+        self.clone_global_filter()
+    }
+
+    /// Returns an hint of the highest [verbosity level](https://docs.rs/tracing-core/0.1.32/tracing_core/metadata/struct.Level.html)
+    /// that this `EnvFilter` will enable.
+    ///
+    /// # Panics
+    ///
+    /// Panics if no max can be found, although that should never be the case
+    /// as we force a default log level and thus always have a max.
+    pub fn global_filter_max_level_hint(&self) -> LevelFilter {
+        self.global_log_level
+            .max_level_hint()
+            .expect("because we force a default log level there should always be a hint")
+    }
+
+    /// Clones the global_log_filter and returns said clone. Since the clone
+    /// implementation is not yet available, serializes the filter back into
+    /// a string and then parses it again.
+    ///
+    /// TODO: the current version of EnvFilter provided by the debian package
+    /// does not implement clone although the following [0] version already
+    /// does. So once the version is bumped, replace this hack with a `.clone()`
+    ///
+    /// [0]: https://docs.rs/tracing-subscriber/0.3.20/src/tracing_subscriber/filter/env/mod.rs.html#211-223
+    ///
+    /// # Panics
+    ///
+    /// Panics if, while serializing into a string and deserializing back into
+    /// a [`EnvFilter`], the parsing fails (which should never happen).
+    fn clone_global_filter(&self) -> EnvFilter {
+        EnvFilter::try_new(format!("{}", self.global_log_level))
+            .expect("creating a new envfilter from a existing filter should always be possible")
     }
 
     /// If present, tries to parse the `env_filter_str` as a [`EnvFilter`],
@@ -184,9 +252,9 @@ mod tests {
 
     use tracing::level_filters::LevelFilter;
     use tracing_log::log;
-    use tracing_subscriber::{Layer, util::SubscriberInitExt};
+    use tracing_subscriber::{EnvFilter, Layer, util::SubscriberInitExt};
 
-    use crate::Logger;
+    use crate::{Logger, builder::LoggerConfig};
 
     /// Modules created for testing purposes. Specifically, to test filtering
     /// of logs in different modules.
@@ -209,6 +277,47 @@ mod tests {
         }};
     }
 
+    // TODO: delete once [`EnvFilter`] implements [`Clone`]
+    #[test]
+    fn check_env_filter_display_contains_expected_modules() {
+        // Arrange
+        let filter_str = "warn,proxmox_log::builder::tests::test_module=info,proxmox_log::builder::tests::test_module::nested_module=error";
+
+        // Act
+        let formatted_filter = EnvFilter::try_new(filter_str).unwrap().to_string();
+
+        // Assert
+        let sort_parts = |filter: &str| -> String {
+            let mut parts = filter.split(',').collect::<Vec<_>>();
+            parts.sort();
+            parts.join(",")
+        };
+        assert_eq!(sort_parts(formatted_filter.as_str()), sort_parts(filter_str));
+    }
+
+    // TODO: delete once [`EnvFilter`] implements [`Clone`]
+    #[test]
+    fn check_env_filter_default_formats_as_expected() {
+        // Arrange
+        let default = LevelFilter::WARN;
+
+        // Act
+        let formatted_filter = EnvFilter::default().add_directive(default.into()).to_string();
+
+        // Assert
+        assert_eq!(formatted_filter.as_str(), "warn");
+    }
+
+    // TODO: delete once [`EnvFilter`] implements [`Clone`]
+    #[test]
+    fn check_empty_env_filter_formats_as_expected() {
+        // Act
+        let formatted_filter = EnvFilter::try_new("").unwrap().to_string();
+
+        // Assert
+        assert_eq!(formatted_filter.as_str(), "");
+    }
+
     #[test]
     fn logger_builder_correctly_applies_filter() {
         // Arrange
@@ -348,7 +457,7 @@ mod tests {
         (
             events,
             Logger {
-                global_log_level: Logger::apply_default_log_level(
+                config: LoggerConfig::from_string(
                     var_filter.map(str::to_string),
                     default_log_level,
                 ),
@@ -371,8 +480,10 @@ mod tests {
     ) -> (Arc<Mutex<Vec<String>>>, tracing::subscriber::DefaultGuard) {
         use tracing_subscriber::layer::SubscriberExt;
 
-        let filter =
-            Logger::apply_default_log_level(var_filter.map(str::to_string), default_log_level);
+        let filter = LoggerConfig::apply_default_log_level(
+            var_filter.map(str::to_string),
+            default_log_level,
+        );
 
         let (events, filter) = TestLayer::new(filter);
         let guard = tracing_subscriber::Registry::default()
diff --git a/proxmox-log/src/lib.rs b/proxmox-log/src/lib.rs
index 32c10e27..3c2e4fdd 100644
--- a/proxmox-log/src/lib.rs
+++ b/proxmox-log/src/lib.rs
@@ -13,7 +13,7 @@ mod pve_task_formatter;
 mod tasklog_layer;
 
 pub mod builder;
-pub use builder::Logger;
+pub use builder::{Logger, LoggerConfig};
 pub use file_logger::{FileLogOptions, FileLogger};
 
 pub use tracing::Level;
@@ -154,7 +154,7 @@ where
 pub fn init_logger(
     env_var_name: &str,
     default_log_level: LevelFilter,
-) -> Result<(), anyhow::Error> {
+) -> Result<LoggerConfig, anyhow::Error> {
     Logger::from_env(env_var_name, default_log_level)
         .journald_on_no_workertask()
         .tasklog_pbs()
@@ -168,7 +168,7 @@ pub fn init_logger(
 pub fn init_cli_logger(
     env_var_name: &str,
     default_log_level: LevelFilter,
-) -> Result<(), anyhow::Error> {
+) -> Result<LoggerConfig, anyhow::Error> {
     Logger::from_env(env_var_name, default_log_level)
         .stderr_on_no_workertask()
         .tasklog_pbs()
-- 
2.47.3





  parent reply	other threads:[~2026-09-21  9:53 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-21  9:51 [RFC datacenter-manager/proxmox 0/5] log: allow finegrained control logging levels Thomas Ellmenreich
2026-09-21  9:51 ` [PATCH proxmox 1/5] log: replace per layer filtering by one global filter Thomas Ellmenreich
2026-09-21  9:51 ` [PATCH proxmox 2/5] log: replace simple level filter with env filter Thomas Ellmenreich
2026-09-21  9:51 ` [PATCH proxmox 3/5] log: add tests to the logger Thomas Ellmenreich
2026-09-21  9:51 ` Thomas Ellmenreich [this message]
2026-09-21  9:51 ` [PATCH datacenter-manager 5/5] api: set REST server debug level based on actual log level Thomas Ellmenreich

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260921095210.229315-6-t.ellmenreich@proxmox.com \
    --to=t.ellmenreich@proxmox.com \
    --cc=pdm-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal