gstreamer/
log.rs

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