cubeb_backend/
log.rs

1// Copyright © 2017-2018 Mozilla Foundation
2//
3// This program is made available under an ISC-style license.  See the
4// accompanying file LICENSE for details.
5
6use std::os::raw::c_char;
7
8/// Maximum length in bytes for a log message.
9/// Longer messages are silently truncated.  See `write_str`.
10const LOG_LIMIT: usize = 1024;
11
12struct StaticCString<const N: usize> {
13    buf: [std::mem::MaybeUninit<u8>; N],
14    len: usize,
15}
16
17impl<const N: usize> StaticCString<N> {
18    fn new() -> Self {
19        StaticCString {
20            buf: unsafe { std::mem::MaybeUninit::uninit().assume_init() },
21            len: 0,
22        }
23    }
24
25    fn as_cstr(&self) -> &std::ffi::CStr {
26        unsafe {
27            std::ffi::CStr::from_bytes_with_nul_unchecked(std::slice::from_raw_parts(
28                self.buf.as_ptr().cast::<u8>(),
29                self.len,
30            ))
31        }
32    }
33}
34
35impl<const N: usize> std::fmt::Write for StaticCString<N> {
36    fn write_str(&mut self, s: &str) -> std::fmt::Result {
37        use std::convert::TryInto;
38        let s = s.as_bytes();
39        let end = s.len().min(N.checked_sub(1).unwrap() - self.len);
40        debug_assert_eq!(s.len(), end, "message truncated");
41        unsafe {
42            std::ptr::copy_nonoverlapping(
43                s[..end].as_ptr(),
44                self.buf
45                    .as_mut_ptr()
46                    .cast::<u8>()
47                    .offset(self.len.try_into().unwrap()),
48                end,
49            )
50        };
51        self.len += end;
52        self.buf[self.len].write(0);
53        Ok(())
54    }
55}
56
57/// Formats `$file:line: $msg\n` into an on-stack buffer of size `LOG_LIMIT`,
58/// then calls `log_callback` with a pointer to the formatted message.
59pub fn cubeb_log_internal_buf_fmt(
60    log_callback: unsafe extern "C" fn(*const c_char, ...),
61    file: &str,
62    line: u32,
63    msg: std::fmt::Arguments,
64) {
65    let filename = std::path::Path::new(file)
66        .file_name()
67        .unwrap()
68        .to_str()
69        .unwrap();
70    let mut buf = StaticCString::<LOG_LIMIT>::new();
71    let _ = std::fmt::write(&mut buf, format_args!("{filename}:{line}: {msg}\n"));
72    unsafe {
73        log_callback(buf.as_cstr().as_ptr());
74    };
75}
76
77#[macro_export]
78macro_rules! cubeb_log_internal {
79    ($log_callback: expr, $level: expr, $fmt: expr, $($arg: expr),+) => {
80        #[allow(unused_unsafe)]
81        unsafe {
82            if $level <= $crate::ffi::cubeb_log_get_level().into() {
83                if let Some(log_callback) = $log_callback {
84                    $crate::log::cubeb_log_internal_buf_fmt(log_callback, file!(), line!(), format_args!($fmt, $($arg),+));
85                }
86            }
87        }
88    };
89    ($log_callback: expr, $level: expr, $msg: expr) => {
90        cubeb_log_internal!($log_callback, $level, "{}", format_args!($msg));
91    };
92}
93
94#[macro_export]
95macro_rules! cubeb_log {
96    ($($arg: expr),+) => (cubeb_log_internal!($crate::ffi::cubeb_log_get_callback(), $crate::LogLevel::Normal, $($arg),+));
97}
98
99#[macro_export]
100macro_rules! cubeb_logv {
101    ($($arg: expr),+) => (cubeb_log_internal!($crate::ffi::cubeb_log_get_callback(), $crate::LogLevel::Verbose, $($arg),+));
102}
103
104#[macro_export]
105macro_rules! cubeb_alog {
106    ($($arg: expr),+) => (cubeb_log_internal!($crate::ffi::cubeb_async_log.into(), $crate::LogLevel::Normal, $($arg),+));
107}
108
109#[macro_export]
110macro_rules! cubeb_alogv {
111    ($($arg: expr),+) => (cubeb_log_internal!($crate::ffi::cubeb_async_log.into(), $crate::LogLevel::Verbose, $($arg),+));
112}
113
114#[cfg(test)]
115mod tests {
116    use crate::{set_logging, LogLevel};
117    extern crate regex;
118    use self::regex::Regex;
119    use std::sync::RwLock;
120
121    // Ensure that tests that modify the global log callback (e.g. to assert)
122    // cannot run concurrently with others
123    static LOG_MODIFIER: RwLock<()> = RwLock::new(());
124
125    #[test]
126    fn test_normal_logging_sync() {
127        let _guard = LOG_MODIFIER.read();
128        cubeb_log!("This is synchronous log output at normal level");
129        cubeb_log!("{} Formatted log", 1);
130        cubeb_log!("{} Formatted {} log {}", 1, 2, 3);
131    }
132
133    #[test]
134    fn test_normal_logging_inline() {
135        let _guard = LOG_MODIFIER.write();
136        // log.rs:128: 1 log\n
137        set_logging(
138            LogLevel::Normal,
139            Some(|s| {
140                let s = s.to_str().unwrap().trim();
141                println!("{}", s);
142                let re = Regex::new(r"log.rs:\d+: 1 log").unwrap();
143                assert!(re.is_match(s));
144            }),
145        )
146        .unwrap();
147        let x = 1;
148        cubeb_log!("{x} log");
149        set_logging(LogLevel::Disabled, None).unwrap();
150    }
151
152    #[test]
153    fn test_verbose_logging_sync() {
154        let _guard = LOG_MODIFIER.read();
155        cubeb_logv!("This is synchronous log output at verbose level");
156        cubeb_logv!("{} Formatted log", 1);
157        cubeb_logv!("{} Formatted {} log {}", 1, 2, 3);
158    }
159
160    #[test]
161    fn test_normal_logging_async() {
162        let _guard = LOG_MODIFIER.read();
163        cubeb_alog!("This is asynchronous log output at normal level");
164        cubeb_alog!("{} Formatted log", 1);
165        cubeb_alog!("{} Formatted {} log {}", 1, 2, 3);
166    }
167
168    #[test]
169    fn test_verbose_logging_async() {
170        let _guard = LOG_MODIFIER.read();
171        cubeb_alogv!("This is asynchronous log output at verbose level");
172        cubeb_alogv!("{} Formatted log", 1);
173        cubeb_alogv!("{} Formatted {} log {}", 1, 2, 3);
174    }
175}