Skip to main content

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