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