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: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1426>
This commit is contained in:
Nick Steel 2024-04-21 00:12:31 +01:00
parent 88a6977777
commit b7b5352353
4 changed files with 117 additions and 0 deletions

1
Cargo.lock generated
View file

@ -842,6 +842,7 @@ dependencies = [
"gstreamer-sys", "gstreamer-sys",
"itertools", "itertools",
"libc", "libc",
"log",
"muldiv", "muldiv",
"num-integer", "num-integer",
"num-rational", "num-rational",

View file

@ -23,6 +23,7 @@ num-rational = { version = "0.4", default-features = false, features = [] }
futures-core = "0.3" futures-core = "0.3"
futures-channel = "0.3" futures-channel = "0.3"
futures-util = { version = "0.3", default-features = false } futures-util = { version = "0.3", default-features = false }
log = { version = "0.4", optional = true }
muldiv = "1" muldiv = "1"
opt-ops = { package = "option-operations", version = "0.5" } opt-ops = { package = "option-operations", version = "0.5" }
serde = { version = "1.0", optional = true, features = ["derive"] } 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_22 = ["ffi/v1_22", "v1_20"]
v1_24 = ["ffi/v1_24", "v1_22"] v1_24 = ["ffi/v1_24", "v1_22"]
serde = ["num-rational/serde", "dep:serde", "serde_bytes"] serde = ["num-rational/serde", "dep:serde", "serde_bytes"]
log = ["dep:log"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View file

@ -49,6 +49,8 @@ mod serde_macros;
#[macro_use] #[macro_use]
pub mod log; pub mod log;
#[cfg(feature = "log")]
pub use crate::log::DebugCategoryLogger;
pub use crate::log::{ pub use crate::log::{
DebugCategory, DebugLogFunction, DebugMessage, LoggedObject, CAT_BUFFER, CAT_BUFFER_LIST, 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, CAT_BUS, CAT_CALL_TRACE, CAT_CAPS, CAT_CLOCK, CAT_CONTEXT, CAT_DEFAULT, CAT_ELEMENT_PADS,

View file

@ -4,6 +4,8 @@ use std::{borrow::Cow, ffi::CStr, fmt, ptr};
use glib::{ffi::gpointer, prelude::*, translate::*}; use glib::{ffi::gpointer, prelude::*, translate::*};
use libc::c_char; use libc::c_char;
#[cfg(feature = "log")]
use log;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::DebugLevel; 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<T>( unsafe extern "C" fn log_handler<T>(
category: *mut ffi::GstDebugCategory, category: *mut ffi::GstDebugCategory,
level: ffi::GstDebugLevel, level: ffi::GstDebugLevel,
@ -1308,6 +1361,65 @@ mod tests {
memdump!(cat, obj: obj, "meh"); memdump!(cat, obj: obj, "meh");
} }
#[cfg(feature = "log")]
static LOGGER: Lazy<DebugCategoryLogger> = 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] #[test]
fn log_handler() { fn log_handler() {
crate::init().unwrap(); crate::init().unwrap();