gstreamer/
log.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{borrow::Cow, ffi::CStr, fmt, ptr};
4
5use glib::{ffi::gpointer, prelude::*, translate::*};
6use libc::c_char;
7#[cfg(feature = "log")]
8use log;
9use std::sync::LazyLock;
10
11use crate::{ffi, DebugLevel};
12
13// import and rename those so they are namespaced as log::*
14pub use crate::auto::functions::debug_add_ring_buffer_logger as add_ring_buffer_logger;
15pub use crate::auto::functions::debug_get_default_threshold as get_default_threshold;
16pub use crate::auto::functions::debug_get_stack_trace as get_stack_trace;
17pub use crate::auto::functions::debug_is_active as is_active;
18pub use crate::auto::functions::debug_is_colored as is_colored;
19pub use crate::auto::functions::debug_print_stack_trace as print_stack_trace;
20pub use crate::auto::functions::debug_remove_ring_buffer_logger as remove_ring_buffer_logger;
21pub use crate::auto::functions::debug_ring_buffer_logger_get_logs as ring_buffer_logger_get_logs;
22pub use crate::auto::functions::debug_set_active as set_active;
23pub use crate::auto::functions::debug_set_colored as set_colored;
24pub use crate::auto::functions::debug_set_default_threshold as set_default_threshold;
25pub use crate::auto::functions::debug_set_threshold_for_name as set_threshold_for_name;
26pub use crate::auto::functions::debug_set_threshold_from_string as set_threshold_from_string;
27pub use crate::auto::functions::debug_unset_threshold_for_name as unset_threshold_for_name;
28
29#[derive(PartialEq, Eq)]
30#[doc(alias = "GstDebugMessage")]
31pub struct DebugMessage(ptr::NonNull<ffi::GstDebugMessage>);
32
33impl fmt::Debug for DebugMessage {
34    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35        f.debug_tuple("DebugMessage").field(&self.get()).finish()
36    }
37}
38
39impl DebugMessage {
40    #[doc(alias = "gst_debug_message_get")]
41    #[inline]
42    pub fn get(&self) -> Option<Cow<'_, glib::GStr>> {
43        unsafe {
44            let message = ffi::gst_debug_message_get(self.0.as_ptr());
45
46            if message.is_null() {
47                None
48            } else {
49                Some(glib::GStr::from_ptr_lossy(message))
50            }
51        }
52    }
53
54    #[cfg(feature = "v1_22")]
55    #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
56    #[doc(alias = "gst_debug_message_get_id")]
57    #[inline]
58    pub fn id(&self) -> Option<&glib::GStr> {
59        unsafe {
60            let id = ffi::gst_debug_message_get_id(self.0.as_ptr());
61
62            if id.is_null() {
63                None
64            } else {
65                Some(glib::GStr::from_ptr(id))
66            }
67        }
68    }
69
70    #[inline]
71    pub fn as_ptr(&self) -> *mut ffi::GstDebugMessage {
72        self.0.as_ptr()
73    }
74}
75
76#[derive(PartialEq, Eq, Clone, Copy, Hash)]
77#[doc(alias = "GstDebugCategory")]
78#[repr(transparent)]
79pub struct DebugCategory(Option<ptr::NonNull<ffi::GstDebugCategory>>);
80
81impl DebugCategory {
82    #[doc(alias = "gst_debug_category_new")]
83    #[doc(alias = "GST_DEBUG_CATEGORY")]
84    #[doc(alias = "GST_DEBUG_CATEGORY_INIT")]
85    pub fn new(
86        name: &str,
87        color: crate::DebugColorFlags,
88        description: Option<&str>,
89    ) -> DebugCategory {
90        skip_assert_initialized!();
91        extern "C" {
92            fn _gst_debug_category_new(
93                name: *const c_char,
94                color: ffi::GstDebugColorFlags,
95                description: *const c_char,
96            ) -> *mut ffi::GstDebugCategory;
97        }
98
99        // Gets the category if it exists already
100        unsafe {
101            let ptr = name.run_with_gstr(|name| {
102                description.run_with_gstr(|description| {
103                    _gst_debug_category_new(
104                        name.to_glib_none().0,
105                        color.into_glib(),
106                        description.to_glib_none().0,
107                    )
108                })
109            });
110
111            // Can be NULL if the debug system is compiled out
112            DebugCategory(ptr::NonNull::new(ptr))
113        }
114    }
115
116    #[doc(alias = "gst_debug_get_category")]
117    #[inline]
118    pub fn get(name: &str) -> Option<DebugCategory> {
119        skip_assert_initialized!();
120        unsafe {
121            extern "C" {
122                fn _gst_debug_get_category(name: *const c_char) -> *mut ffi::GstDebugCategory;
123            }
124
125            let cat = name.run_with_gstr(|name| _gst_debug_get_category(name.to_glib_none().0));
126
127            if cat.is_null() {
128                None
129            } else {
130                Some(DebugCategory(Some(ptr::NonNull::new_unchecked(cat))))
131            }
132        }
133    }
134
135    #[doc(alias = "get_threshold")]
136    #[doc(alias = "gst_debug_category_get_threshold")]
137    #[inline]
138    pub fn threshold(self) -> crate::DebugLevel {
139        match self.0 {
140            Some(cat) => unsafe { from_glib(cat.as_ref().threshold) },
141            None => crate::DebugLevel::None,
142        }
143    }
144
145    #[doc(alias = "gst_debug_category_set_threshold")]
146    #[inline]
147    pub fn set_threshold(self, threshold: crate::DebugLevel) {
148        if let Some(cat) = self.0 {
149            unsafe { ffi::gst_debug_category_set_threshold(cat.as_ptr(), threshold.into_glib()) }
150        }
151    }
152
153    #[doc(alias = "gst_debug_category_reset_threshold")]
154    #[inline]
155    pub fn reset_threshold(self) {
156        if let Some(cat) = self.0 {
157            unsafe { ffi::gst_debug_category_reset_threshold(cat.as_ptr()) }
158        }
159    }
160
161    #[doc(alias = "get_color")]
162    #[doc(alias = "gst_debug_category_get_color")]
163    #[inline]
164    pub fn color(self) -> crate::DebugColorFlags {
165        match self.0 {
166            Some(cat) => unsafe { from_glib(cat.as_ref().color) },
167            None => crate::DebugColorFlags::empty(),
168        }
169    }
170
171    #[doc(alias = "get_name")]
172    #[doc(alias = "gst_debug_category_get_name")]
173    #[inline]
174    pub fn name<'a>(self) -> &'a str {
175        match self.0 {
176            Some(cat) => unsafe { CStr::from_ptr(cat.as_ref().name).to_str().unwrap() },
177            None => "",
178        }
179    }
180
181    #[doc(alias = "get_description")]
182    #[doc(alias = "gst_debug_category_get_description")]
183    #[inline]
184    pub fn description<'a>(self) -> Option<&'a str> {
185        let cat = self.0?;
186
187        unsafe {
188            let ptr = cat.as_ref().description;
189
190            if ptr.is_null() {
191                None
192            } else {
193                Some(CStr::from_ptr(ptr).to_str().unwrap())
194            }
195        }
196    }
197
198    #[inline]
199    #[doc(alias = "gst_debug_log")]
200    #[doc(alias = "gst_debug_log_literal")]
201    pub fn log(
202        self,
203        obj: Option<&impl IsA<glib::Object>>,
204        level: crate::DebugLevel,
205        file: &glib::GStr,
206        function: &str,
207        line: u32,
208        args: fmt::Arguments,
209    ) {
210        if !self.above_threshold(level) {
211            return;
212        }
213
214        self.log_unfiltered_internal(
215            obj.map(|obj| obj.as_ref()),
216            level,
217            file,
218            function,
219            line,
220            args,
221        )
222    }
223
224    #[inline]
225    #[doc(alias = "gst_debug_log_literal")]
226    pub fn log_literal(
227        self,
228        obj: Option<&impl IsA<glib::Object>>,
229        level: crate::DebugLevel,
230        file: &glib::GStr,
231        function: &str,
232        line: u32,
233        msg: &glib::GStr,
234    ) {
235        if !self.above_threshold(level) {
236            return;
237        }
238
239        self.log_literal_unfiltered_internal(
240            obj.map(|obj| obj.as_ref()),
241            level,
242            file,
243            function,
244            line,
245            msg,
246        )
247    }
248
249    // rustdoc-stripper-ignore-next
250    /// Logs without checking the log level.
251    #[inline(never)]
252    fn log_unfiltered_internal(
253        self,
254        obj: Option<&glib::Object>,
255        level: crate::DebugLevel,
256        file: &glib::GStr,
257        function: &str,
258        line: u32,
259        args: fmt::Arguments,
260    ) {
261        let mut w = smallvec::SmallVec::<[u8; 256]>::new();
262
263        // Can't really happen but better safe than sorry
264        if std::io::Write::write_fmt(&mut w, args).is_err() {
265            return;
266        }
267        w.push(0);
268
269        self.log_literal_unfiltered_internal(obj, level, file, function, line, unsafe {
270            glib::GStr::from_utf8_with_nul_unchecked(&w)
271        });
272    }
273
274    #[inline(never)]
275    fn log_literal_unfiltered_internal(
276        self,
277        obj: Option<&glib::Object>,
278        level: crate::DebugLevel,
279        file: &glib::GStr,
280        function: &str,
281        line: u32,
282        msg: &glib::GStr,
283    ) {
284        let cat = match self.0 {
285            Some(cat) => cat,
286            None => return,
287        };
288
289        let obj_ptr = match obj {
290            Some(obj) => obj.as_ptr(),
291            None => ptr::null_mut(),
292        };
293
294        function.run_with_gstr(|function| {
295            #[cfg(feature = "v1_20")]
296            unsafe {
297                ffi::gst_debug_log_literal(
298                    cat.as_ptr(),
299                    level.into_glib(),
300                    file.as_ptr(),
301                    function.as_ptr(),
302                    line as i32,
303                    obj_ptr,
304                    msg.as_ptr(),
305                );
306            }
307            #[cfg(not(feature = "v1_20"))]
308            unsafe {
309                ffi::gst_debug_log(
310                    cat.as_ptr(),
311                    level.into_glib(),
312                    file.as_ptr(),
313                    function.as_ptr(),
314                    line as i32,
315                    obj_ptr,
316                    b"%s\0".as_ptr() as *const _,
317                    msg.as_ptr(),
318                );
319            }
320        });
321    }
322
323    #[cfg(feature = "v1_22")]
324    #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
325    #[inline]
326    #[doc(alias = "gst_debug_log_id")]
327    pub fn log_id(
328        self,
329        id: impl AsRef<glib::GStr>,
330        level: crate::DebugLevel,
331        file: &glib::GStr,
332        function: &str,
333        line: u32,
334        args: fmt::Arguments,
335    ) {
336        if !self.above_threshold(level) {
337            return;
338        }
339
340        self.log_id_unfiltered_internal(id.as_ref(), level, file, function, line, args);
341    }
342
343    #[cfg(feature = "v1_22")]
344    #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
345    #[inline]
346    #[doc(alias = "gst_debug_log_id_literal")]
347    pub fn log_id_literal(
348        self,
349        id: impl AsRef<glib::GStr>,
350        level: crate::DebugLevel,
351        file: &glib::GStr,
352        function: &str,
353        line: u32,
354        msg: &glib::GStr,
355    ) {
356        if !self.above_threshold(level) {
357            return;
358        }
359
360        self.log_id_literal_unfiltered_internal(id.as_ref(), level, file, function, line, msg);
361    }
362
363    #[cfg(feature = "v1_22")]
364    #[inline(never)]
365    fn log_id_unfiltered_internal(
366        self,
367        id: &glib::GStr,
368        level: crate::DebugLevel,
369        file: &glib::GStr,
370        function: &str,
371        line: u32,
372        args: fmt::Arguments,
373    ) {
374        let mut w = smallvec::SmallVec::<[u8; 256]>::new();
375
376        // Can't really happen but better safe than sorry
377        if std::io::Write::write_fmt(&mut w, args).is_err() {
378            return;
379        }
380        w.push(0);
381
382        self.log_id_literal_unfiltered_internal(id, level, file, function, line, unsafe {
383            glib::GStr::from_utf8_with_nul_unchecked(&w)
384        });
385    }
386
387    #[cfg(feature = "v1_22")]
388    #[inline(never)]
389    fn log_id_literal_unfiltered_internal(
390        self,
391        id: &glib::GStr,
392        level: crate::DebugLevel,
393        file: &glib::GStr,
394        function: &str,
395        line: u32,
396        msg: &glib::GStr,
397    ) {
398        let cat = match self.0 {
399            Some(cat) => cat,
400            None => return,
401        };
402
403        function.run_with_gstr(|function| unsafe {
404            ffi::gst_debug_log_id_literal(
405                cat.as_ptr(),
406                level.into_glib(),
407                file.as_ptr(),
408                function.as_ptr(),
409                line as i32,
410                id.as_ptr(),
411                msg.as_ptr(),
412            );
413        });
414    }
415
416    #[doc(alias = "get_all_categories")]
417    #[doc(alias = "gst_debug_get_all_categories")]
418    #[inline]
419    pub fn all_categories() -> glib::SList<DebugCategory> {
420        unsafe { glib::SList::from_glib_container(ffi::gst_debug_get_all_categories()) }
421    }
422
423    #[cfg(feature = "v1_18")]
424    #[cfg_attr(docsrs, doc(cfg(feature = "v1_18")))]
425    #[doc(alias = "gst_debug_log_get_line")]
426    #[inline]
427    pub fn get_line(
428        &self,
429        level: crate::DebugLevel,
430        file: &glib::GStr,
431        function: &glib::GStr,
432        line: u32,
433        object: Option<&LoggedObject>,
434        message: &DebugMessage,
435    ) -> Option<glib::GString> {
436        let cat = self.0?;
437
438        unsafe {
439            from_glib_full(ffi::gst_debug_log_get_line(
440                cat.as_ptr(),
441                level.into_glib(),
442                file.as_ptr(),
443                function.as_ptr(),
444                line as i32,
445                object.map(|o| o.as_ptr()).unwrap_or(ptr::null_mut()),
446                message.0.as_ptr(),
447            ))
448        }
449    }
450
451    #[inline]
452    pub fn as_ptr(&self) -> *mut ffi::GstDebugCategory {
453        self.0.map(|p| p.as_ptr()).unwrap_or(ptr::null_mut())
454    }
455}
456
457impl DebugLogger for DebugCategory {
458    #[inline]
459    fn above_threshold(&self, level: crate::DebugLevel) -> bool {
460        match self.0 {
461            Some(cat) => unsafe { cat.as_ref().threshold >= level.into_glib() },
462            None => false,
463        }
464    }
465
466    // rustdoc-stripper-ignore-next
467    /// Logs without checking the log level.
468    #[inline]
469    #[doc(alias = "gst_debug_log")]
470    fn log_unfiltered(
471        &self,
472        obj: Option<&impl IsA<glib::Object>>,
473        level: crate::DebugLevel,
474        file: &glib::GStr,
475        function: &str,
476        line: u32,
477        args: fmt::Arguments,
478    ) {
479        self.log_unfiltered_internal(
480            obj.map(|obj| obj.as_ref()),
481            level,
482            file,
483            function,
484            line,
485            args,
486        )
487    }
488
489    #[doc(alias = "gst_debug_log_literal")]
490    fn log_literal_unfiltered(
491        &self,
492        obj: Option<&impl IsA<glib::Object>>,
493        level: crate::DebugLevel,
494        file: &glib::GStr,
495        function: &str,
496        line: u32,
497        msg: &glib::GStr,
498    ) {
499        self.log_literal_unfiltered_internal(
500            obj.map(|obj| obj.as_ref()),
501            level,
502            file,
503            function,
504            line,
505            msg,
506        )
507    }
508
509    #[cfg(feature = "v1_22")]
510    #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
511    // rustdoc-stripper-ignore-next
512    /// Logs without checking the log level.
513    #[inline]
514    #[doc(alias = "gst_debug_log_id_literal")]
515    fn log_id_literal_unfiltered(
516        &self,
517        id: impl AsRef<glib::GStr>,
518        level: crate::DebugLevel,
519        file: &glib::GStr,
520        function: &str,
521        line: u32,
522        msg: &glib::GStr,
523    ) {
524        self.log_id_literal_unfiltered_internal(id.as_ref(), level, file, function, line, msg)
525    }
526
527    #[cfg(feature = "v1_22")]
528    #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
529    // rustdoc-stripper-ignore-next
530    /// Logs without checking the log level.
531    #[inline]
532    #[doc(alias = "gst_debug_log_id")]
533    fn log_id_unfiltered(
534        &self,
535        id: impl AsRef<glib::GStr>,
536        level: crate::DebugLevel,
537        file: &glib::GStr,
538        function: &str,
539        line: u32,
540        args: fmt::Arguments,
541    ) {
542        self.log_id_unfiltered_internal(id.as_ref(), level, file, function, line, args)
543    }
544}
545
546unsafe impl Sync for DebugCategory {}
547unsafe impl Send for DebugCategory {}
548
549impl fmt::Debug for DebugCategory {
550    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
551        f.debug_tuple("DebugCategory").field(&self.name()).finish()
552    }
553}
554
555impl GlibPtrDefault for DebugCategory {
556    type GlibType = *mut ffi::GstDebugCategory;
557}
558
559unsafe impl TransparentPtrType for DebugCategory {}
560
561impl FromGlibPtrNone<*mut ffi::GstDebugCategory> for DebugCategory {
562    #[inline]
563    unsafe fn from_glib_none(ptr: *mut ffi::GstDebugCategory) -> Self {
564        debug_assert!(!ptr.is_null());
565        DebugCategory(Some(ptr::NonNull::new_unchecked(ptr)))
566    }
567}
568
569impl FromGlibPtrFull<*mut ffi::GstDebugCategory> for DebugCategory {
570    #[inline]
571    unsafe fn from_glib_full(ptr: *mut ffi::GstDebugCategory) -> Self {
572        debug_assert!(!ptr.is_null());
573        DebugCategory(Some(ptr::NonNull::new_unchecked(ptr)))
574    }
575}
576
577pub static CAT_RUST: LazyLock<DebugCategory> = LazyLock::new(|| {
578    DebugCategory::new(
579        "GST_RUST",
580        crate::DebugColorFlags::UNDERLINE,
581        Some("GStreamer's Rust binding core"),
582    )
583});
584
585macro_rules! declare_debug_category_from_name(
586    ($cat:ident, $cat_name:expr) => (
587        pub static $cat: LazyLock<DebugCategory> = LazyLock::new(|| DebugCategory::get($cat_name)
588            .expect(&format!("Unable to find `DebugCategory` with name {}", $cat_name)));
589    );
590);
591
592declare_debug_category_from_name!(CAT_DEFAULT, "default");
593declare_debug_category_from_name!(CAT_GST_INIT, "GST_INIT");
594declare_debug_category_from_name!(CAT_MEMORY, "GST_MEMORY");
595declare_debug_category_from_name!(CAT_PARENTAGE, "GST_PARENTAGE");
596declare_debug_category_from_name!(CAT_STATES, "GST_STATES");
597declare_debug_category_from_name!(CAT_SCHEDULING, "GST_SCHEDULING");
598declare_debug_category_from_name!(CAT_BUFFER, "GST_BUFFER");
599declare_debug_category_from_name!(CAT_BUFFER_LIST, "GST_BUFFER_LIST");
600declare_debug_category_from_name!(CAT_BUS, "GST_BUS");
601declare_debug_category_from_name!(CAT_CAPS, "GST_CAPS");
602declare_debug_category_from_name!(CAT_CLOCK, "GST_CLOCK");
603declare_debug_category_from_name!(CAT_ELEMENT_PADS, "GST_ELEMENT_PADS");
604declare_debug_category_from_name!(CAT_PADS, "GST_PADS");
605declare_debug_category_from_name!(CAT_PERFORMANCE, "GST_PERFORMANCE");
606declare_debug_category_from_name!(CAT_PIPELINE, "GST_PIPELINE");
607declare_debug_category_from_name!(CAT_PLUGIN_LOADING, "GST_PLUGIN_LOADING");
608declare_debug_category_from_name!(CAT_PLUGIN_INFO, "GST_PLUGIN_INFO");
609declare_debug_category_from_name!(CAT_PROPERTIES, "GST_PROPERTIES");
610declare_debug_category_from_name!(CAT_NEGOTIATION, "GST_NEGOTIATION");
611declare_debug_category_from_name!(CAT_REFCOUNTING, "GST_REFCOUNTING");
612declare_debug_category_from_name!(CAT_ERROR_SYSTEM, "GST_ERROR_SYSTEM");
613declare_debug_category_from_name!(CAT_EVENT, "GST_EVENT");
614declare_debug_category_from_name!(CAT_MESSAGE, "GST_MESSAGE");
615declare_debug_category_from_name!(CAT_PARAMS, "GST_PARAMS");
616declare_debug_category_from_name!(CAT_CALL_TRACE, "GST_CALL_TRACE");
617declare_debug_category_from_name!(CAT_SIGNAL, "GST_SIGNAL");
618declare_debug_category_from_name!(CAT_PROBE, "GST_PROBE");
619declare_debug_category_from_name!(CAT_REGISTRY, "GST_REGISTRY");
620declare_debug_category_from_name!(CAT_QOS, "GST_QOS");
621declare_debug_category_from_name!(CAT_META, "GST_META");
622declare_debug_category_from_name!(CAT_LOCKING, "GST_LOCKING");
623declare_debug_category_from_name!(CAT_CONTEXT, "GST_CONTEXT");
624
625pub trait DebugLogger {
626    fn above_threshold(&self, level: DebugLevel) -> bool;
627
628    fn log_unfiltered(
629        &self,
630        obj: Option<&impl IsA<glib::Object>>,
631        level: DebugLevel,
632        file: &glib::GStr,
633        function: &str,
634        line: u32,
635        args: fmt::Arguments,
636    );
637
638    fn log_literal_unfiltered(
639        &self,
640        obj: Option<&impl IsA<glib::Object>>,
641        level: DebugLevel,
642        file: &glib::GStr,
643        function: &str,
644        line: u32,
645        msg: &glib::GStr,
646    );
647
648    #[cfg(feature = "v1_22")]
649    fn log_id_unfiltered(
650        &self,
651        id: impl AsRef<glib::GStr>,
652        level: DebugLevel,
653        file: &glib::GStr,
654        function: &str,
655        line: u32,
656        args: fmt::Arguments,
657    );
658
659    #[cfg(feature = "v1_22")]
660    fn log_id_literal_unfiltered(
661        &self,
662        id: impl AsRef<glib::GStr>,
663        level: DebugLevel,
664        file: &glib::GStr,
665        function: &str,
666        line: u32,
667        msg: &glib::GStr,
668    );
669}
670
671#[macro_export]
672macro_rules! error(
673    ($logger:expr, obj = $obj:expr, $($args:tt)*) => { {
674        $crate::log_with_level!($logger, $crate::DebugLevel::Error, obj = $obj, $($args)*)
675    }};
676    ($logger:expr, imp = $imp:expr, $($args:tt)*) => { {
677        $crate::log_with_level!($logger, $crate::DebugLevel::Error, imp = $imp, $($args)*)
678    }};
679    ($logger:expr, id = $id:expr, $($args:tt)*) => { {
680        $crate::log_with_level!($logger, $crate::DebugLevel::Error, id = $id, $($args)*)
681    }};
682    ($logger:expr, $($args:tt)*) => { {
683        $crate::log_with_level!($logger, $crate::DebugLevel::Error, $($args)*)
684    }};
685);
686
687#[macro_export]
688macro_rules! warning(
689    ($logger:expr, obj = $obj:expr, $($args:tt)*) => { {
690        $crate::log_with_level!($logger, $crate::DebugLevel::Warning, obj = $obj, $($args)*)
691    }};
692    ($logger:expr, imp = $imp:expr, $($args:tt)*) => { {
693        $crate::log_with_level!($logger, $crate::DebugLevel::Warning, imp = $imp, $($args)*)
694    }};
695    ($logger:expr, id = $id:expr, $($args:tt)*) => { {
696        $crate::log_with_level!($logger, $crate::DebugLevel::Warning, id = $id, $($args)*)
697    }};
698    ($logger:expr, $($args:tt)*) => { {
699        $crate::log_with_level!($logger, $crate::DebugLevel::Warning, $($args)*)
700    }};
701);
702
703#[macro_export]
704macro_rules! fixme(
705    ($logger:expr, obj = $obj:expr, $($args:tt)*) => { {
706        $crate::log_with_level!($logger, $crate::DebugLevel::Fixme, obj = $obj, $($args)*)
707    }};
708    ($logger:expr, imp = $imp:expr, $($args:tt)*) => { {
709        $crate::log_with_level!($logger, $crate::DebugLevel::Fixme, imp = $imp, $($args)*)
710    }};
711    ($logger:expr, id = $id:expr, $($args:tt)*) => { {
712        $crate::log_with_level!($logger, $crate::DebugLevel::Fixme, id = $id, $($args)*)
713    }};
714    ($logger:expr, $($args:tt)*) => { {
715        $crate::log_with_level!($logger, $crate::DebugLevel::Fixme, $($args)*)
716    }};
717);
718
719#[macro_export]
720macro_rules! info(
721    ($logger:expr, obj = $obj:expr, $($args:tt)*) => { {
722        $crate::log_with_level!($logger, $crate::DebugLevel::Info, obj = $obj, $($args)*)
723    }};
724    ($logger:expr, imp = $imp:expr, $($args:tt)*) => { {
725        $crate::log_with_level!($logger, $crate::DebugLevel::Info, imp = $imp, $($args)*)
726    }};
727    ($logger:expr, id = $id:expr, $($args:tt)*) => { {
728        $crate::log_with_level!($logger, $crate::DebugLevel::Info, id = $id, $($args)*)
729    }};
730    ($logger:expr, $($args:tt)*) => { {
731        $crate::log_with_level!($logger, $crate::DebugLevel::Info, $($args)*)
732    }};
733);
734
735#[macro_export]
736macro_rules! debug(
737    ($logger:expr, obj = $obj:expr, $($args:tt)*) => { {
738        $crate::log_with_level!($logger, $crate::DebugLevel::Debug, obj = $obj, $($args)*)
739    }};
740    ($logger:expr, imp = $imp:expr, $($args:tt)*) => { {
741        $crate::log_with_level!($logger, $crate::DebugLevel::Debug, imp = $imp, $($args)*)
742    }};
743    ($logger:expr, id = $id:expr, $($args:tt)*) => { {
744        $crate::log_with_level!($logger, $crate::DebugLevel::Debug, id = $id, $($args)*)
745    }};
746    ($logger:expr, $($args:tt)*) => { {
747        $crate::log_with_level!($logger, $crate::DebugLevel::Debug, $($args)*)
748    }};
749);
750
751#[macro_export]
752macro_rules! log(
753    ($logger:expr, obj = $obj:expr, $($args:tt)*) => { {
754        $crate::log_with_level!($logger, $crate::DebugLevel::Log, obj = $obj, $($args)*)
755    }};
756    ($logger:expr, imp = $imp:expr, $($args:tt)*) => { {
757        $crate::log_with_level!($logger, $crate::DebugLevel::Log, imp = $imp, $($args)*)
758    }};
759    ($logger:expr, id = $id:expr, $($args:tt)*) => { {
760        $crate::log_with_level!($logger, $crate::DebugLevel::Log, id = $id, $($args)*)
761    }};
762    ($logger:expr, $($args:tt)*) => { {
763        $crate::log_with_level!($logger, $crate::DebugLevel::Log, $($args)*)
764    }};
765);
766
767#[macro_export]
768macro_rules! trace(
769    ($logger:expr, obj = $obj:expr, $($args:tt)*) => { {
770        $crate::log_with_level!($logger, $crate::DebugLevel::Trace, obj = $obj, $($args)*)
771    }};
772    ($logger:expr, imp = $imp:expr, $($args:tt)*) => { {
773        $crate::log_with_level!($logger, $crate::DebugLevel::Trace, imp = $imp, $($args)*)
774    }};
775    ($logger:expr, id = $id:expr, $($args:tt)*) => { {
776        $crate::log_with_level!($logger, $crate::DebugLevel::Trace, id = $id, $($args)*)
777    }};
778    ($logger:expr, $($args:tt)*) => { {
779        $crate::log_with_level!($logger, $crate::DebugLevel::Trace, $($args)*)
780    }};
781);
782
783#[macro_export]
784macro_rules! memdump(
785    ($logger:expr, obj = $obj:expr, $($args:tt)*) => { {
786        $crate::log_with_level!($logger, $crate::DebugLevel::Memdump, obj = $obj, $($args)*)
787    }};
788    ($logger:expr, imp = $imp:expr, $($args:tt)*) => { {
789        $crate::log_with_level!($logger, $crate::DebugLevel::Memdump, imp = $imp, $($args)*)
790    }};
791    ($logger:expr, id = $id:expr, $($args:tt)*) => { {
792        $crate::log_with_level!($logger, $crate::DebugLevel::Memdump, id = $id, $($args)*)
793    }};
794    ($logger:expr, $($args:tt)*) => { {
795        $crate::log_with_level!($logger, $crate::DebugLevel::Memdump, $($args)*)
796    }};
797);
798
799#[macro_export]
800macro_rules! log_with_level(
801    ($logger:expr, $level:expr, obj = $obj:expr, $msg:literal) => { {
802        #[allow(unused_imports)]
803        use $crate::log::DebugLogger;
804        let logger = &$logger;
805
806        // Check the log level before using `format_args!` otherwise
807        // formatted arguments are evaluated even if we end up not logging.
808        #[allow(unused_unsafe)]
809        #[allow(clippy::redundant_closure_call)]
810        if logger.above_threshold($level) {
811            use $crate::glib::prelude::Cast;
812
813            // FIXME: Once there's a function_name! macro that returns a string literal we can
814            // directly pass it as `&GStr` forward
815
816            let obj = &$obj;
817            let obj = unsafe { obj.unsafe_cast_ref::<$crate::glib::Object>() };
818            let function_name = $crate::glib::function_name!();
819
820            // Check if formatting is necessary or not
821            // FIXME: This needs to be a closure because the return value of format_args!() can't
822            // be assigned to a variable
823            (|args: std::fmt::Arguments| {
824                if args.as_str().is_some() {
825                    logger.log_literal_unfiltered(
826                        Some(obj),
827                        $level,
828                        unsafe { $crate::glib::GStr::from_utf8_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) },
829                        function_name,
830                        line!(),
831                        $crate::glib::gstr!($msg),
832                    )
833                } else {
834                    logger.log_unfiltered(
835                        Some(obj),
836                        $level,
837                        unsafe { $crate::glib::GStr::from_utf8_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) },
838                        function_name,
839                        line!(),
840                        args,
841                    )
842                }
843            })(format_args!($msg))
844        }
845    }};
846    ($logger:expr, $level:expr, obj = $obj:expr, $($args:tt)*) => { {
847        #[allow(unused_imports)]
848        use $crate::log::DebugLogger;
849        let logger = &$logger;
850
851        // Check the log level before using `format_args!` otherwise
852        // formatted arguments are evaluated even if we end up not logging.
853        #[allow(unused_unsafe)]
854        if logger.above_threshold($level) {
855            use $crate::glib::prelude::Cast;
856
857            // FIXME: Once there's a function_name! macro that returns a string literal we can
858            // directly pass it as `&GStr` forward
859
860            let obj = &$obj;
861            let obj = unsafe { obj.unsafe_cast_ref::<$crate::glib::Object>() };
862            logger.log_unfiltered(
863                    Some(obj),
864                    $level,
865                    unsafe { $crate::glib::GStr::from_utf8_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) },
866                    $crate::glib::function_name!(),
867                    line!(),
868                    format_args!($($args)*),
869                )
870        }
871    }};
872    ($logger:expr, $level:expr, imp = $imp:expr, $msg:literal) => { {
873        #[allow(unused_imports)]
874        use $crate::log::DebugLogger;
875        let logger = &$logger;
876
877        // Check the log level before using `format_args!` otherwise
878        // formatted arguments are evaluated even if we end up not logging.
879        #[allow(unused_unsafe)]
880        #[allow(clippy::redundant_closure_call)]
881        if logger.above_threshold($level) {
882            use $crate::glib::prelude::Cast;
883
884            // FIXME: Once there's a function_name! macro that returns a string literal we can
885            // directly pass it as `&GStr` forward
886
887            let obj = $imp.obj();
888            let obj = unsafe { obj.unsafe_cast_ref::<$crate::glib::Object>() };
889            let function_name = $crate::glib::function_name!();
890
891            // Check if formatting is necessary or not
892            // FIXME: This needs to be a closure because the return value of format_args!() can't
893            // be assigned to a variable
894            (|args: std::fmt::Arguments| {
895                if args.as_str().is_some() {
896                    logger.log_literal_unfiltered(
897                        Some(obj),
898                        $level,
899                        unsafe { $crate::glib::GStr::from_utf8_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) },
900                        function_name,
901                        line!(),
902                        $crate::glib::gstr!($msg),
903                    )
904                } else {
905                    logger.log_unfiltered(
906                        Some(obj),
907                        $level,
908                        unsafe { $crate::glib::GStr::from_utf8_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) },
909                        function_name,
910                        line!(),
911                        args,
912                    )
913                }
914            })(format_args!($msg))
915        }
916    }};
917    ($logger:expr, $level:expr, imp = $imp:expr, $($args:tt)*) => { {
918        #[allow(unused_imports)]
919        use $crate::log::DebugLogger;
920        let logger = &$logger;
921
922        // Check the log level before using `format_args!` otherwise
923        // formatted arguments are evaluated even if we end up not logging.
924        #[allow(unused_unsafe)]
925        if logger.above_threshold($level) {
926            use $crate::glib::prelude::Cast;
927
928            // FIXME: Once there's a function_name! macro that returns a string literal we can
929            // directly pass it as `&GStr` forward
930
931            let obj = $imp.obj();
932            let obj = unsafe { obj.unsafe_cast_ref::<$crate::glib::Object>() };
933            logger.log_unfiltered(
934                    Some(obj),
935                    $level,
936                    unsafe { $crate::glib::GStr::from_utf8_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) },
937                    $crate::glib::function_name!(),
938                    line!(),
939                    format_args!($($args)*),
940                )
941        }
942    }};
943    ($logger:expr, $level:expr, id = $id:literal, $msg:literal) => { {
944        #[allow(unused_imports)]
945        use $crate::log::DebugLogger;
946        let logger = &$logger;
947
948        // Check the log level before using `format_args!` otherwise
949        // formatted arguments are evaluated even if we end up not logging.
950        #[allow(unused_unsafe)]
951        #[allow(clippy::redundant_closure_call)]
952        if logger.above_threshold($level) {
953            // FIXME: Once there's a function_name! macro that returns a string literal we can
954            // directly pass it as `&GStr` forward
955
956            let function_name = $crate::glib::function_name!();
957
958            // Check if formatting is necessary or not
959            // FIXME: This needs to be a closure because the return value of format_args!() can't
960            // be assigned to a variable
961            (|args: std::fmt::Arguments| {
962                if args.as_str().is_some() {
963                    logger.log_id_literal_unfiltered(
964                        $crate::glib::gstr!($id),
965                        $level,
966                        unsafe { $crate::glib::GStr::from_utf8_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) },
967                        function_name,
968                        line!(),
969                        $crate::glib::gstr!($msg),
970                    )
971                } else {
972                    logger.log_id_unfiltered(
973                        $crate::glib::gstr!($id),
974                        $level,
975                        unsafe { $crate::glib::GStr::from_utf8_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) },
976                        function_name,
977                        line!(),
978                        args,
979                    )
980                }
981            })(format_args!($msg))
982        }
983    }};
984    ($logger:expr, $level:expr, id = $id:literal, $($args:tt)*) => { {
985        #[allow(unused_imports)]
986        use $crate::log::DebugLogger;
987        let logger = &$logger;
988
989        // Check the log level before using `format_args!` otherwise
990        // formatted arguments are evaluated even if we end up not logging.
991        #[allow(unused_unsafe)]
992        if logger.above_threshold($level) {
993            // FIXME: Once there's a function_name! macro that returns a string literal we can
994            // directly pass it as `&GStr` forward
995
996            logger.log_id_unfiltered(
997                    $crate::glib::gstr!($id),
998                    $level,
999                    unsafe { $crate::glib::GStr::from_utf8_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) },
1000                    $crate::glib::function_name!(),
1001                    line!(),
1002                    format_args!($($args)*),
1003                )
1004        }
1005    }};
1006    ($logger:expr, $level:expr, id = $id:expr, $msg:literal) => { {
1007        #[allow(unused_imports)]
1008        use $crate::log::DebugLogger;
1009        let logger = &$logger;
1010
1011        // Check the log level before using `format_args!` otherwise
1012        // formatted arguments are evaluated even if we end up not logging.
1013        #[allow(unused_unsafe)]
1014        #[allow(clippy::redundant_closure_call)]
1015        if logger.above_threshold($level) {
1016            // FIXME: Once there's a function_name! macro that returns a string literal we can
1017            // directly pass it as `&GStr` forward
1018
1019            let function_name = $crate::glib::function_name!();
1020
1021            // Check if formatting is necessary or not
1022            // FIXME: This needs to be a closure because the return value of format_args!() can't
1023            // be assigned to a variable
1024            (|args: std::fmt::Arguments| {
1025                if args.as_str().is_some() {
1026                    logger.log_id_literal_unfiltered(
1027                        $id,
1028                        $level,
1029                        unsafe { $crate::glib::GStr::from_utf8_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) },
1030                        function_name,
1031                        line!(),
1032                        $crate::glib::gstr!($msg),
1033                    )
1034                } else {
1035                    logger.log_id_unfiltered(
1036                        $id,
1037                        $level,
1038                        unsafe { $crate::glib::GStr::from_utf8_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) },
1039                        function_name,
1040                        line!(),
1041                        args,
1042                    )
1043                }
1044            })(format_args!($msg))
1045        }
1046    }};
1047    ($logger:expr, $level:expr, id = $id:expr, $($args:tt)*) => { {
1048        #[allow(unused_imports)]
1049        use $crate::log::DebugLogger;
1050        let logger = &$logger;
1051
1052        // Check the log level before using `format_args!` otherwise
1053        // formatted arguments are evaluated even if we end up not logging.
1054        #[allow(unused_unsafe)]
1055        if logger.above_threshold($level) {
1056            // FIXME: Once there's a function_name! macro that returns a string literal we can
1057            // directly pass it as `&GStr` forward
1058
1059            logger.log_id_unfiltered(
1060                $id,
1061                $level,
1062                unsafe { $crate::glib::GStr::from_utf8_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) },
1063                $crate::glib::function_name!(),
1064                line!(),
1065                format_args!($($args)*),
1066            )
1067        }
1068    }};
1069    ($logger:expr, $level:expr, $msg:literal) => { {
1070        #[allow(unused_imports)]
1071        use $crate::log::DebugLogger;
1072        let logger = &$logger;
1073
1074        // Check the log level before using `format_args!` otherwise
1075        // formatted arguments are evaluated even if we end up not logging.
1076        #[allow(unused_unsafe)]
1077        #[allow(clippy::redundant_closure_call)]
1078        if logger.above_threshold($level) {
1079            // FIXME: Once there's a function_name! macro that returns a string literal we can
1080            // directly pass it as `&GStr` forward
1081
1082            let function_name = $crate::glib::function_name!();
1083
1084            // Check if formatting is necessary or not
1085            // FIXME: This needs to be a closure because the return value of format_args!() can't
1086            // be assigned to a variable
1087            (|args: std::fmt::Arguments| {
1088                if args.as_str().is_some() {
1089                    logger.log_literal_unfiltered(
1090                        None as Option<&$crate::glib::Object>,
1091                        $level,
1092                        unsafe { $crate::glib::GStr::from_utf8_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) },
1093                        function_name,
1094                        line!(),
1095                        $crate::glib::gstr!($msg),
1096                    )
1097                } else {
1098                    logger.log_unfiltered(
1099                        None as Option<&$crate::glib::Object>,
1100                        $level,
1101                        unsafe { $crate::glib::GStr::from_utf8_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) },
1102                        function_name,
1103                        line!(),
1104                        args,
1105                    )
1106                }
1107            })(format_args!($msg))
1108        }
1109    }};
1110    ($logger:expr, $level:expr, $($args:tt)*) => { {
1111        #[allow(unused_imports)]
1112        use $crate::log::DebugLogger;
1113        let logger = &$logger;
1114
1115        // Check the log level before using `format_args!` otherwise
1116        // formatted arguments are evaluated even if we end up not logging.
1117        #[allow(unused_unsafe)]
1118        if logger.above_threshold($level) {
1119            // FIXME: Once there's a function_name! macro that returns a string literal we can
1120            // directly pass it as `&GStr` forward
1121
1122            logger.log_unfiltered(
1123                None as Option<&$crate::glib::Object>,
1124                $level,
1125                unsafe { $crate::glib::GStr::from_utf8_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) },
1126                $crate::glib::function_name!(),
1127                line!(),
1128                format_args!($($args)*),
1129            )
1130        }
1131    }};
1132);
1133
1134#[cfg(feature = "log")]
1135#[cfg_attr(docsrs, doc(cfg(feature = "log")))]
1136#[derive(Debug)]
1137pub struct DebugCategoryLogger(DebugCategory);
1138
1139#[cfg(feature = "log")]
1140#[cfg_attr(docsrs, doc(cfg(feature = "log")))]
1141impl DebugCategoryLogger {
1142    pub fn new(cat: DebugCategory) -> Self {
1143        skip_assert_initialized!();
1144        Self(cat)
1145    }
1146
1147    fn to_level(level: log::Level) -> crate::DebugLevel {
1148        skip_assert_initialized!();
1149        match level {
1150            log::Level::Error => DebugLevel::Error,
1151            log::Level::Warn => DebugLevel::Warning,
1152            log::Level::Info => DebugLevel::Info,
1153            log::Level::Debug => DebugLevel::Debug,
1154            log::Level::Trace => DebugLevel::Trace,
1155        }
1156    }
1157}
1158
1159#[cfg(feature = "log")]
1160#[cfg_attr(docsrs, doc(cfg(feature = "log")))]
1161impl log::Log for DebugCategoryLogger {
1162    fn enabled(&self, metadata: &log::Metadata) -> bool {
1163        self.0.above_threshold(Self::to_level(metadata.level()))
1164    }
1165
1166    fn log(&self, record: &log::Record) {
1167        if !self.enabled(record.metadata()) {
1168            return;
1169        }
1170        record.file().unwrap_or("").run_with_gstr(|file| {
1171            self.0.log(
1172                None::<&glib::Object>,
1173                Self::to_level(record.level()),
1174                file,
1175                record.module_path().unwrap_or(""),
1176                record.line().unwrap_or(0),
1177                *record.args(),
1178            );
1179        });
1180    }
1181
1182    fn flush(&self) {}
1183}
1184
1185unsafe extern "C" fn log_handler<T>(
1186    category: *mut ffi::GstDebugCategory,
1187    level: ffi::GstDebugLevel,
1188    file: *const c_char,
1189    function: *const c_char,
1190    line: i32,
1191    object: *mut glib::gobject_ffi::GObject,
1192    message: *mut ffi::GstDebugMessage,
1193    user_data: gpointer,
1194) where
1195    T: Fn(
1196            DebugCategory,
1197            DebugLevel,
1198            &glib::GStr,
1199            &glib::GStr,
1200            u32,
1201            Option<&LoggedObject>,
1202            &DebugMessage,
1203        ) + Send
1204        + Sync
1205        + 'static,
1206{
1207    if category.is_null() {
1208        return;
1209    }
1210    let category = DebugCategory(Some(ptr::NonNull::new_unchecked(category)));
1211    let level = from_glib(level);
1212    let file = glib::GStr::from_ptr(file);
1213    let function = glib::GStr::from_ptr(function);
1214    let line = line as u32;
1215    let object = ptr::NonNull::new(object).map(LoggedObject);
1216    let message = DebugMessage(ptr::NonNull::new_unchecked(message));
1217    let handler = &*(user_data as *mut T);
1218    (handler)(
1219        category,
1220        level,
1221        file,
1222        function,
1223        line,
1224        object.as_ref(),
1225        &message,
1226    );
1227}
1228
1229unsafe extern "C" fn log_handler_data_free<T>(data: gpointer) {
1230    let data = Box::from_raw(data as *mut T);
1231    drop(data);
1232}
1233
1234#[derive(Debug)]
1235pub struct DebugLogFunction(ptr::NonNull<std::os::raw::c_void>);
1236
1237// The contained pointer is never dereferenced and has no thread affinity.
1238// It may be convenient to send it or share it between threads to allow cleaning
1239// up log functions from other threads than the one that created it.
1240unsafe impl Send for DebugLogFunction {}
1241unsafe impl Sync for DebugLogFunction {}
1242
1243#[derive(Debug)]
1244#[doc(alias = "GObject")]
1245pub struct LoggedObject(ptr::NonNull<glib::gobject_ffi::GObject>);
1246
1247impl LoggedObject {
1248    #[inline]
1249    pub fn as_ptr(&self) -> *mut glib::gobject_ffi::GObject {
1250        self.0.as_ptr()
1251    }
1252}
1253
1254impl fmt::Display for LoggedObject {
1255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1256        unsafe {
1257            let ptr = self.0.as_ptr();
1258            let g_type_instance = &mut (*ptr).g_type_instance;
1259            if glib::gobject_ffi::g_type_check_instance_is_fundamentally_a(
1260                g_type_instance,
1261                glib::gobject_ffi::g_object_get_type(),
1262            ) != glib::ffi::GFALSE
1263            {
1264                let type_ = (*g_type_instance.g_class).g_type;
1265
1266                if glib::gobject_ffi::g_type_is_a(type_, ffi::gst_pad_get_type())
1267                    != glib::ffi::GFALSE
1268                {
1269                    let name_ptr = (*(ptr as *mut ffi::GstObject)).name;
1270                    let name = if name_ptr.is_null() {
1271                        "<null>"
1272                    } else {
1273                        CStr::from_ptr(name_ptr)
1274                            .to_str()
1275                            .unwrap_or("<invalid name>")
1276                    };
1277
1278                    let parent_ptr = (*(ptr as *mut ffi::GstObject)).parent;
1279                    let parent_name = if parent_ptr.is_null() {
1280                        "<null>"
1281                    } else {
1282                        let name_ptr = (*(parent_ptr)).name;
1283                        if name_ptr.is_null() {
1284                            "<null>"
1285                        } else {
1286                            CStr::from_ptr(name_ptr)
1287                                .to_str()
1288                                .unwrap_or("<invalid name>")
1289                        }
1290                    };
1291
1292                    write!(f, "{parent_name}:{name}")
1293                } else if glib::gobject_ffi::g_type_is_a(type_, ffi::gst_object_get_type())
1294                    != glib::ffi::GFALSE
1295                {
1296                    let name_ptr = (*(ptr as *mut ffi::GstObject)).name;
1297                    let name = if name_ptr.is_null() {
1298                        "<null>"
1299                    } else {
1300                        CStr::from_ptr(name_ptr)
1301                            .to_str()
1302                            .unwrap_or("<invalid name>")
1303                    };
1304                    write!(f, "{name}")
1305                } else {
1306                    let type_name = CStr::from_ptr(glib::gobject_ffi::g_type_name(type_));
1307                    write!(
1308                        f,
1309                        "{}:{:?}",
1310                        type_name.to_str().unwrap_or("<invalid type>"),
1311                        ptr
1312                    )
1313                }
1314            } else {
1315                write!(f, "{ptr:?}")
1316            }
1317        }
1318    }
1319}
1320
1321#[doc(alias = "gst_debug_add_log_function")]
1322pub fn add_log_function<T>(function: T) -> DebugLogFunction
1323where
1324    T: Fn(
1325            DebugCategory,
1326            DebugLevel,
1327            &glib::GStr,
1328            &glib::GStr,
1329            u32,
1330            Option<&LoggedObject>,
1331            &DebugMessage,
1332        ) + Send
1333        + Sync
1334        + 'static,
1335{
1336    skip_assert_initialized!();
1337    unsafe {
1338        let user_data = Box::new(function);
1339        let user_data_ptr = Box::into_raw(user_data) as gpointer;
1340        ffi::gst_debug_add_log_function(
1341            Some(log_handler::<T>),
1342            user_data_ptr,
1343            Some(log_handler_data_free::<T>),
1344        );
1345        DebugLogFunction(ptr::NonNull::new_unchecked(user_data_ptr))
1346    }
1347}
1348
1349pub fn remove_default_log_function() {
1350    skip_assert_initialized!();
1351    unsafe {
1352        ffi::gst_debug_remove_log_function(None);
1353    }
1354}
1355
1356#[doc(alias = "gst_debug_remove_log_function_by_data")]
1357pub fn remove_log_function(log_fn: DebugLogFunction) {
1358    skip_assert_initialized!();
1359    unsafe {
1360        ffi::gst_debug_remove_log_function_by_data(log_fn.0.as_ptr());
1361    }
1362}
1363
1364#[cfg(test)]
1365mod tests {
1366    use std::sync::{mpsc, Arc, Mutex};
1367
1368    use super::*;
1369
1370    #[test]
1371    #[doc(alias = "get_existing")]
1372    fn existing() {
1373        crate::init().unwrap();
1374
1375        let perf_cat = DebugCategory::get("GST_PERFORMANCE")
1376            .expect("Unable to find `DebugCategory` with name \"GST_PERFORMANCE\"");
1377        assert_eq!(perf_cat.name(), CAT_PERFORMANCE.name());
1378    }
1379
1380    #[test]
1381    fn all() {
1382        crate::init().unwrap();
1383
1384        assert!(DebugCategory::all_categories()
1385            .iter()
1386            .any(|c| c.name() == "GST_PERFORMANCE"));
1387    }
1388
1389    #[test]
1390    fn new_and_log() {
1391        crate::init().unwrap();
1392
1393        let cat = DebugCategory::new(
1394            "test-cat",
1395            crate::DebugColorFlags::empty(),
1396            Some("some debug category"),
1397        );
1398
1399        error!(cat, "meh");
1400        warning!(cat, "meh");
1401        fixme!(cat, "meh");
1402        info!(cat, "meh");
1403        debug!(cat, "meh");
1404        log!(cat, "meh");
1405        trace!(cat, "meh");
1406        memdump!(cat, "meh");
1407
1408        let obj = crate::Bin::with_name("meh");
1409
1410        error!(cat, obj = &obj, "meh");
1411        warning!(cat, obj = &obj, "meh");
1412        fixme!(cat, obj = &obj, "meh");
1413        info!(cat, obj = &obj, "meh");
1414        debug!(cat, obj = &obj, "meh");
1415        log!(cat, obj = &obj, "meh");
1416        trace!(cat, obj = &obj, "meh");
1417        memdump!(cat, obj = &obj, "meh");
1418
1419        error!(cat, obj = obj, "meh");
1420        warning!(cat, obj = obj, "meh");
1421        fixme!(cat, obj = obj, "meh");
1422        info!(cat, obj = obj, "meh");
1423        debug!(cat, obj = obj, "meh");
1424        log!(cat, obj = obj, "meh");
1425        trace!(cat, obj = obj, "meh");
1426        memdump!(cat, obj = obj, "meh");
1427    }
1428
1429    #[cfg(feature = "log")]
1430    static LOGGER: LazyLock<DebugCategoryLogger> = LazyLock::new(|| {
1431        DebugCategoryLogger::new(DebugCategory::new(
1432            "Log_trait",
1433            crate::DebugColorFlags::empty(),
1434            Some("Using the Log trait"),
1435        ))
1436    });
1437
1438    #[test]
1439    #[cfg(feature = "log")]
1440    fn log_trait() {
1441        crate::init().unwrap();
1442
1443        log::set_logger(&(*LOGGER)).expect("Failed to set logger");
1444        log::set_max_level(log::LevelFilter::Trace);
1445        log::error!("meh");
1446        log::warn!("fish");
1447
1448        let (sender, receiver) = mpsc::channel();
1449        let sender = Arc::new(Mutex::new(sender));
1450        let handler = move |category: DebugCategory,
1451                            level: DebugLevel,
1452                            _file: &glib::GStr,
1453                            _function: &glib::GStr,
1454                            _line: u32,
1455                            _object: Option<&LoggedObject>,
1456                            message: &DebugMessage| {
1457            let cat = DebugCategory::get("Log_trait").unwrap();
1458
1459            if category != cat {
1460                // This test can run in parallel with other tests, including new_and_log above.
1461                // We cannot be certain we only see our own messages.
1462                return;
1463            }
1464
1465            assert_eq!(level, DebugLevel::Error);
1466            assert_eq!(message.get().unwrap().as_ref(), "meh");
1467            let _ = sender.lock().unwrap().send(());
1468        };
1469
1470        remove_default_log_function();
1471        add_log_function(handler);
1472
1473        let cat = LOGGER.0;
1474
1475        cat.set_threshold(crate::DebugLevel::Warning);
1476        log::error!("meh");
1477        receiver.recv().unwrap();
1478
1479        cat.set_threshold(crate::DebugLevel::Error);
1480        log::error!("meh");
1481        receiver.recv().unwrap();
1482
1483        cat.set_threshold(crate::DebugLevel::None);
1484        log::error!("fish");
1485        log::warn!("meh");
1486    }
1487
1488    #[test]
1489    fn log_handler() {
1490        crate::init().unwrap();
1491
1492        let cat = DebugCategory::new(
1493            "test-cat-log",
1494            crate::DebugColorFlags::empty(),
1495            Some("some debug category"),
1496        );
1497        cat.set_threshold(DebugLevel::Info);
1498        let obj = crate::Bin::with_name("meh");
1499
1500        let (sender, receiver) = mpsc::channel();
1501
1502        let sender = Arc::new(Mutex::new(sender));
1503
1504        let handler = move |category: DebugCategory,
1505                            level: DebugLevel,
1506                            _file: &glib::GStr,
1507                            _function: &glib::GStr,
1508                            _line: u32,
1509                            _object: Option<&LoggedObject>,
1510                            message: &DebugMessage| {
1511            let cat = DebugCategory::get("test-cat-log").unwrap();
1512
1513            if category != cat {
1514                // This test can run in parallel with other tests, including new_and_log above.
1515                // We cannot be certain we only see our own messages.
1516                return;
1517            }
1518
1519            assert_eq!(level, DebugLevel::Info);
1520            assert_eq!(message.get().unwrap().as_ref(), "meh");
1521            let _ = sender.lock().unwrap().send(());
1522        };
1523
1524        remove_default_log_function();
1525        let log_fn = add_log_function(handler);
1526        info!(cat, obj = &obj, "meh");
1527
1528        receiver.recv().unwrap();
1529
1530        remove_log_function(log_fn);
1531
1532        info!(cat, obj = &obj, "meh2");
1533        assert!(receiver.recv().is_err());
1534    }
1535
1536    #[test]
1537    fn no_argument_evaluation() {
1538        crate::init().unwrap();
1539
1540        let cat = DebugCategory::new(
1541            "no_argument_evaluation",
1542            crate::DebugColorFlags::empty(),
1543            Some("No Argument Evaluation debug category"),
1544        );
1545
1546        let mut arg_evaluated = false;
1547        trace!(cat, "{}", {
1548            arg_evaluated = true;
1549            "trace log"
1550        });
1551
1552        assert!(!arg_evaluated);
1553    }
1554
1555    #[cfg(feature = "v1_22")]
1556    #[test]
1557    fn id_logging() {
1558        crate::init().unwrap();
1559
1560        let cat = DebugCategory::new(
1561            "log_with_id_test_category",
1562            crate::DebugColorFlags::empty(),
1563            Some("Blablabla"),
1564        );
1565
1566        cat.set_threshold(crate::DebugLevel::Trace);
1567
1568        trace!(cat, id = "123", "test");
1569        trace!(cat, id = glib::GString::from("123"), "test");
1570        trace!(cat, id = &glib::GString::from("123"), "test");
1571
1572        // Try with a formatted string too (which is a different code path in the bindings)
1573        let log_id = glib::GString::from("456");
1574        trace!(cat, id = &log_id, "{log_id:?}");
1575    }
1576}