maplibre_native/
logging.rs1use std::ffi::{c_char, c_void};
2use std::panic::{self, AssertUnwindSafe};
3use std::sync::{Arc, Mutex, MutexGuard};
4
5use crate::{Result, sys};
6use maplibre_core::LogSeverityMask;
7use maplibre_native_core as maplibre_core;
8
9pub use maplibre_core::LogRecord;
10
11type LogCallback = dyn Fn(LogRecord) -> bool + Send + Sync + 'static;
12
13struct CallbackState {
14 callback: Box<LogCallback>,
15}
16
17static LOG_CALLBACK_STATE: Mutex<Option<Arc<CallbackState>>> = Mutex::new(None);
18
19fn lock_log_callback_state() -> MutexGuard<'static, Option<Arc<CallbackState>>> {
20 LOG_CALLBACK_STATE
21 .lock()
22 .unwrap_or_else(|poisoned| poisoned.into_inner())
23}
24
25pub fn set_log_callback<F>(callback: F) -> Result<()>
32where
33 F: Fn(LogRecord) -> bool + Send + Sync + 'static,
34{
35 let replacement = Arc::new(CallbackState {
36 callback: Box::new(callback),
37 });
38 let user_data = Arc::as_ptr(&replacement).cast_mut().cast::<c_void>();
39 let previous = {
40 let mut current = lock_log_callback_state();
41 maplibre_core::check(unsafe {
45 sys::mln_log_set_callback(Some(log_callback_trampoline), user_data)
46 })?;
47
48 current.replace(replacement)
49 };
50 drop(previous);
51 Ok(())
52}
53
54pub fn clear_log_callback() -> Result<()> {
56 let previous = {
59 let mut current = lock_log_callback_state();
60 maplibre_core::check(unsafe { sys::mln_log_clear_callback() })?;
61 current.take()
62 };
63 drop(previous);
64 Ok(())
65}
66
67pub fn set_async_log_severity_mask(mask: LogSeverityMask) -> Result<()> {
69 maplibre_core::check(unsafe { sys::mln_log_set_async_severity_mask(mask.bits()) })
72}
73
74unsafe extern "C" fn log_callback_trampoline(
75 user_data: *mut c_void,
76 severity: u32,
77 event: u32,
78 code: i64,
79 message: *const c_char,
80) -> u32 {
81 if user_data.is_null() {
82 return 0;
83 }
84
85 let state = unsafe { &*user_data.cast::<CallbackState>() };
89 invoke_callback(state, severity, event, code, message)
90}
91
92fn invoke_callback(
93 state: &CallbackState,
94 raw_severity: u32,
95 raw_event: u32,
96 code: i64,
97 message: *const c_char,
98) -> u32 {
99 let Ok(record) = (unsafe {
103 maplibre_core::logging::copy_log_record(raw_severity, raw_event, code, message)
104 }) else {
105 return 0;
106 };
107
108 match panic::catch_unwind(AssertUnwindSafe(|| (state.callback)(record))) {
109 Ok(true) => 1,
110 Ok(false) | Err(_) => 0,
111 }
112}
113
114#[cfg(test)]
115pub(crate) mod test_support {
116 use std::sync::{Mutex, MutexGuard};
117
118 use super::{LogSeverityMask, clear_log_callback, set_async_log_severity_mask};
119
120 static LOGGING_TEST_LOCK: Mutex<()> = Mutex::new(());
121
122 pub(crate) struct LoggingTestGuard {
125 _lock: MutexGuard<'static, ()>,
126 }
127
128 impl LoggingTestGuard {
129 pub(crate) fn new() -> Self {
130 let guard = Self {
131 _lock: LOGGING_TEST_LOCK
132 .lock()
133 .unwrap_or_else(|poisoned| poisoned.into_inner()),
134 };
135 clear_logging_after_test();
136 guard
137 }
138 }
139
140 impl Drop for LoggingTestGuard {
141 fn drop(&mut self) {
142 clear_logging_after_test();
143 }
144 }
145
146 fn clear_logging_after_test() {
147 let _ = clear_log_callback();
148 let _ = set_async_log_severity_mask(LogSeverityMask::DEFAULT);
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use std::ffi::{CString, c_void};
155 use std::sync::Arc;
156 use std::sync::atomic::{AtomicUsize, Ordering};
157
158 use super::test_support::LoggingTestGuard;
159 use super::*;
160 use crate::ErrorKind;
161 use maplibre_core::{LogEvent, LogSeverity};
162
163 #[test]
164 fn log_callback_install_clear_and_trampoline_copy_record() {
166 let _guard = LoggingTestGuard::new();
167 let calls = Arc::new(AtomicUsize::new(0));
168 let test_calls = calls.clone();
169
170 set_log_callback(move |record| {
171 test_calls.fetch_add(1, Ordering::SeqCst);
172 if record.code == 42 {
173 assert_eq!(record.severity, LogSeverity::Warning);
174 assert_eq!(record.severity.raw_value(), sys::MLN_LOG_SEVERITY_WARNING);
175 assert_eq!(record.event, LogEvent::Render);
176 assert_eq!(record.event.raw_value(), sys::MLN_LOG_EVENT_RENDER);
177 assert_eq!(record.message, "hello");
178 return true;
179 }
180 false
181 })
182 .unwrap();
183
184 let baseline_calls = calls.load(Ordering::SeqCst);
185 let message = CString::new("hello").unwrap();
186 let current = {
187 let state = lock_log_callback_state();
188 state.as_ref().unwrap().clone()
189 };
190 let user_data = Arc::as_ptr(¤t).cast_mut().cast::<c_void>();
191 assert_eq!(
192 unsafe {
193 log_callback_trampoline(
194 user_data,
195 sys::MLN_LOG_SEVERITY_WARNING,
196 sys::MLN_LOG_EVENT_RENDER,
197 42,
198 message.as_ptr(),
199 )
200 },
201 1
202 );
203 assert_eq!(calls.load(Ordering::SeqCst), baseline_calls + 1);
204
205 clear_log_callback().unwrap();
206 assert!(lock_log_callback_state().is_none());
207 assert_eq!(
208 unsafe {
209 log_callback_trampoline(
210 std::ptr::null_mut(),
211 sys::MLN_LOG_SEVERITY_WARNING,
212 sys::MLN_LOG_EVENT_RENDER,
213 42,
214 message.as_ptr(),
215 )
216 },
217 0
218 );
219 assert_eq!(calls.load(Ordering::SeqCst), baseline_calls + 1);
220 }
221
222 #[test]
223 fn log_callback_replacement_invokes_only_replacement() {
225 let _guard = LoggingTestGuard::new();
226 let first_calls = Arc::new(AtomicUsize::new(0));
227 let second_calls = Arc::new(AtomicUsize::new(0));
228 let first_callback_calls = Arc::clone(&first_calls);
229 set_log_callback(move |_| {
230 first_callback_calls.fetch_add(1, Ordering::SeqCst);
231 true
232 })
233 .unwrap();
234
235 let second_callback_calls = Arc::clone(&second_calls);
236 set_log_callback(move |_| {
237 second_callback_calls.fetch_add(1, Ordering::SeqCst);
238 true
239 })
240 .unwrap();
241
242 let current = {
243 let state = lock_log_callback_state();
244 state.as_ref().unwrap().clone()
245 };
246 let message = CString::new("replacement").unwrap();
247 assert_eq!(
248 invoke_callback(
249 ¤t,
250 sys::MLN_LOG_SEVERITY_INFO,
251 sys::MLN_LOG_EVENT_GENERAL,
252 7,
253 message.as_ptr(),
254 ),
255 1
256 );
257
258 assert_eq!(first_calls.load(Ordering::SeqCst), 0);
259 assert_eq!(second_calls.load(Ordering::SeqCst), 1);
260
261 clear_log_callback().unwrap();
262 assert!(lock_log_callback_state().is_none());
263 }
264
265 #[test]
266 fn invalid_utf8_log_messages_are_not_consumed() {
268 let _guard = LoggingTestGuard::new();
269 set_log_callback(|_| true).unwrap();
270 let invalid = b"\xff\0";
271 let current = {
272 let state = lock_log_callback_state();
273 state.as_ref().unwrap().clone()
274 };
275
276 assert_eq!(
277 invoke_callback(
278 ¤t,
279 sys::MLN_LOG_SEVERITY_ERROR,
280 sys::MLN_LOG_EVENT_GENERAL,
281 0,
282 invalid.as_ptr().cast(),
283 ),
284 0
285 );
286
287 clear_log_callback().unwrap();
288 }
289
290 #[test]
291 fn log_callback_panics_are_not_consumed() {
293 let _guard = LoggingTestGuard::new();
294 set_log_callback(|_| panic!("contained panic")).unwrap();
295
296 let message = CString::new("boom").unwrap();
297 let current = {
298 let state = lock_log_callback_state();
299 state.as_ref().unwrap().clone()
300 };
301
302 assert_eq!(
303 invoke_callback(
304 ¤t,
305 sys::MLN_LOG_SEVERITY_ERROR,
306 sys::MLN_LOG_EVENT_GENERAL,
307 0,
308 message.as_ptr(),
309 ),
310 0
311 );
312
313 clear_log_callback().unwrap();
314 }
315
316 #[test]
317 fn async_log_severity_mask_status_propagates_invalid_bits() {
319 let _guard = LoggingTestGuard::new();
320 let invalid_mask = LogSeverityMask::from_bits_retain(1 << 31);
321
322 let error = set_async_log_severity_mask(invalid_mask).unwrap_err();
323
324 assert_eq!(error.kind(), ErrorKind::InvalidArgument);
325 assert_eq!(error.raw_status(), Some(sys::MLN_STATUS_INVALID_ARGUMENT));
326 }
327
328 #[test]
329 fn async_log_severity_mask_accepts_known_values() {
331 let _guard = LoggingTestGuard::new();
332
333 set_async_log_severity_mask(LogSeverityMask::INFO | LogSeverityMask::ERROR).unwrap();
334 set_async_log_severity_mask(LogSeverityMask::DEFAULT).unwrap();
335 }
336}