1use std::{fmt, io::Write, ptr};
4
5use glib::{prelude::*, translate::*};
6
7use crate::log::DebugLogger;
8use crate::{ffi, ClockTime, DebugCategory, DebugLevel, LogContextFlags, LogContextHashFlags};
9
10#[derive(Debug)]
63#[doc(alias = "GstLogContext")]
64#[repr(transparent)]
65pub struct LogContext(ptr::NonNull<ffi::GstLogContext>);
66
67impl LogContext {
68 #[cfg_attr(docsrs, doc(cfg(feature = "v1_28")))]
74 #[doc(alias = "gst_log_context_get_category")]
75 #[doc(alias = "get_category")]
76 #[inline]
77 pub fn category(&self) -> DebugCategory {
78 unsafe { from_glib_none(ffi::gst_log_context_get_category(self.0.as_ptr())) }
79 }
80
81 #[cfg_attr(docsrs, doc(cfg(feature = "v1_28")))]
83 #[doc(alias = "gst_log_context_reset")]
84 #[inline]
85 pub fn reset(&self) {
86 unsafe {
87 ffi::gst_log_context_reset(self.0.as_ptr());
88 }
89 }
90
91 #[inline]
92 pub fn as_ptr(&self) -> *mut ffi::GstLogContext {
93 self.0.as_ptr()
94 }
95
96 #[inline(never)]
99 fn log_unfiltered_internal(
100 &self,
101 obj: Option<&glib::Object>,
102 level: DebugLevel,
103 file: &glib::GStr,
104 function: &str,
105 line: u32,
106 args: fmt::Arguments,
107 ) {
108 let mut w = smallvec::SmallVec::<[u8; 256]>::new();
109
110 if Write::write_fmt(&mut w, args).is_err() {
112 return;
113 }
114 w.push(0);
115
116 self.log_literal_unfiltered_internal(obj, level, file, function, line, unsafe {
117 glib::GStr::from_utf8_with_nul_unchecked(&w)
118 });
119 }
120
121 #[inline(never)]
122 fn log_literal_unfiltered_internal(
123 &self,
124 obj: Option<&glib::Object>,
125 level: DebugLevel,
126 file: &glib::GStr,
127 function: &str,
128 line: u32,
129 msg: &glib::GStr,
130 ) {
131 let obj_ptr = match obj {
132 Some(obj) => obj.as_ptr(),
133 None => ptr::null_mut(),
134 };
135
136 function.run_with_gstr(|function| unsafe {
137 ffi::gst_debug_log_literal_with_context(
138 self.0.as_ptr(),
139 level.into_glib(),
140 file.as_ptr(),
141 function.as_ptr(),
142 line as i32,
143 obj_ptr,
144 msg.as_ptr(),
145 );
146 });
147 }
148
149 #[inline(never)]
150 fn log_id_unfiltered_internal(
151 &self,
152 id: &glib::GStr,
153 level: DebugLevel,
154 file: &glib::GStr,
155 function: &str,
156 line: u32,
157 args: fmt::Arguments,
158 ) {
159 let mut w = smallvec::SmallVec::<[u8; 256]>::new();
160
161 if Write::write_fmt(&mut w, args).is_err() {
163 return;
164 }
165 w.push(0);
166
167 self.log_id_literal_unfiltered_internal(id, level, file, function, line, unsafe {
168 glib::GStr::from_utf8_with_nul_unchecked(&w)
169 });
170 }
171
172 #[inline(never)]
173 fn log_id_literal_unfiltered_internal(
174 &self,
175 id: &glib::GStr,
176 level: DebugLevel,
177 file: &glib::GStr,
178 function: &str,
179 line: u32,
180 msg: &glib::GStr,
181 ) {
182 function.run_with_gstr(|function| unsafe {
183 ffi::gst_debug_log_id_literal_with_context(
184 self.0.as_ptr(),
185 level.into_glib(),
186 file.as_ptr(),
187 function.as_ptr(),
188 line as i32,
189 id.as_ptr(),
190 msg.as_ptr(),
191 );
192 });
193 }
194
195 #[cfg_attr(docsrs, doc(cfg(feature = "v1_28")))]
196 #[doc(alias = "gst_debug_log_with_context")]
197 pub fn log(
198 &self,
199 obj: Option<&impl IsA<glib::Object>>,
200 level: DebugLevel,
201 file: &glib::GStr,
202 function: &str,
203 line: u32,
204 args: fmt::Arguments,
205 ) {
206 if !self.above_threshold(level) {
207 return;
208 }
209
210 self.log_unfiltered_internal(
211 obj.map(|obj| obj.as_ref()),
212 level,
213 file,
214 function,
215 line,
216 args,
217 )
218 }
219
220 #[cfg_attr(docsrs, doc(cfg(feature = "v1_28")))]
221 #[doc(alias = "gst_debug_log_literal_with_context")]
222 pub fn log_literal(
223 &self,
224 obj: Option<&impl IsA<glib::Object>>,
225 level: DebugLevel,
226 file: &glib::GStr,
227 function: &str,
228 line: u32,
229 msg: &glib::GStr,
230 ) {
231 if !self.above_threshold(level) {
232 return;
233 }
234
235 self.log_literal_unfiltered_internal(
236 obj.map(|obj| obj.as_ref()),
237 level,
238 file,
239 function,
240 line,
241 msg,
242 )
243 }
244
245 #[cfg_attr(docsrs, doc(cfg(feature = "v1_28")))]
246 #[doc(alias = "gst_debug_log_id_with_context")]
247 pub fn log_id(
248 &self,
249 id: impl AsRef<glib::GStr>,
250 level: DebugLevel,
251 file: &glib::GStr,
252 function: &str,
253 line: u32,
254 args: fmt::Arguments,
255 ) {
256 if !self.above_threshold(level) {
257 return;
258 }
259
260 self.log_id_unfiltered_internal(id.as_ref(), level, file, function, line, args);
261 }
262
263 #[cfg_attr(docsrs, doc(cfg(feature = "v1_28")))]
264 #[doc(alias = "gst_debug_log_id_literal_with_context")]
265 pub fn log_id_literal(
266 &self,
267 id: impl AsRef<glib::GStr>,
268 level: DebugLevel,
269 file: &glib::GStr,
270 function: &str,
271 line: u32,
272 msg: &glib::GStr,
273 ) {
274 if !self.above_threshold(level) {
275 return;
276 }
277
278 self.log_id_literal_unfiltered_internal(id.as_ref(), level, file, function, line, msg);
279 }
280}
281
282impl Drop for LogContext {
283 fn drop(&mut self) {
284 unsafe {
285 ffi::gst_log_context_free(self.0.as_ptr());
286 }
287 }
288}
289
290unsafe impl Send for LogContext {}
291unsafe impl Sync for LogContext {}
292
293impl crate::log::DebugLogger for LogContext {
294 #[inline]
295 fn above_threshold(&self, level: crate::DebugLevel) -> bool {
296 self.category().above_threshold(level)
297 }
298
299 #[inline]
300 fn log_unfiltered(
301 &self,
302 obj: Option<&impl IsA<glib::Object>>,
303 level: crate::DebugLevel,
304 file: &glib::GStr,
305 function: &str,
306 line: u32,
307 args: std::fmt::Arguments,
308 ) {
309 self.log_unfiltered_internal(
310 obj.map(|obj| obj.as_ref()),
311 level,
312 file,
313 function,
314 line,
315 args,
316 )
317 }
318
319 #[inline]
320 fn log_literal_unfiltered(
321 &self,
322 obj: Option<&impl IsA<glib::Object>>,
323 level: crate::DebugLevel,
324 file: &glib::GStr,
325 function: &str,
326 line: u32,
327 msg: &glib::GStr,
328 ) {
329 self.log_literal_unfiltered_internal(
330 obj.map(|obj| obj.as_ref()),
331 level,
332 file,
333 function,
334 line,
335 msg,
336 )
337 }
338
339 #[inline]
340 fn log_id_unfiltered(
341 &self,
342 id: impl AsRef<glib::GStr>,
343 level: crate::DebugLevel,
344 file: &glib::GStr,
345 function: &str,
346 line: u32,
347 args: std::fmt::Arguments,
348 ) {
349 self.log_id_unfiltered_internal(id.as_ref(), level, file, function, line, args)
350 }
351
352 #[inline]
355 fn log_id_literal_unfiltered(
356 &self,
357 id: impl AsRef<glib::GStr>,
358 level: crate::DebugLevel,
359 file: &glib::GStr,
360 function: &str,
361 line: u32,
362 msg: &glib::GStr,
363 ) {
364 self.log_id_literal_unfiltered_internal(id.as_ref(), level, file, function, line, msg)
365 }
366}
367
368#[derive(Debug)]
372#[doc(alias = "GstLogContextBuilder")]
373#[repr(transparent)]
374pub struct LogContextBuilder(ptr::NonNull<ffi::GstLogContextBuilder>);
375
376impl LogContextBuilder {
377 #[cfg_attr(docsrs, doc(cfg(feature = "v1_28")))]
388 #[doc(alias = "gst_log_context_builder_new")]
389 pub fn new(category: DebugCategory, flags: LogContextFlags) -> Self {
390 skip_assert_initialized!();
391 unsafe {
392 let ptr = ffi::gst_log_context_builder_new(category.as_ptr(), flags.into_glib());
393 LogContextBuilder(ptr::NonNull::new_unchecked(ptr))
394 }
395 }
396
397 #[cfg_attr(docsrs, doc(cfg(feature = "v1_28")))]
398 #[doc(alias = "gst_log_context_builder_set_hash_flags")]
399 pub fn hash_flags(self, flags: LogContextHashFlags) -> Self {
400 unsafe {
401 ffi::gst_log_context_builder_set_hash_flags(self.0.as_ptr(), flags.into_glib());
402 }
403 self
404 }
405
406 #[cfg_attr(docsrs, doc(cfg(feature = "v1_28")))]
407 #[doc(alias = "gst_log_context_builder_set_category")]
408 pub fn category(self, category: DebugCategory) -> Self {
409 unsafe {
410 ffi::gst_log_context_builder_set_category(self.0.as_ptr(), category.as_ptr());
411 }
412 self
413 }
414
415 #[cfg_attr(docsrs, doc(cfg(feature = "v1_28")))]
416 #[doc(alias = "gst_log_context_builder_set_interval")]
417 pub fn interval(self, interval: impl Into<Option<ClockTime>>) -> Self {
418 unsafe {
419 ffi::gst_log_context_builder_set_interval(self.0.as_ptr(), interval.into().into_glib());
420 }
421 self
422 }
423
424 #[cfg_attr(docsrs, doc(cfg(feature = "v1_28")))]
431 #[doc(alias = "gst_log_context_builder_build")]
432 pub fn build(self) -> LogContext {
433 unsafe {
434 let ptr = ffi::gst_log_context_builder_build(self.0.as_ptr());
435 LogContext(ptr::NonNull::new_unchecked(ptr))
436 }
437 }
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443 use crate::{log::DebugLogger, DebugLevel, LogContextFlags, LogContextHashFlags};
444
445 #[test]
446 fn log_context_builder_basic() {
447 crate::init().unwrap();
448
449 let cat = crate::DebugCategory::new(
450 "test-log-context",
451 crate::DebugColorFlags::empty(),
452 Some("Test log context category"),
453 );
454
455 let context = LogContextBuilder::new(cat, LogContextFlags::empty()).build();
457
458 assert_eq!(context.category(), cat);
459 }
460
461 #[test]
462 fn log_context_builder_with_flags() {
463 crate::init().unwrap();
464
465 let cat = crate::DebugCategory::new(
466 "test-log-context-flags",
467 crate::DebugColorFlags::empty(),
468 Some("Test log context with flags"),
469 );
470
471 let context = LogContextBuilder::new(cat, LogContextFlags::THROTTLE)
473 .hash_flags(LogContextHashFlags::USE_LINE_NUMBER)
474 .interval(Some(crate::ClockTime::from_seconds(1)))
475 .build();
476
477 assert_eq!(context.category(), cat);
478 }
479
480 #[test]
481 fn log_context_trait_implementation() {
482 crate::init().unwrap();
483
484 let cat = crate::DebugCategory::new(
485 "test-trait-impl",
486 crate::DebugColorFlags::empty(),
487 Some("Test trait implementation"),
488 );
489
490 let context = LogContextBuilder::new(cat, LogContextFlags::empty()).build();
491
492 assert_eq!(
494 context.above_threshold(DebugLevel::Error),
495 cat.above_threshold(DebugLevel::Error)
496 );
497 assert_eq!(
498 context.above_threshold(DebugLevel::Debug),
499 cat.above_threshold(DebugLevel::Debug)
500 );
501 }
502
503 #[test]
504 fn log_context_with_macros() {
505 crate::init().unwrap();
506
507 let cat = crate::DebugCategory::new(
508 "test-macro-usage",
509 crate::DebugColorFlags::empty(),
510 Some("Test macro usage"),
511 );
512 cat.set_threshold(DebugLevel::Trace);
513
514 let context = LogContextBuilder::new(cat, LogContextFlags::empty()).build();
515
516 crate::error!(context, "error message");
518 crate::error!(&context, "error message");
519 crate::warning!(context, "warning message");
520 crate::warning!(&context, "warning message");
521 crate::info!(context, "info message");
522 crate::info!(&context, "info message");
523 crate::debug!(context, "debug message");
524 crate::debug!(&context, "debug message");
525 crate::trace!(context, "trace message");
526 crate::trace!(&context, "trace message");
527
528 let obj = crate::Bin::with_name("test-bin");
530 crate::error!(context, obj = &obj, "error with object");
531 crate::warning!(context, obj = &obj, "warning with object");
532
533 let value = 42;
535 crate::info!(context, "formatted message: {}", value);
536 crate::debug!(context, obj = &obj, "formatted with obj: {}", value);
537 }
538
539 #[test]
540 fn log_context_interchangeable_with_category() {
541 crate::init().unwrap();
542
543 let cat = crate::DebugCategory::new(
544 "test-interchangeable",
545 crate::DebugColorFlags::empty(),
546 Some("Test interchangeable usage"),
547 );
548 cat.set_threshold(DebugLevel::Info);
549
550 let context = LogContextBuilder::new(cat, LogContextFlags::empty()).build();
551
552 let test_message = "test message";
554
555 crate::info!(cat, "{}", test_message);
557 crate::info!(&cat, "{}", test_message);
558 crate::info!(context, "{}", test_message);
559 crate::info!(&context, "{}", test_message);
560
561 let obj = crate::Bin::with_name("test-bin-2");
563 crate::info!(cat, obj = &obj, "{}", test_message);
564 crate::info!(context, obj = &obj, "{}", test_message);
565 }
566
567 #[test]
568 fn static_log_context() {
569 crate::init().unwrap();
570
571 static TEST_CATEGORY: std::sync::LazyLock<crate::DebugCategory> =
573 std::sync::LazyLock::new(|| {
574 crate::DebugCategory::new(
575 "test-static-context",
576 crate::DebugColorFlags::empty(),
577 Some("Test static context"),
578 )
579 });
580
581 static TEST_CONTEXT: std::sync::LazyLock<LogContext> = std::sync::LazyLock::new(|| {
583 LogContextBuilder::new(*TEST_CATEGORY, LogContextFlags::empty()).build()
584 });
585
586 crate::info!(TEST_CONTEXT, "message from static context");
588
589 assert_eq!(TEST_CONTEXT.category(), *TEST_CATEGORY);
590 }
591
592 #[test]
593 fn static_log_context_with_advanced_options() {
594 crate::init().unwrap();
595
596 static ADVANCED_CATEGORY: std::sync::LazyLock<crate::DebugCategory> =
598 std::sync::LazyLock::new(|| {
599 crate::DebugCategory::new(
600 "test-static-advanced",
601 crate::DebugColorFlags::empty(),
602 Some("Test static context advanced"),
603 )
604 });
605
606 static ADVANCED_CONTEXT: std::sync::LazyLock<LogContext> = std::sync::LazyLock::new(|| {
608 LogContextBuilder::new(*ADVANCED_CATEGORY, LogContextFlags::THROTTLE)
609 .hash_flags(LogContextHashFlags::USE_LINE_NUMBER)
610 .interval(Some(crate::ClockTime::from_seconds(2)))
611 .build()
612 });
613
614 crate::debug!(ADVANCED_CONTEXT, "advanced static context message");
615 assert_eq!(ADVANCED_CONTEXT.category(), *ADVANCED_CATEGORY);
616 }
617
618 #[test]
619 fn log_context_with_id_logging() {
620 crate::init().unwrap();
621
622 let cat = crate::DebugCategory::new(
623 "test-id-logging",
624 crate::DebugColorFlags::empty(),
625 Some("Test ID logging"),
626 );
627 cat.set_threshold(DebugLevel::Trace);
628
629 let context = LogContextBuilder::new(cat, LogContextFlags::empty()).build();
630
631 crate::trace!(context, id = "test-id-123", "message with ID");
633 crate::debug!(
634 context,
635 id = "test-id-456",
636 "another message with ID: {}",
637 42
638 );
639 }
640
641 #[test]
642 fn log_context_memory_safety() {
643 crate::init().unwrap();
644
645 let cat = crate::DebugCategory::new(
646 "test-memory-safety",
647 crate::DebugColorFlags::empty(),
648 Some("Test memory safety"),
649 );
650
651 {
653 let context = LogContextBuilder::new(cat, LogContextFlags::empty()).build();
654 crate::info!(context, "message before drop");
655 } let context2 = LogContextBuilder::new(cat, LogContextFlags::THROTTLE).build();
659 crate::info!(context2, "message from second context");
660 }
661
662 #[test]
663 fn log_context_category_consistency() {
664 crate::init().unwrap();
665
666 let cat1 = crate::DebugCategory::new(
667 "test-consistency-1",
668 crate::DebugColorFlags::empty(),
669 Some("Test consistency 1"),
670 );
671
672 let cat2 = crate::DebugCategory::new(
673 "test-consistency-2",
674 crate::DebugColorFlags::empty(),
675 Some("Test consistency 2"),
676 );
677
678 let context1 = LogContextBuilder::new(cat1, LogContextFlags::empty()).build();
679 let context2 = LogContextBuilder::new(cat2, LogContextFlags::empty()).build();
680
681 assert_eq!(context1.category(), cat1);
683 assert_eq!(context2.category(), cat2);
684 assert_ne!(context1.category(), cat2);
685 assert_ne!(context2.category(), cat1);
686 }
687
688 #[test]
689 fn log_context_threshold_behavior() {
690 crate::init().unwrap();
691
692 let cat = crate::DebugCategory::new(
693 "test-threshold",
694 crate::DebugColorFlags::empty(),
695 Some("Test threshold behavior"),
696 );
697
698 let context = LogContextBuilder::new(cat, LogContextFlags::empty()).build();
699
700 cat.set_threshold(DebugLevel::Warning);
702
703 assert!(context.above_threshold(DebugLevel::Error));
704 assert!(context.above_threshold(DebugLevel::Warning));
705 assert!(!context.above_threshold(DebugLevel::Info));
706 assert!(!context.above_threshold(DebugLevel::Debug));
707
708 assert_eq!(
710 context.above_threshold(DebugLevel::Error),
711 cat.above_threshold(DebugLevel::Error)
712 );
713 assert_eq!(
714 context.above_threshold(DebugLevel::Warning),
715 cat.above_threshold(DebugLevel::Warning)
716 );
717 assert_eq!(
718 context.above_threshold(DebugLevel::Info),
719 cat.above_threshold(DebugLevel::Info)
720 );
721 assert_eq!(
722 context.above_threshold(DebugLevel::Debug),
723 cat.above_threshold(DebugLevel::Debug)
724 );
725 }
726}