From b7b5352353e147fccc00ff684dffb65c21a6e6b1 Mon Sep 17 00:00:00 2001 From: Nick Steel Date: Sun, 21 Apr 2024 00:12:31 +0100 Subject: [PATCH] log: `Log` trait adapter around the GStreamer debug system Allows usage of normal `log` crate macros, and for other crates using those macros to have their log messages go to the GStreamer debug logs. This implementation is based on the one found in Servo. Fixes #187 DebugCategoryLogger is optional via 'log' feature check category above threshold skip_assert_initialized for constructor and helper Part-of: --- Cargo.lock | 1 + gstreamer/Cargo.toml | 2 + gstreamer/src/lib.rs | 2 + gstreamer/src/log.rs | 112 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 8783c0668..326c61c9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -842,6 +842,7 @@ dependencies = [ "gstreamer-sys", "itertools", "libc", + "log", "muldiv", "num-integer", "num-rational", diff --git a/gstreamer/Cargo.toml b/gstreamer/Cargo.toml index d6e20807b..214964006 100644 --- a/gstreamer/Cargo.toml +++ b/gstreamer/Cargo.toml @@ -23,6 +23,7 @@ num-rational = { version = "0.4", default-features = false, features = [] } futures-core = "0.3" futures-channel = "0.3" futures-util = { version = "0.3", default-features = false } +log = { version = "0.4", optional = true } muldiv = "1" opt-ops = { package = "option-operations", version = "0.5" } serde = { version = "1.0", optional = true, features = ["derive"] } @@ -48,6 +49,7 @@ v1_20 = ["ffi/v1_20", "v1_18"] v1_22 = ["ffi/v1_22", "v1_20"] v1_24 = ["ffi/v1_24", "v1_22"] serde = ["num-rational/serde", "dep:serde", "serde_bytes"] +log = ["dep:log"] [package.metadata.docs.rs] all-features = true diff --git a/gstreamer/src/lib.rs b/gstreamer/src/lib.rs index 2d65fa6ad..d3cf22f01 100644 --- a/gstreamer/src/lib.rs +++ b/gstreamer/src/lib.rs @@ -49,6 +49,8 @@ mod serde_macros; #[macro_use] pub mod log; +#[cfg(feature = "log")] +pub use crate::log::DebugCategoryLogger; pub use crate::log::{ DebugCategory, DebugLogFunction, DebugMessage, LoggedObject, CAT_BUFFER, CAT_BUFFER_LIST, CAT_BUS, CAT_CALL_TRACE, CAT_CAPS, CAT_CLOCK, CAT_CONTEXT, CAT_DEFAULT, CAT_ELEMENT_PADS, diff --git a/gstreamer/src/log.rs b/gstreamer/src/log.rs index 8c90f5d8d..68469371b 100644 --- a/gstreamer/src/log.rs +++ b/gstreamer/src/log.rs @@ -4,6 +4,8 @@ use std::{borrow::Cow, ffi::CStr, fmt, ptr}; use glib::{ffi::gpointer, prelude::*, translate::*}; use libc::c_char; +#[cfg(feature = "log")] +use log; use once_cell::sync::Lazy; use crate::DebugLevel; @@ -1064,6 +1066,57 @@ macro_rules! log_with_level( }}; ); +#[cfg(feature = "log")] +#[cfg_attr(docsrs, doc(cfg(feature = "log")))] +#[derive(Debug)] +pub struct DebugCategoryLogger(DebugCategory); + +#[cfg(feature = "log")] +#[cfg_attr(docsrs, doc(cfg(feature = "log")))] +impl DebugCategoryLogger { + pub fn new(cat: DebugCategory) -> Self { + skip_assert_initialized!(); + Self(cat) + } + + fn to_level(level: log::Level) -> crate::DebugLevel { + skip_assert_initialized!(); + match level { + log::Level::Error => DebugLevel::Error, + log::Level::Warn => DebugLevel::Warning, + log::Level::Info => DebugLevel::Info, + log::Level::Debug => DebugLevel::Debug, + log::Level::Trace => DebugLevel::Trace, + } + } +} + +#[cfg(feature = "log")] +#[cfg_attr(docsrs, doc(cfg(feature = "log")))] +impl log::Log for DebugCategoryLogger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + self.0.above_threshold(Self::to_level(metadata.level())) + } + + fn log(&self, record: &log::Record) { + if !self.enabled(record.metadata()) { + return; + } + record.file().unwrap_or("").run_with_gstr(|file| { + self.0.log( + None::<&glib::Object>, + Self::to_level(record.level()), + file, + record.module_path().unwrap_or(""), + record.line().unwrap_or(0), + *record.args(), + ); + }); + } + + fn flush(&self) {} +} + unsafe extern "C" fn log_handler( category: *mut ffi::GstDebugCategory, level: ffi::GstDebugLevel, @@ -1308,6 +1361,65 @@ mod tests { memdump!(cat, obj: obj, "meh"); } + #[cfg(feature = "log")] + static LOGGER: Lazy = Lazy::new(|| { + DebugCategoryLogger::new(DebugCategory::new( + "Log_trait", + crate::DebugColorFlags::empty(), + Some("Using the Log trait"), + )) + }); + + #[test] + #[cfg(feature = "log")] + fn log_trait() { + crate::init().unwrap(); + + log::set_logger(&(*LOGGER)).expect("Failed to set logger"); + log::set_max_level(log::LevelFilter::Trace); + log::error!("meh"); + log::warn!("fish"); + + let (sender, receiver) = mpsc::channel(); + let sender = Arc::new(Mutex::new(sender)); + let handler = move |category: DebugCategory, + level: DebugLevel, + _file: &glib::GStr, + _function: &glib::GStr, + _line: u32, + _object: Option<&LoggedObject>, + message: &DebugMessage| { + let cat = DebugCategory::get("Log_trait").unwrap(); + + if category != cat { + // This test can run in parallel with other tests, including new_and_log above. + // We cannot be certain we only see our own messages. + return; + } + + assert_eq!(level, DebugLevel::Error); + assert_eq!(message.get().unwrap().as_ref(), "meh"); + let _ = sender.lock().unwrap().send(()); + }; + + remove_default_log_function(); + add_log_function(handler); + + let cat = LOGGER.0; + + cat.set_threshold(crate::DebugLevel::Warning); + log::error!("meh"); + receiver.recv().unwrap(); + + cat.set_threshold(crate::DebugLevel::Error); + log::error!("meh"); + receiver.recv().unwrap(); + + cat.set_threshold(crate::DebugLevel::None); + log::error!("fish"); + log::warn!("meh"); + } + #[test] fn log_handler() { crate::init().unwrap();