1use std::os::raw::c_char;
7
8const 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
57pub 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 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 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}