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