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