diff --git a/gstreamer/Cargo.toml b/gstreamer/Cargo.toml index d6e20807b..d31b490e0 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"] } @@ -39,6 +40,7 @@ ron = "0.8" serde_json = "1.0" futures-executor = "0.3.1" gir-format-check = "0.1" +serial_test = "3" [features] default = [] @@ -48,6 +50,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..5937f362e 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 { + Self(cat) + } + + fn to_level(level: log::Level) -> crate::DebugLevel { + 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 { + let lvl = DebugCategoryLogger::to_level(metadata.level()); + self.0.above_threshold(lvl) + } + + fn log(&self, record: &log::Record) { + if !self.enabled(record.metadata()) { + return; + } + let lvl = DebugCategoryLogger::to_level(record.level()); + record.file().unwrap_or("").run_with_gstr(|file| { + self.0.log( + None::<&glib::Object>, + lvl, + file, + record.module_path().unwrap_or(""), + record.line().unwrap_or(0), + record.args().clone(), + ); + }); + } + + fn flush(&self) {} +} + unsafe extern "C" fn log_handler( category: *mut ffi::GstDebugCategory, level: ffi::GstDebugLevel, @@ -1245,6 +1298,7 @@ pub fn remove_log_function(log_fn: DebugLogFunction) { #[cfg(test)] mod tests { + use serial_test::serial; use std::sync::{mpsc, Arc, Mutex}; use super::*; @@ -1308,7 +1362,68 @@ 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] + #[serial] + #[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] + #[serial] fn log_handler() { crate::init().unwrap();