From c17ebf44568e7ef20c8d4c6c02c11580d0c91350 Mon Sep 17 00:00:00 2001 From: Jason Francis Date: Sun, 27 Feb 2022 22:32:59 -0500 Subject: [PATCH 1/4] glib: Add GStr for borrowing GStrings --- glib-macros/src/lib.rs | 17 ++ glib/src/gstring.rs | 425 ++++++++++++++++++++++++++++++++++++++++- glib/src/lib.rs | 5 +- 3 files changed, 439 insertions(+), 8 deletions(-) diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index a5df07b67b17..1a7f88e881ab 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -803,3 +803,20 @@ pub fn variant_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); variant_derive::impl_variant(input) } + +#[proc_macro] +pub fn cstr_bytes(item: TokenStream) -> TokenStream { + syn::parse::Parser::parse2( + |stream: syn::parse::ParseStream<'_>| { + let literal = stream.parse::()?; + stream.parse::()?; + let bytes = std::ffi::CString::new(literal.value()) + .map_err(|e| syn::Error::new_spanned(&literal, format!("{}", e)))? + .into_bytes_with_nul(); + let bytes = proc_macro2::Literal::byte_string(&bytes); + Ok(quote::quote! { #bytes }.into()) + }, + item.into(), + ) + .unwrap_or_else(|e| e.into_compile_error().into()) +} diff --git a/glib/src/gstring.rs b/glib/src/gstring.rs index 46498fd51d26..9323c7ddcc81 100644 --- a/glib/src/gstring.rs +++ b/glib/src/gstring.rs @@ -14,6 +14,337 @@ use std::ptr; use std::slice; use std::string::String; +// rustdoc-stripper-ignore-next +/// Representaion of a borrowed [`GString`]. +/// +/// This type is very similar to [`std::ffi::CStr`], but with one added constraint: the string +/// must also be valid UTF-8. +#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[repr(transparent)] +pub struct GStr(str); + +impl GStr { + // rustdoc-stripper-ignore-next + /// Creates a GLib string wrapper from a string slice. The string slice must be terminated with + /// a nul byte. + /// + /// This function will cast the provided bytes to a `GStr` wrapper after ensuring + /// that the string slice is nul-terminated and does not contain any interior nul bytes. + pub fn from_str_with_nul(s: &str) -> Result<&Self, std::ffi::FromBytesWithNulError> { + let bytes = s.as_bytes(); + CStr::from_bytes_with_nul(bytes)?; + Ok(unsafe { Self::from_bytes_with_nul_unchecked(bytes) }) + } + // rustdoc-stripper-ignore-next + /// Unsafely creates a GLib string wrapper from a byte slice. + /// + /// This function will cast the provided `bytes` to a `GStr` wrapper without performing any + /// sanity checks. The provided slice **must** be valid UTF-8, nul-terminated and not contain + /// any interior nul bytes. + #[inline] + pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &Self { + debug_assert!(!bytes.is_empty() && bytes[bytes.len() - 1] == 0); + mem::transmute(bytes) + } + // rustdoc-stripper-ignore-next + /// Wraps a raw C string with a safe GLib string wrapper. The provided C string **must** be + /// valid UTF-8 and nul-terminated. All constraints from [`std::ffi::CStr::from_ptr`] also + /// apply here. + #[inline] + pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> &'a Self { + let cstr = CStr::from_ptr(ptr); + Self::from_bytes_with_nul_unchecked(cstr.to_bytes_with_nul()) + } + // rustdoc-stripper-ignore-next + /// Converts this GLib string to a byte slice containing the trailing 0 byte. + /// + /// This function is the equivalent of [`GStr::to_bytes`] except that it will retain the + /// trailing nul terminator instead of chopping it off. + #[inline] + pub fn to_bytes_with_nul(&self) -> &[u8] { + self.0.as_bytes() + } + // rustdoc-stripper-ignore-next + /// Converts this GLib string to a byte slice. + /// + /// The returned slice will **not** contain the trailing nul terminator that this GLib + /// string has. + #[inline] + pub fn to_bytes(&self) -> &[u8] { + self.as_str().as_bytes() + } + // rustdoc-stripper-ignore-next + /// Returns the inner pointer to this GLib string. + /// + /// The returned pointer will be valid for as long as `self` is, and points to a contiguous + /// region of memory terminated with a 0 byte to represent the end of the string. + /// + /// **WARNING** + /// + /// The returned pointer is read-only; writing to it (including passing it to C code that + /// writes to it) causes undefined behavior. It is your responsibility to make + /// sure that the underlying memory is not freed too early. + #[inline] + pub fn as_ptr(&self) -> *const c_char { + self.0.as_ptr() as *const _ + } + // rustdoc-stripper-ignore-next + /// Converts this GLib string to a string slice. + #[inline] + pub fn as_str(&self) -> &str { + // Clip off the nul byte + &self.0[0..self.0.len() - 1] + } + // rustdoc-stripper-ignore-next + /// Converts this GLib string to a C string slice. + #[inline] + pub fn as_c_str(&self) -> &CStr { + unsafe { CStr::from_bytes_with_nul_unchecked(self.to_bytes_with_nul()) } + } +} + +// rustdoc-stripper-ignore-next +/// Converts a static string literal into a static nul-terminated string. +/// +/// The expanded expression has type [`&'static GStr`]. This macro will panic if the +/// string literal contains any interior nul bytes. +/// +/// # Examples +/// +/// ``` +/// # fn main() { +/// use glib::{gstr, GStr, GString}; +/// +/// const MY_STRING: &GStr = gstr!("Hello"); +/// assert_eq!(MY_STRING.to_bytes_with_nul()[5], 0u8); +/// let owned: GString = MY_STRING.to_owned(); +/// assert_eq!(MY_STRING, owned); +/// # } +/// ``` +/// +/// [`&'static GStr`]: crate::GStr +#[macro_export] +macro_rules! gstr { + ($s:literal) => { + unsafe { $crate::GStr::from_bytes_with_nul_unchecked($crate::cstr_bytes!($s)) } + }; +} + +impl Default for &GStr { + fn default() -> Self { + const SLICE: &[c_char] = &[0]; + unsafe { GStr::from_ptr(SLICE.as_ptr()) } + } +} + +impl<'a> TryFrom<&'a CStr> for &'a GStr { + type Error = std::str::Utf8Error; + #[inline] + fn try_from(s: &'a CStr) -> Result { + s.to_str()?; + Ok(unsafe { GStr::from_bytes_with_nul_unchecked(s.to_bytes_with_nul()) }) + } +} + +impl PartialEq for String { + fn eq(&self, other: &GStr) -> bool { + self.as_str() == other.as_str() + } +} + +impl PartialEq for GStr { + fn eq(&self, other: &str) -> bool { + self.as_str() == other + } +} + +impl<'a> PartialEq<&'a str> for GStr { + fn eq(&self, other: &&'a str) -> bool { + self.as_str() == *other + } +} + +impl<'a> PartialEq for &'a str { + fn eq(&self, other: &GStr) -> bool { + *self == other.as_str() + } +} + +impl PartialEq for GStr { + fn eq(&self, other: &String) -> bool { + self.as_str() == other.as_str() + } +} + +impl PartialEq for str { + fn eq(&self, other: &GStr) -> bool { + self == other.as_str() + } +} + +impl PartialOrd for String { + fn partial_cmp(&self, other: &GStr) -> Option { + Some(self.cmp(&String::from(other.as_str()))) + } +} + +impl PartialOrd for GStr { + fn partial_cmp(&self, other: &String) -> Option { + Some(self.as_str().cmp(other.as_str())) + } +} + +impl PartialOrd for str { + fn partial_cmp(&self, other: &GStr) -> Option { + Some(self.cmp(other.as_str())) + } +} + +impl PartialOrd for GStr { + fn partial_cmp(&self, other: &str) -> Option { + Some(self.as_str().cmp(other)) + } +} + +impl AsRef for GStr { + fn as_ref(&self) -> &GStr { + self + } +} + +impl AsRef for GStr { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl AsRef for GStr { + fn as_ref(&self) -> &CStr { + self.as_c_str() + } +} + +impl AsRef for GStr { + fn as_ref(&self) -> &OsStr { + OsStr::new(self.as_str()) + } +} + +impl AsRef<[u8]> for GStr { + fn as_ref(&self) -> &[u8] { + self.to_bytes() + } +} + +impl Deref for GStr { + type Target = str; + + fn deref(&self) -> &str { + self.as_str() + } +} + +impl ToOwned for GStr { + type Owned = GString; + + #[inline] + fn to_owned(&self) -> Self::Owned { + GString(Inner::Native(Some(self.as_c_str().to_owned()))) + } +} + +impl GlibPtrDefault for GStr { + type GlibType = *mut c_char; +} + +impl StaticType for GStr { + fn static_type() -> Type { + str::static_type() + } +} + +unsafe impl<'a> crate::value::FromValue<'a> for &'a GStr { + type Checker = crate::value::GenericValueTypeOrNoneChecker; + + unsafe fn from_value(value: &'a crate::Value) -> Self { + let ptr = gobject_ffi::g_value_get_string(value.to_glib_none().0); + let cstr = CStr::from_ptr(ptr); + assert!(cstr.to_str().is_ok()); + GStr::from_bytes_with_nul_unchecked(cstr.to_bytes_with_nul()) + } +} + +impl crate::value::ToValue for GStr { + #[inline] + fn to_value(&self) -> crate::Value { + self.as_str().to_value() + } + + #[inline] + fn value_type(&self) -> Type { + str::static_type() + } +} + +impl crate::value::ToValue for &GStr { + #[inline] + fn to_value(&self) -> crate::Value { + (*self).to_value() + } + + #[inline] + fn value_type(&self) -> Type { + str::static_type() + } +} + +impl crate::value::ToValueOptional for GStr { + #[inline] + fn to_value_optional(s: Option<&Self>) -> crate::Value { + crate::value::ToValueOptional::to_value_optional(s.map(|s| s.as_str())) + } +} + +#[doc(hidden)] +impl<'a> ToGlibPtr<'a, *const c_char> for GStr { + type Storage = GString; + + #[inline] + fn to_glib_none(&'a self) -> Stash<'a, *const c_char, Self> { + let tmp = self.to_owned(); + Stash(self.as_ptr(), tmp) + } + + #[inline] + fn to_glib_full(&self) -> *const c_char { + self.as_str().to_glib_full() + } +} + +#[doc(hidden)] +impl<'a> ToGlibPtr<'a, *mut c_char> for GStr { + type Storage = GString; + + #[inline] + fn to_glib_none(&'a self) -> Stash<'a, *mut c_char, Self> { + let tmp = self.to_owned(); + Stash(tmp.as_ptr() as *mut c_char, tmp) + } + + #[inline] + fn to_glib_full(&self) -> *mut c_char { + self.as_str().to_glib_full() + } +} + +// rustdoc-stripper-ignore-next +/// A type representing an owned, C-compatible, nul-terminated UTF-8 string. +/// +/// `GString` is to &[GStr] as [`String`] is to &[str]: the former in +/// each pair are owned strings; the latter are borrowed references. +/// +/// This type is very similar to [`std::ffi::CString`], but with one added constraint: the string +/// must also be valid UTF-8. pub struct GString(Inner); enum Inner { Native(Option), @@ -47,6 +378,19 @@ impl GString { } } + // rustdoc-stripper-ignore-next + /// Extracts the [`GStr`] containing the entire string. + pub fn as_gstr(&self) -> &GStr { + let bytes = match self.0 { + Inner::Native(ref cstr) => cstr.as_ref().unwrap().to_bytes_with_nul(), + Inner::Foreign { len, .. } if len == 0 => &[0], + Inner::Foreign { ptr, len } => unsafe { + slice::from_raw_parts(ptr.as_ptr() as *const _, len + 1) + }, + }; + unsafe { GStr::from_bytes_with_nul_unchecked(bytes) } + } + // rustdoc-stripper-ignore-next /// Return the underlying pointer of the `GString`. pub fn as_ptr(&self) -> *const c_char { @@ -114,6 +458,12 @@ impl hash::Hash for GString { } } +impl Borrow for GString { + fn borrow(&self) -> &GStr { + self.as_gstr() + } +} + impl Borrow for GString { fn borrow(&self) -> &str { self.as_str() @@ -144,19 +494,37 @@ impl PartialEq for String { } } +impl PartialEq for GString { + fn eq(&self, other: &GStr) -> bool { + self.as_str() == other.as_str() + } +} + +impl PartialEq<&GStr> for GString { + fn eq(&self, other: &&GStr) -> bool { + self.as_str() == other.as_str() + } +} + impl PartialEq for GString { fn eq(&self, other: &str) -> bool { self.as_str() == other } } -impl<'a> PartialEq<&'a str> for GString { - fn eq(&self, other: &&'a str) -> bool { +impl PartialEq<&str> for GString { + fn eq(&self, other: &&str) -> bool { self.as_str() == *other } } -impl<'a> PartialEq for &'a str { +impl PartialEq for &GStr { + fn eq(&self, other: &GString) -> bool { + self.as_str() == other.as_str() + } +} + +impl PartialEq for &str { fn eq(&self, other: &GString) -> bool { *self == other.as_str() } @@ -174,6 +542,12 @@ impl PartialEq for str { } } +impl PartialEq for GStr { + fn eq(&self, other: &GString) -> bool { + self.as_str() == other.as_str() + } +} + impl PartialOrd for String { fn partial_cmp(&self, other: &GString) -> Option { Some(self.cmp(&String::from(other.as_str()))) @@ -186,6 +560,18 @@ impl PartialOrd for GString { } } +impl PartialOrd for GStr { + fn partial_cmp(&self, other: &GString) -> Option { + Some(self.as_str().cmp(other)) + } +} + +impl PartialOrd for GString { + fn partial_cmp(&self, other: &GStr) -> Option { + Some(self.as_str().cmp(other.as_str())) + } +} + impl PartialOrd for str { fn partial_cmp(&self, other: &GString) -> Option { Some(self.cmp(other)) @@ -200,18 +586,36 @@ impl PartialOrd for GString { impl Eq for GString {} +impl AsRef for GString { + fn as_ref(&self) -> &GStr { + self.as_gstr() + } +} + impl AsRef for GString { fn as_ref(&self) -> &str { self.as_str() } } +impl AsRef for GString { + fn as_ref(&self) -> &CStr { + self.as_gstr().as_c_str() + } +} + impl AsRef for GString { fn as_ref(&self) -> &OsStr { OsStr::new(self.as_str()) } } +impl AsRef<[u8]> for GString { + fn as_ref(&self) -> &[u8] { + self.as_str().as_bytes() + } +} + impl Deref for GString { type Target = str; @@ -267,9 +671,16 @@ impl From> for GString { } } -impl<'a> From<&'a str> for GString { +impl From<&GStr> for GString { + #[inline] + fn from(s: &GStr) -> GString { + s.to_owned() + } +} + +impl From<&str> for GString { #[inline] - fn from(s: &'a str) -> Self { + fn from(s: &str) -> Self { // Allocates with the GLib allocator unsafe { // No check for valid UTF-8 here @@ -305,9 +716,9 @@ impl From for GString { } } -impl<'a> From<&'a CStr> for GString { +impl From<&CStr> for GString { #[inline] - fn from(c: &'a CStr) -> Self { + fn from(c: &CStr) -> Self { // Creates a copy with the GLib allocator // Also check if it's valid UTF-8 c.to_str().unwrap().into() diff --git a/glib/src/lib.rs b/glib/src/lib.rs index 5e4affc20d2d..2ab46a1f9e78 100644 --- a/glib/src/lib.rs +++ b/glib/src/lib.rs @@ -21,6 +21,9 @@ pub use glib_macros::{ Enum, ErrorDomain, SharedBoxed, Variant, }; +#[doc(hidden)] +pub use glib_macros::cstr_bytes; + pub use self::byte_array::ByteArray; pub use self::bytes::Bytes; pub use self::closure::{Closure, RustClosure}; @@ -94,7 +97,7 @@ pub use self::source::*; #[macro_use] pub mod translate; mod gstring; -pub use self::gstring::GString; +pub use self::gstring::{GStr, GString}; mod gstring_builder; pub use self::gstring_builder::GStringBuilder; pub mod types; From 5c0fd646a505283d002a85e94cec36a9b6773ef9 Mon Sep 17 00:00:00 2001 From: Jason Francis Date: Wed, 9 Mar 2022 12:48:04 -0500 Subject: [PATCH 2/4] glib: Finish adding all structured logging functions --- glib/Gir.toml | 1 + glib/src/auto/enums.rs | 58 +++++ glib/src/auto/mod.rs | 3 + glib/src/lib.rs | 14 +- glib/src/log.rs | 408 +++++++++++++++++++++++++++++++---- glib/tests/structured_log.rs | 136 ++++++++++++ 6 files changed, 572 insertions(+), 48 deletions(-) create mode 100644 glib/tests/structured_log.rs diff --git a/glib/Gir.toml b/glib/Gir.toml index 3359c0eae7af..da09ce4edd2b 100644 --- a/glib/Gir.toml +++ b/glib/Gir.toml @@ -20,6 +20,7 @@ generate = [ "GLib.KeyFileError", "GLib.KeyFileFlags", "GLib.LogLevelFlags", + "GLib.LogWriterOutput", "GLib.MainContextFlags", "GLib.OptionArg", "GLib.OptionFlags", diff --git a/glib/src/auto/enums.rs b/glib/src/auto/enums.rs index 14fd664e0b0b..e99d2d5af2de 100644 --- a/glib/src/auto/enums.rs +++ b/glib/src/auto/enums.rs @@ -354,6 +354,64 @@ impl ErrorDomain for KeyFileError { } } +#[cfg(any(feature = "v2_50", feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_50")))] +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] +#[non_exhaustive] +#[doc(alias = "GLogWriterOutput")] +pub enum LogWriterOutput { + #[doc(alias = "G_LOG_WRITER_HANDLED")] + Handled, + #[doc(alias = "G_LOG_WRITER_UNHANDLED")] + Unhandled, + #[doc(hidden)] + __Unknown(i32), +} + +#[cfg(any(feature = "v2_50", feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_50")))] +impl fmt::Display for LogWriterOutput { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "LogWriterOutput::{}", + match *self { + Self::Handled => "Handled", + Self::Unhandled => "Unhandled", + _ => "Unknown", + } + ) + } +} + +#[cfg(any(feature = "v2_50", feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_50")))] +#[doc(hidden)] +impl IntoGlib for LogWriterOutput { + type GlibType = ffi::GLogWriterOutput; + + fn into_glib(self) -> ffi::GLogWriterOutput { + match self { + Self::Handled => ffi::G_LOG_WRITER_HANDLED, + Self::Unhandled => ffi::G_LOG_WRITER_UNHANDLED, + Self::__Unknown(value) => value, + } + } +} + +#[cfg(any(feature = "v2_50", feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_50")))] +#[doc(hidden)] +impl FromGlib for LogWriterOutput { + unsafe fn from_glib(value: ffi::GLogWriterOutput) -> Self { + match value { + ffi::G_LOG_WRITER_HANDLED => Self::Handled, + ffi::G_LOG_WRITER_UNHANDLED => Self::Unhandled, + value => Self::__Unknown(value), + } + } +} + #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] #[non_exhaustive] #[doc(alias = "GOptionArg")] diff --git a/glib/src/auto/mod.rs b/glib/src/auto/mod.rs index 6b7efaa574f2..b68fcffa986c 100644 --- a/glib/src/auto/mod.rs +++ b/glib/src/auto/mod.rs @@ -35,6 +35,9 @@ pub use self::enums::ChecksumType; pub use self::enums::DateMonth; pub use self::enums::DateWeekday; pub use self::enums::KeyFileError; +#[cfg(any(feature = "v2_50", feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_50")))] +pub use self::enums::LogWriterOutput; pub use self::enums::OptionArg; pub use self::enums::SeekType; pub use self::enums::TimeType; diff --git a/glib/src/lib.rs b/glib/src/lib.rs index 2ab46a1f9e78..e6008e4efa7d 100644 --- a/glib/src/lib.rs +++ b/glib/src/lib.rs @@ -129,14 +129,24 @@ pub use self::quark::Quark; mod log; pub use self::log::log_set_handler; -// #[cfg(any(feature = "v2_50", feature = "dox"))] -// pub use log::log_variant; pub use self::log::{ log_default_handler, log_remove_handler, log_set_always_fatal, log_set_default_handler, log_set_fatal_mask, log_unset_default_handler, set_print_handler, set_printerr_handler, unset_print_handler, unset_printerr_handler, LogHandlerId, LogLevel, LogLevels, }; +#[cfg(any(feature = "v2_50", feature = "dox"))] +pub use self::log::{ + log_set_writer_func, log_structured_array, log_variant, log_writer_default, + log_writer_format_fields, log_writer_journald, log_writer_standard_streams, LogField, +}; + +#[cfg(any(all(unix, feature = "v2_50"), feature = "dox"))] +pub use self::log::{log_writer_is_journald, log_writer_supports_color}; + +#[cfg(any(feature = "v2_68", feature = "dox"))] +pub use self::log::{log_writer_default_set_use_stderr, log_writer_default_would_drop}; + #[doc(hidden)] #[cfg(any(feature = "dox", feature = "log_macros"))] #[cfg_attr(feature = "dox", doc(cfg(feature = "log_macros")))] diff --git a/glib/src/log.rs b/glib/src/log.rs index b8fccf71e76a..f402b4980773 100644 --- a/glib/src/log.rs +++ b/glib/src/log.rs @@ -2,10 +2,15 @@ use crate::translate::*; use crate::GString; +#[cfg(any(feature = "v2_50", feature = "dox"))] +use crate::{GStr, LogWriterOutput}; use once_cell::sync::Lazy; use std::boxed::Box as Box_; use std::sync::{Arc, Mutex}; +#[cfg(any(all(unix, feature = "v2_50"), feature = "dox"))] +use std::os::unix::io::AsRawFd; + #[derive(Debug)] pub struct LogHandlerId(u32); @@ -78,6 +83,20 @@ impl FromGlib for LogLevel { } } +impl LogLevel { + #[doc(hidden)] + pub fn priority(&self) -> &'static str { + match self { + Self::Error => "3", + Self::Critical => "4", + Self::Warning => "4", + Self::Message => "5", + Self::Info => "6", + Self::Debug => "7", + } + } +} + bitflags::bitflags! { #[doc(alias = "GLogLevelFlags")] pub struct LogLevels: u32 { @@ -142,7 +161,7 @@ pub fn log_set_handler, LogLevel, &str) + Send + Sync + 'stat let message: Borrowed = from_glib_borrow(message); let callback: &P = &*(user_data as *mut _); (*callback)( - (*log_domain).as_deref(), + (*log_domain).as_ref().map(|s| s.as_str()), from_glib(log_level), message.as_str(), ); @@ -190,17 +209,6 @@ pub fn log_set_fatal_mask(log_domain: Option<&str>, fatal_levels: LogLevels) -> } } -// #[cfg(any(feature = "v2_50", feature = "dox"))] -// pub fn log_variant(log_domain: Option<&str>, log_level: LogLevel, fields: &Variant) { -// unsafe { -// ffi::g_log_variant( -// log_domain.to_glib_none().0, -// log_level.into_glib(), -// fields.to_glib_none().0, -// ); -// } -// } - type PrintCallback = dyn Fn(&str) + Send + Sync + 'static; static PRINT_HANDLER: Lazy>>> = Lazy::new(|| Mutex::new(None)); @@ -292,7 +300,7 @@ pub fn log_set_default_handler, LogLevel, &str) + Send + Sync let log_domain: Borrowed> = from_glib_borrow(log_domain); let message: Borrowed = from_glib_borrow(message); (*callback)( - (*log_domain).as_deref(), + (*log_domain).as_ref().map(|s| s.as_str()), from_glib(log_levels), message.as_str(), ); @@ -328,6 +336,129 @@ pub fn log_default_handler(log_domain: Option<&str>, log_level: LogLevel, messag } } +// rustdoc-stripper-ignore-next +/// Structure representing a single field in a structured log entry. +/// +/// See [`g_log_structured`][gls] for details. Log fields may contain UTF-8 strings, binary with +/// embedded nul bytes, or arbitrary pointers. +/// +/// [gls]: https://docs.gtk.org/glib/func.log_structured.html +#[cfg(any(feature = "v2_50", feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_50")))] +#[repr(transparent)] +#[derive(Debug)] +#[doc(alias = "GLogField")] +pub struct LogField<'a>(ffi::GLogField, std::marker::PhantomData<&'a GStr>); + +#[cfg(any(feature = "v2_50", feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_50")))] +impl<'a> LogField<'a> { + // rustdoc-stripper-ignore-next + /// Creates a field from a borrowed key and value. + pub fn new(key: &'a GStr, value: &[u8]) -> Self { + let (value, length) = if value.len() > 0 { + (value, value.len().try_into().unwrap()) + } else { + // Use an empty C string to represent empty data, since length: 0 is reserved for user + // data fields. + (&[0u8] as &[u8], -1isize) + }; + Self( + ffi::GLogField { + key: key.as_ptr(), + value: value.as_ptr() as *const _, + length, + }, + Default::default(), + ) + } + // rustdoc-stripper-ignore-next + /// Creates a field with an empty value and `data` as a user data key. Fields created with this + /// function are ignored by the default log writer. These fields are used to pass custom data + /// into a writer function set with [`log_set_writer_func`], where it can be retreived using + /// [`Self::user_data`]. + /// + /// The passed `usize` can be used by the log writer as a key into a static data structure. + /// Thread locals are preferred since the log writer function will run in the same thread that + /// invoked [`log_structured_array`]. + pub fn new_user_data(key: &'a GStr, data: usize) -> Self { + Self( + ffi::GLogField { + key: key.as_ptr(), + value: data as *const _, + length: 0, + }, + Default::default(), + ) + } + // rustdoc-stripper-ignore-next + /// Retreives the field key. + pub fn key(&self) -> &str { + unsafe { std::ffi::CStr::from_ptr(self.0.key as *const _) } + .to_str() + .unwrap() + } + // rustdoc-stripper-ignore-next + /// Retrieves a byte array of the field value. Returns `None` if the field was created with + /// [`Self::new_user_data`]. + pub fn value_bytes(&self) -> Option<&[u8]> { + if self.0.length == 0 { + None + } else if self.0.length < 0 { + Some(unsafe { std::ffi::CStr::from_ptr(self.0.value as *const _) }.to_bytes()) + } else { + Some(unsafe { + std::slice::from_raw_parts(self.0.value as *const u8, self.0.length as usize) + }) + } + } + // rustdoc-stripper-ignore-next + /// Retrieves a string of the field value, or `None` if the string is not valid UTF-8. Also + /// returns `None` if the field was created with [`Self::new_user_data`]. + pub fn value_str(&self) -> Option<&str> { + std::str::from_utf8(self.value_bytes()?).ok() + } + // rustdoc-stripper-ignore-next + /// Retrieves the the user data value from a field created with [`Self::new_user_data`]. + /// Returns `None` if the field was created with [`Self::new`]. + pub fn user_data(&self) -> Option { + (self.0.length == 0).then(|| self.0.value as usize) + } +} + +#[cfg(any(feature = "v2_50", feature = "dox"))] +type WriterCallback = dyn Fn(LogLevel, &[LogField<'_>]) -> LogWriterOutput + Send + Sync + 'static; + +#[cfg(any(feature = "v2_50", feature = "dox"))] +static WRITER_FUNC: once_cell::sync::OnceCell> = + once_cell::sync::OnceCell::new(); + +#[cfg(any(feature = "v2_50", feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_50")))] +#[doc(alias = "g_log_set_writer_func")] +pub fn log_set_writer_func< + P: Fn(LogLevel, &[LogField<'_>]) -> LogWriterOutput + Send + Sync + 'static, +>( + writer_func: P, +) { + if WRITER_FUNC.set(Box::new(writer_func)).is_err() { + panic!("Writer func can only be set once"); + } + unsafe extern "C" fn writer_trampoline( + log_level: ffi::GLogLevelFlags, + fields: *const ffi::GLogField, + n_fields: libc::size_t, + _user_data: ffi::gpointer, + ) -> ffi::GLogWriterOutput { + let writer_func = WRITER_FUNC.get().unwrap(); + let fields = std::slice::from_raw_parts(fields as *const LogField<'_>, n_fields); + writer_func(from_glib(log_level), fields).into_glib() + } + unsafe { + ffi::g_log_set_writer_func(Some(writer_trampoline), std::ptr::null_mut(), None); + } +} + #[macro_export] #[doc(hidden)] macro_rules! g_log_inner { @@ -710,36 +841,221 @@ macro_rules! g_printerr { } // rustdoc-stripper-ignore-next -// /// Macro used to log using GLib logging system. It uses [g_log_structured][gls]. -// /// -// /// [gls]: https://developer.gnome.org/glib/stable/glib-Message-Logging.html#g-log-structured) -// /// -// /// Example: -// /// -// /// ```no_run -// /// use glib::{LogLevel, g_log_structured}; -// /// -// /// g_log_structured!("test", LogLevel::Debug, {"MESSAGE" => "tadam!"}); -// /// g_log_structured!("test", LogLevel::Debug, {"MESSAGE" => "tadam!", "random" => "yes"}); -// /// ``` -// #[cfg(any(feature = "v2_50", feature = "dox"))] -// #[macro_export] -// macro_rules! g_log_structured { -// ($log_domain:expr, $log_level:expr, {$($key:expr => $value:expr),+}) => {{ -// use $crate::translate::{Stash, IntoGlib, ToGlibPtr}; -// use $crate::LogLevel; -// use std::ffi::CString; - -// fn check_log_args(_log_domain: &str, _log_level: LogLevel) {} -// fn check_key(key: &str) -> Stash<*const i8, str> { key.to_glib_none() } - -// check_log_args(&$log_domain, $log_level); -// unsafe { -// ffi::g_log_structured( -// $log_domain.to_glib_none().0, -// $log_level.into_glib(), -// $(check_key($key).0, check_key(format!("{}", $value).as_str()).0 ),+ -// ) -// } -// }}; -// } +/// Macro used to log using GLib structured logging system. +/// +/// The structured data is provided inside braces as key-value pairs using the `=>` token and +/// separated by semicolons. The key can be a string literal or an expression that satisfies +/// [`AsRef`]. The value can be a format string with arguments, or a single expression that +/// satisfies `AsRef<[u8]>`. +/// +/// See [`g_log_structured`][gls] for more details. +/// +/// [gls]: https://docs.gtk.org/glib/func.log_structured.html +/// [`AsRef`]: crate::GStr +/// +/// Example: +/// +/// ```no_run +/// use glib::{GString, LogLevel, log_structured}; +/// use std::ffi::CString; +/// +/// log_structured!( +/// "test", +/// LogLevel::Debug, +/// { +/// // a normal string field +/// "MY_FIELD" => "123"; +/// // fields can also take format arguments +/// "MY_FIELD2" => "abc {}", "def"; +/// // single argument can be a &str or a &[u8] or anything else satsfying AsRef<[u8]> +/// "MY_FIELD3" => CString::new("my string").unwrap().to_bytes(); +/// // field names can also be dynamic +/// GString::from("MY_FIELD4") => b"a binary string".to_owned(); +/// // the main log message goes in the MESSAGE field +/// "MESSAGE" => "test: {} {}", 1, 2, ; +/// } +/// ); +/// ``` +#[macro_export] +macro_rules! log_structured { + ($log_domain:expr, $log_level:expr, {$($key:expr => $format:expr $(,$arg:expr)* $(,)?);+ $(;)?} $(,)?) => { + (|| { + let log_domain = as std::convert::From<_>>::from($log_domain); + let log_domain_str = log_domain.unwrap_or_default(); + let level: $crate::LogLevel = $log_level; + let field_count = + <[()]>::len(&[$($crate::log_structured_inner!(@clear $key)),+]) + + log_domain.map(|_| 2usize).unwrap_or(1usize); + + $crate::log_structured_array( + level, + &[ + $crate::LogField::new( + $crate::gstr!("PRIORITY"), + level.priority().as_bytes(), + ), + $( + $crate::LogField::new( + $crate::log_structured_inner!(@key $key), + $crate::log_structured_inner!(@value $format $(,$arg)*), + ), + )+ + $crate::LogField::new( + $crate::gstr!("GLIB_DOMAIN"), + log_domain_str.as_bytes(), + ), + ][0..field_count], + ) + })() + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! log_structured_inner { + (@clear $($_:tt)*) => { () }; + (@key $key:literal) => { $crate::gstr!($key) }; + (@key $key:expr) => { std::convert::AsRef::<$crate::GStr>::as_ref(&$key) }; + (@value $value:expr) => { std::convert::AsRef::<[u8]>::as_ref(&$value) }; + (@value $format:expr $(,$arg:expr)+) => { + { + let mut builder = $crate::GStringBuilder::default(); + if std::fmt::Write::write_fmt(&mut builder, format_args!($format, $($arg),+)).is_err() { + return; + } + builder.into_string() + }.as_str().as_bytes() + }; +} + +#[cfg(any(feature = "v2_50", feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_50")))] +#[doc(alias = "g_log_structured_array")] +#[inline] +pub fn log_structured_array(log_level: LogLevel, fields: &[LogField<'_>]) { + unsafe { + ffi::g_log_structured_array( + log_level.into_glib(), + fields.as_ptr() as *const ffi::GLogField, + fields.len(), + ) + } +} + +#[cfg(any(feature = "v2_50", feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_50")))] +#[doc(alias = "g_log_variant")] +#[inline] +pub fn log_variant(log_domain: Option<&str>, log_level: LogLevel, fields: &crate::Variant) { + unsafe { + ffi::g_log_variant( + log_domain.to_glib_none().0, + log_level.into_glib(), + fields.to_glib_none().0, + ); + } +} + +#[cfg(any(all(unix, feature = "v2_50"), feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(all(unix, feature = "v2_50"))))] +#[doc(alias = "g_log_writer_supports_color")] +#[inline] +pub fn log_writer_supports_color(output_fd: T) -> bool { + unsafe { from_glib(ffi::g_log_writer_supports_color(output_fd.as_raw_fd())) } +} + +#[cfg(any(all(unix, feature = "v2_50"), feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(all(unix, feature = "v2_50"))))] +#[doc(alias = "g_log_writer_is_journald")] +#[inline] +pub fn log_writer_is_journald(output_fd: T) -> bool { + unsafe { from_glib(ffi::g_log_writer_is_journald(output_fd.as_raw_fd())) } +} + +#[cfg(any(feature = "v2_50", feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_50")))] +#[doc(alias = "g_log_writer_format_fields")] +#[inline] +pub fn log_writer_format_fields( + log_level: LogLevel, + fields: &[LogField<'_>], + use_color: bool, +) -> GString { + unsafe { + from_glib_full(ffi::g_log_writer_format_fields( + log_level.into_glib(), + fields.as_ptr() as *const ffi::GLogField, + fields.len(), + use_color.into_glib(), + )) + } +} + +#[cfg(any(feature = "v2_50", feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_50")))] +#[doc(alias = "g_log_writer_journald")] +#[inline] +pub fn log_writer_journald(log_level: LogLevel, fields: &[LogField<'_>]) -> LogWriterOutput { + unsafe { + from_glib(ffi::g_log_writer_journald( + log_level.into_glib(), + fields.as_ptr() as *const ffi::GLogField, + fields.len(), + std::ptr::null_mut(), + )) + } +} + +#[cfg(any(feature = "v2_50", feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_50")))] +#[doc(alias = "g_log_writer_standard_streams")] +#[inline] +pub fn log_writer_standard_streams( + log_level: LogLevel, + fields: &[LogField<'_>], +) -> LogWriterOutput { + unsafe { + from_glib(ffi::g_log_writer_standard_streams( + log_level.into_glib(), + fields.as_ptr() as *const ffi::GLogField, + fields.len(), + std::ptr::null_mut(), + )) + } +} + +#[cfg(any(feature = "v2_50", feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_50")))] +#[doc(alias = "g_log_writer_default")] +#[inline] +pub fn log_writer_default(log_level: LogLevel, fields: &[LogField<'_>]) -> LogWriterOutput { + unsafe { + from_glib(ffi::g_log_writer_default( + log_level.into_glib(), + fields.as_ptr() as *const ffi::GLogField, + fields.len(), + std::ptr::null_mut(), + )) + } +} + +#[cfg(any(feature = "v2_68", feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_68")))] +#[doc(alias = "g_log_writer_default_set_use_stderr")] +#[inline] +pub unsafe fn log_writer_default_set_use_stderr(use_stderr: bool) { + ffi::g_log_writer_default_set_use_stderr(use_stderr.into_glib()); +} + +#[cfg(any(feature = "v2_68", feature = "dox"))] +#[cfg_attr(feature = "dox", doc(cfg(feature = "v2_68")))] +#[doc(alias = "g_log_writer_default_would_drop")] +#[inline] +pub fn log_writer_default_would_drop(log_level: LogLevel, log_domain: Option<&str>) -> bool { + unsafe { + from_glib(ffi::g_log_writer_default_would_drop( + log_level.into_glib(), + log_domain.to_glib_none().0, + )) + } +} diff --git a/glib/tests/structured_log.rs b/glib/tests/structured_log.rs new file mode 100644 index 000000000000..b49e9ece4891 --- /dev/null +++ b/glib/tests/structured_log.rs @@ -0,0 +1,136 @@ +#[cfg(feature = "v2_50")] +#[test] +fn structured_log() { + use glib::*; + use std::sync::{Arc, Mutex}; + + let log = Arc::new(Mutex::new(Vec::new())); + { + let log = log.clone(); + // can only be called once per test file + log_set_writer_func(move |level, fields| { + let fields = fields + .iter() + .map(|f| { + let value = if let Some(data) = f.user_data() { + assert!(f.value_str().is_none()); + format!("USERDATA: {}", data) + } else { + f.value_str().unwrap().to_owned() + }; + (f.key().to_owned(), value) + }) + .collect::>(); + log.lock().unwrap().push((level, fields)); + LogWriterOutput::Handled + }); + } + log_structured!( + "test", + LogLevel::Message, + { + "MY_META" => "abc"; + "MESSAGE" => "normal with meta"; + "MY_META2" => "def"; + } + ); + + log_structured!( + None, + LogLevel::Message, + { + "MY_META" => "abc"; + "MESSAGE" => "formatted with meta: {} {}", 123, 456.0; + "MY_META2" => "def{}", "ghi"; + "EMPTY" => b""; + GString::from("MY_META3") => b"bstring".to_owned(); + } + ); + log_structured_array( + LogLevel::Warning, + &[ + LogField::new( + gstr!("MESSAGE_ID"), + "1e45a69523d3460680e2721d3072408f".as_bytes(), + ), + LogField::new(gstr!("PRIORITY"), "4".as_bytes()), + LogField::new(gstr!("MESSAGE"), "from array".as_bytes()), + LogField::new_user_data(gstr!("SOMEDATA"), 12345), + ], + ); + let dict = VariantDict::new(None); + dict.insert_value( + "MESSAGE_ID", + &"9e093d0fac2f4d50838a649796ab154b".to_variant(), + ); + dict.insert_value("RELEASE", &"true".to_variant()); + dict.insert_value("MY_BYTES", &"123".as_bytes().to_variant()); + dict.insert_value("MESSAGE", &"from variant".to_variant()); + log_variant(Some("test"), LogLevel::Debug, &dict.end()); + + let log = std::mem::take(&mut *log.lock().unwrap()); + let log = log + .iter() + .map(|(l, v)| { + ( + *l, + v.iter() + .map(|(k, v)| (k.as_str(), v.as_str())) + .collect::>(), + ) + }) + .collect::>(); + assert_eq!( + log[0], + ( + LogLevel::Message, + vec![ + ("PRIORITY", "5" as &str), + ("MY_META", "abc"), + ("MESSAGE", "normal with meta"), + ("MY_META2", "def"), + ("GLIB_DOMAIN", "test"), + ] + ), + ); + assert_eq!( + log[1], + ( + LogLevel::Message, + vec![ + ("PRIORITY", "5" as &str), + ("MY_META", "abc"), + ("MESSAGE", "formatted with meta: 123 456"), + ("MY_META2", "defghi"), + ("EMPTY", ""), + ("MY_META3", "bstring"), + ] + ) + ); + assert_eq!( + log[2], + ( + LogLevel::Warning, + vec![ + ("MESSAGE_ID", "1e45a69523d3460680e2721d3072408f" as &str), + ("PRIORITY", "4"), + ("MESSAGE", "from array"), + ("SOMEDATA", "USERDATA: 12345"), + ] + ) + ); + assert_eq!( + log[3], + ( + LogLevel::Debug, + vec![ + ("PRIORITY", "7" as &str), + ("GLIB_DOMAIN", "test"), + ("MESSAGE_ID", "9e093d0fac2f4d50838a649796ab154b"), + ("MY_BYTES", "123"), + ("RELEASE", "true"), + ("MESSAGE", "from variant"), + ] + ) + ); +} From 8e70ed14120eb6e8d62ed13bc0a569a813699958 Mon Sep 17 00:00:00 2001 From: Jason Francis Date: Wed, 9 Mar 2022 12:50:13 -0500 Subject: [PATCH 3/4] glib: Simplify g_log_inner and g_print_inner Avoids bringing the fmt::Write trait into scope. --- glib/src/log.rs | 40 +++++++++++++++------------------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/glib/src/log.rs b/glib/src/log.rs index f402b4980773..9dfb7d7d2dc5 100644 --- a/glib/src/log.rs +++ b/glib/src/log.rs @@ -463,23 +463,20 @@ pub fn log_set_writer_func< #[doc(hidden)] macro_rules! g_log_inner { ($log_domain:expr, $log_level:expr, $format:literal $(,$arg:expr)* $(,)?) => {{ - use $crate::translate::{IntoGlib, ToGlibPtr}; - use $crate::LogLevel; - use std::fmt::{self, Write}; - - fn check_log_args(_log_level: LogLevel, _format: &str) {} - - check_log_args($log_level, $format); let mut w = $crate::GStringBuilder::default(); // Can't really happen but better safe than sorry - if !std::write!(&mut w, $format, $($arg),*).is_err() { + if !std::fmt::Write::write_fmt(&mut w, std::format_args!($format, $($arg),*)).is_err() { unsafe { $crate::ffi::g_log( - $log_domain, - $log_level.into_glib(), + $crate::translate::ToGlibPtr::to_glib_none(&$log_domain).0, + <$crate::LogLevel as $crate::translate::IntoGlib>::into_glib( + $log_level + ), b"%s\0".as_ptr() as *const _, - ToGlibPtr::<*const std::os::raw::c_char>::to_glib_none(&w.into_string()).0, + $crate::translate::ToGlibPtr::<*const std::os::raw::c_char>::to_glib_none( + &w.into_string() + ).0, ); } } @@ -529,13 +526,11 @@ macro_rules! g_log_inner { #[macro_export] macro_rules! g_log { ($log_level:expr, $format:literal $(,$arg:expr)* $(,)?) => {{ - $crate::g_log_inner!(std::ptr::null(), $log_level, $format, $($arg),*); + $crate::g_log_inner!(None::<&str>, $log_level, $format, $($arg),*); }}; ($log_domain:expr, $log_level:expr, $format:literal $(,$arg:expr)* $(,)?) => {{ - use $crate::translate::{IntoGlib, ToGlibPtr}; - - let log_domain: Option<&str> = $log_domain.into(); - $crate::g_log_inner!(log_domain.to_glib_none().0, $log_level, $format, $($arg),*); + let log_domain = as std::convert::From<_>>::from($log_domain); + $crate::g_log_inner!(log_domain, $log_level, $format, $($arg),*); }}; } @@ -765,21 +760,16 @@ macro_rules! g_debug { #[macro_export] macro_rules! g_print_inner { ($func:ident, $format:expr $(, $arg:expr)* $(,)?) => {{ - use $crate::translate::{IntoGlib, ToGlibPtr}; - use $crate::LogLevel; - use std::fmt::{self, Write}; - - fn check_arg(_format: &str) {} - - check_arg($format); let mut w = $crate::GStringBuilder::default(); // Can't really happen but better safe than sorry - if !std::write!(&mut w, $format, $($arg),*).is_err() { + if !std::fmt::Write::write_fmt(&mut w, std::format_args!($format, $($arg),*)).is_err() { unsafe { $crate::ffi::$func( b"%s\0".as_ptr() as *const _, - ToGlibPtr::<*const std::os::raw::c_char>::to_glib_none(&w.into_string()).0, + $crate::translate::ToGlibPtr::<*const std::os::raw::c_char>::to_glib_none( + &w.into_string() + ).0, ); } } From 50cc4732a8327a23a4799aa3ab75e2a0d9ca77ff Mon Sep 17 00:00:00 2001 From: Jason Francis Date: Wed, 9 Mar 2022 12:52:16 -0500 Subject: [PATCH 4/4] glib: Optimize GlibLogger to avoid some copies --- glib/src/bridged_logging.rs | 113 +++++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 46 deletions(-) diff --git a/glib/src/bridged_logging.rs b/glib/src/bridged_logging.rs index 53d2510c2e8a..c7a8ccf38b9b 100644 --- a/glib/src/bridged_logging.rs +++ b/glib/src/bridged_logging.rs @@ -19,8 +19,9 @@ pub enum GlibLoggerFormat { LineAndFile, // rustdoc-stripper-ignore-next /// A logger using glib structured logging. Structured logging is available - /// only on features `v2_56` and later. - #[cfg(any(feature = "v2_56", feature = "dox"))] + /// only on features `v2_50` and later. + #[cfg(any(feature = "v2_50", feature = "dox"))] + #[cfg_attr(feature = "dox", doc(cfg(feature = "v2_50")))] Structured, } @@ -104,15 +105,15 @@ impl GlibLogger { Self { format, domain } } - fn level_to_glib(level: rs_log::Level) -> crate::ffi::GLogLevelFlags { + fn level_to_glib(level: rs_log::Level) -> crate::LogLevel { match level { // Errors are mapped to critical to avoid automatic termination - rs_log::Level::Error => crate::ffi::G_LOG_LEVEL_CRITICAL, - rs_log::Level::Warn => crate::ffi::G_LOG_LEVEL_WARNING, - rs_log::Level::Info => crate::ffi::G_LOG_LEVEL_INFO, - rs_log::Level::Debug => crate::ffi::G_LOG_LEVEL_DEBUG, + rs_log::Level::Error => crate::LogLevel::Critical, + rs_log::Level::Warn => crate::LogLevel::Warning, + rs_log::Level::Info => crate::LogLevel::Info, + rs_log::Level::Debug => crate::LogLevel::Debug, // There is no equivalent to trace level in glib - rs_log::Level::Trace => crate::ffi::G_LOG_LEVEL_DEBUG, + rs_log::Level::Trace => crate::LogLevel::Debug, } } @@ -121,16 +122,14 @@ impl GlibLogger { unsafe { crate::ffi::g_log( domain.to_glib_none().0, - GlibLogger::level_to_glib(level), + GlibLogger::level_to_glib(level).into_glib(), b"%s\0".as_ptr() as *const _, ToGlibPtr::<*const std::os::raw::c_char>::to_glib_none(message).0, ); } } - #[cfg(any(feature = "v2_56", feature = "dox"))] - #[cfg_attr(feature = "dox", doc(cfg(feature = "v2_56")))] - #[doc(alias = "g_log_structured_standard")] + #[cfg(any(feature = "v2_50", feature = "dox"))] fn write_log_structured( domain: Option<&str>, level: rs_log::Level, @@ -139,24 +138,19 @@ impl GlibLogger { func: Option<&str>, message: &str, ) { - let line_str = line.map(|l| l.to_string()); + let line = line.map(|l| l.to_string()); + let line = line.as_ref().map(|s| s.as_str()); - let domain = domain.unwrap_or("default"); - let file = file.unwrap_or(""); - let line_str = line_str.unwrap_or_else(|| String::from("")); - let func = func.unwrap_or(""); - - unsafe { - crate::ffi::g_log_structured_standard( - domain.to_glib_none().0, - GlibLogger::level_to_glib(level), - file.to_glib_none().0, - line_str.to_glib_none().0, - func.to_glib_none().0, - b"%s\0".as_ptr() as *const _, - ToGlibPtr::<*const std::os::raw::c_char>::to_glib_none(message).0, - ); - } + crate::log_structured!( + domain.unwrap_or("default"), + GlibLogger::level_to_glib(level), + { + "CODE_FILE" => file.unwrap_or(""); + "CODE_LINE" => line.unwrap_or(""); + "CODE_FUNC" => func.unwrap_or(""); + "MESSAGE" => message; + } + ); } } @@ -178,28 +172,55 @@ impl rs_log::Log for GlibLogger { match self.format { GlibLoggerFormat::Plain => { - let s = format!("{}", record.args()); - GlibLogger::write_log(domain, record.level(), &s) + let args = record.args(); + if let Some(s) = args.as_str() { + GlibLogger::write_log(domain, record.level(), s); + } else { + GlibLogger::write_log(domain, record.level(), &args.to_string()); + } } GlibLoggerFormat::LineAndFile => { - let s = match (record.file(), record.line()) { - (Some(file), Some(line)) => format!("{}:{}: {}", file, line, record.args()), - (Some(file), None) => format!("{}: {}", file, record.args()), - _ => format!("{}", record.args()), + match (record.file(), record.line()) { + (Some(file), Some(line)) => { + let s = format!("{}:{}: {}", file, line, record.args()); + GlibLogger::write_log(domain, record.level(), &s); + } + (Some(file), None) => { + let s = format!("{}: {}", file, record.args()); + GlibLogger::write_log(domain, record.level(), &s); + } + _ => { + let args = record.args(); + if let Some(s) = args.as_str() { + GlibLogger::write_log(domain, record.level(), s); + } else { + GlibLogger::write_log(domain, record.level(), &args.to_string()); + } + } }; - - GlibLogger::write_log(domain, record.level(), &s); } - #[cfg(any(feature = "v2_56", feature = "dox"))] + #[cfg(any(feature = "v2_50", feature = "dox"))] GlibLoggerFormat::Structured => { - GlibLogger::write_log_structured( - domain, - record.level(), - record.file(), - record.line(), - record.module_path(), - &format!("{}", record.args()), - ); + let args = record.args(); + if let Some(s) = args.as_str() { + GlibLogger::write_log_structured( + domain, + record.level(), + record.file(), + record.line(), + record.module_path(), + s, + ); + } else { + GlibLogger::write_log_structured( + domain, + record.level(), + record.file(), + record.line(), + record.module_path(), + &args.to_string(), + ); + } } }; }