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
next prev 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.