From 55911e94cf5b33655bbe318b85a27836f1fa3e09 Mon Sep 17 00:00:00 2001 From: Philipp Jungkamp Date: Thu, 14 Aug 2025 17:42:33 +0200 Subject: [PATCH] glib: Add logging bridge for structured logs --- glib/Cargo.toml | 1 + glib/src/bridged_logging.rs | 105 +++++++++++++++++++++++++++++++++++- glib/src/lib.rs | 4 +- 3 files changed, 107 insertions(+), 3 deletions(-) diff --git a/glib/Cargo.toml b/glib/Cargo.toml index 97a11ccd7c9b..8c134dc984a0 100644 --- a/glib/Cargo.toml +++ b/glib/Cargo.toml @@ -56,6 +56,7 @@ v2_82 = ["v2_80", "glib-sys/v2_82", "gobject-sys/v2_82"] v2_84 = ["v2_82", "glib-sys/v2_84", "gobject-sys/v2_84"] v2_86 = ["v2_84", "glib-sys/v2_86", "gobject-sys/v2_86"] log = ["rs-log"] +log_kv = ["log", "rs-log/kv"] log_macros = ["log"] compiletests = [] gio = ["gio-sys"] diff --git a/glib/src/bridged_logging.rs b/glib/src/bridged_logging.rs index 17ca1e8f7e31..7bd92c4d56be 100644 --- a/glib/src/bridged_logging.rs +++ b/glib/src/bridged_logging.rs @@ -1,6 +1,6 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use crate::{gstr, log as glib_log, log_structured_array, translate::*, LogField}; +use crate::{gstr, log as glib_log, log_structured_array, translate::*, LogField, LogWriterOutput}; // rustdoc-stripper-ignore-next /// Enumeration of the possible formatting behaviours for a @@ -262,7 +262,108 @@ pub fn rust_log_handler(domain: Option<&str>, level: glib_log::LogLevel, message glib_log::LogLevel::Debug => rs_log::Level::Debug, }; - rs_log::log!(target: domain.unwrap_or(""), level, "{}", message); + rs_log::log!(target: domain.unwrap_or_default(), level, "{message}"); +} + +#[cfg(feature = "log_kv")] +struct LogFields<'field>(&'field [LogField<'field>]); + +#[cfg(feature = "log_kv")] +impl<'fields> rs_log::kv::Source for LogFields<'fields> { + fn visit<'kvs>( + &'kvs self, + visitor: &mut dyn rs_log::kv::VisitSource<'kvs>, + ) -> Result<(), rs_log::kv::Error> { + use rs_log::kv::{Key, Value}; + + for field in self.0 { + let key = Key::from_str(field.key()); + let value = field + .value_str() + .map(Value::from) + .unwrap_or_else(Value::null); + visitor.visit_pair(key, value)?; + } + + Ok(()) + } + + fn count(&self) -> usize { + self.0.len() + } +} + +// rustdoc-stripper-ignore-next +/// Provides a glib log writer which routes all structured logging messages to the +/// [`log crate`](https://crates.io/crates/log). +/// +/// In order to use this function, `glib` must be built with the `log` feature +/// enabled. +/// +/// Use this function if you want to use the log crate as the main logging +/// output in your application, and want to route all structured logging happening in +/// glib to the log crate. If you want the opposite, use [`GlibLogger`](struct.GlibLogger.html). +/// +/// NOTE: This should never be used when [`GlibLogger`](struct.GlibLogger.html) is +/// registered as a logger, otherwise a stack overflow will occur. +/// +/// ```no_run +/// glib::log_set_writer_func(glib::rust_log_writer); +/// ``` +pub fn rust_log_writer(log_level: glib_log::LogLevel, fields: &[LogField<'_>]) -> LogWriterOutput { + let lvl = match log_level { + glib_log::LogLevel::Error | glib_log::LogLevel::Critical => rs_log::Level::Error, + glib_log::LogLevel::Warning => rs_log::Level::Warn, + glib_log::LogLevel::Info | glib_log::LogLevel::Message => rs_log::Level::Info, + glib_log::LogLevel::Debug => rs_log::Level::Debug, + }; + + if lvl > rs_log::STATIC_MAX_LEVEL { + return LogWriterOutput::Handled; + } + + let mut domain = None::<&str>; + let mut message = None::<&str>; + let mut file = None::<&str>; + let mut line = None::; + let mut func = None::<&str>; + + for field in fields { + let Some(value) = field.value_str() else { + continue; + }; + + match field.key() { + "GLIB_DOMAIN" => domain = Some(value), + "MESSAGE" => message = Some(value), + "CODE_FILE" => file = Some(value), + "CODE_LINE" => line = value.parse().ok(), + "CODE_FUNC" => func = Some(value), + _ => continue, + }; + } + + if let Some(message) = message { + let logger = rs_log::logger(); + let mut record = rs_log::Record::builder(); + + #[cfg(feature = "log_kv")] + let fields = LogFields(fields); + + record + .level(lvl) + .target(domain.unwrap_or_default()) + .file(file) + .line(line) + .module_path(func); + + #[cfg(feature = "log_kv")] + record.key_values(&fields); + + logger.log(&record.args(format_args!("{message}")).build()); + } + + LogWriterOutput::Handled } // rustdoc-stripper-ignore-next diff --git a/glib/src/lib.rs b/glib/src/lib.rs index bef53b42955d..661d5fc573c8 100644 --- a/glib/src/lib.rs +++ b/glib/src/lib.rs @@ -221,7 +221,9 @@ pub use self::log::{log_writer_is_journald, log_writer_supports_color}; mod bridged_logging; #[cfg(feature = "log")] #[cfg_attr(docsrs, doc(cfg(feature = "log")))] -pub use self::bridged_logging::{rust_log_handler, GlibLogger, GlibLoggerDomain, GlibLoggerFormat}; +pub use self::bridged_logging::{ + rust_log_handler, rust_log_writer, GlibLogger, GlibLoggerDomain, GlibLoggerFormat, +}; #[macro_use] pub mod subclass;