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
74pub fn restore_default_async_log_severity_mask() -> Result<()> {
76 set_async_log_severity_mask(LogSeverityMask::DEFAULT)
77}
78
79unsafe extern "C" fn log_callback_trampoline(
80 user_data: *mut c_void,
81 severity: u32,
82 event: u32,
83 code: i64,
84 message: *const c_char,
85) -> u32 {
86 if user_data.is_null() {
87 return 0;
88 }
89
90 let state = unsafe { &*user_data.cast::<CallbackState>() };
94 invoke_callback(state, severity, event, code, message)
95}
96
97fn invoke_callback(
98 state: &CallbackState,
99 raw_severity: u32,
100 raw_event: u32,
101 code: i64,
102 message: *const c_char,
103) -> u32 {
104 let Ok(record) = (unsafe {
108 maplibre_core::logging::copy_log_record(raw_severity, raw_event, code, message)
109 }) else {
110 return 0;
111 };
112
113 match panic::catch_unwind(AssertUnwindSafe(|| (state.callback)(record))) {
114 Ok(true) => 1,
115 Ok(false) | Err(_) => 0,
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use std::ffi::{CString, c_void};
122 use std::sync::atomic::{AtomicUsize, Ordering};
123 use std::sync::{Arc, Mutex, MutexGuard};
124
125 use super::*;
126 use crate::ErrorKind;
127 use maplibre_core::{LogEvent, LogSeverity};
128
129 static LOGGING_TEST_LOCK: Mutex<()> = Mutex::new(());
130
131 struct LoggingTestGuard {
132 _lock: MutexGuard<'static, ()>,
133 }
134
135 impl LoggingTestGuard {
136 fn new() -> Self {
137 let guard = Self {
138 _lock: LOGGING_TEST_LOCK
139 .lock()
140 .unwrap_or_else(|poisoned| poisoned.into_inner()),
141 };
142 clear_logging_after_test();
143 guard
144 }
145 }
146
147 impl Drop for LoggingTestGuard {
148 fn drop(&mut self) {
149 clear_logging_after_test();
150 }
151 }
152
153 fn clear_logging_after_test() {
154 let _ = clear_log_callback();
155 let _ = restore_default_async_log_severity_mask();
156 }
157
158 #[test]
159 fn log_callback_install_clear_and_trampoline_copy_record() {
160 let _guard = LoggingTestGuard::new();
161 let calls = Arc::new(AtomicUsize::new(0));
162 let test_calls = calls.clone();
163
164 set_log_callback(move |record| {
165 test_calls.fetch_add(1, Ordering::SeqCst);
166 if record.code == 42 {
167 assert_eq!(record.severity, LogSeverity::Warning);
168 assert_eq!(record.severity.raw_value(), sys::MLN_LOG_SEVERITY_WARNING);
169 assert_eq!(record.event, LogEvent::Render);
170 assert_eq!(record.event.raw_value(), sys::MLN_LOG_EVENT_RENDER);
171 assert_eq!(record.message, "hello");
172 return true;
173 }
174 false
175 })
176 .unwrap();
177
178 let baseline_calls = calls.load(Ordering::SeqCst);
179 let message = CString::new("hello").unwrap();
180 let current = {
181 let state = lock_log_callback_state();
182 state.as_ref().unwrap().clone()
183 };
184 let user_data = Arc::as_ptr(¤t).cast_mut().cast::<c_void>();
185 assert_eq!(
186 unsafe {
187 log_callback_trampoline(
188 user_data,
189 sys::MLN_LOG_SEVERITY_WARNING,
190 sys::MLN_LOG_EVENT_RENDER,
191 42,
192 message.as_ptr(),
193 )
194 },
195 1
196 );
197 assert_eq!(calls.load(Ordering::SeqCst), baseline_calls + 1);
198
199 clear_log_callback().unwrap();
200 assert!(lock_log_callback_state().is_none());
201 assert_eq!(
202 unsafe {
203 log_callback_trampoline(
204 std::ptr::null_mut(),
205 sys::MLN_LOG_SEVERITY_WARNING,
206 sys::MLN_LOG_EVENT_RENDER,
207 42,
208 message.as_ptr(),
209 )
210 },
211 0
212 );
213 assert_eq!(calls.load(Ordering::SeqCst), baseline_calls + 1);
214 }
215
216 #[test]
217 fn invalid_utf8_log_messages_are_not_consumed() {
218 let _guard = LoggingTestGuard::new();
219 set_log_callback(|_| true).unwrap();
220 let invalid = b"\xff\0";
221 let current = {
222 let state = lock_log_callback_state();
223 state.as_ref().unwrap().clone()
224 };
225
226 assert_eq!(
227 invoke_callback(
228 ¤t,
229 sys::MLN_LOG_SEVERITY_ERROR,
230 sys::MLN_LOG_EVENT_GENERAL,
231 0,
232 invalid.as_ptr().cast(),
233 ),
234 0
235 );
236
237 clear_log_callback().unwrap();
238 }
239
240 #[test]
241 fn log_callback_panics_are_not_consumed() {
242 let _guard = LoggingTestGuard::new();
243 set_log_callback(|_| panic!("contained panic")).unwrap();
244
245 let message = CString::new("boom").unwrap();
246 let current = {
247 let state = lock_log_callback_state();
248 state.as_ref().unwrap().clone()
249 };
250
251 assert_eq!(
252 invoke_callback(
253 ¤t,
254 sys::MLN_LOG_SEVERITY_ERROR,
255 sys::MLN_LOG_EVENT_GENERAL,
256 0,
257 message.as_ptr(),
258 ),
259 0
260 );
261
262 clear_log_callback().unwrap();
263 }
264
265 #[test]
266 fn async_log_severity_mask_status_propagates_invalid_bits() {
267 let _guard = LoggingTestGuard::new();
268 let invalid_mask = LogSeverityMask::from_bits_retain(1 << 31);
269
270 let error = set_async_log_severity_mask(invalid_mask).unwrap_err();
271
272 assert_eq!(error.kind(), ErrorKind::InvalidArgument);
273 assert_eq!(error.raw_status(), Some(sys::MLN_STATUS_INVALID_ARGUMENT));
274 }
275
276 #[test]
277 fn async_log_severity_mask_accepts_known_values() {
278 let _guard = LoggingTestGuard::new();
279
280 set_async_log_severity_mask(LogSeverityMask::INFO | LogSeverityMask::ERROR).unwrap();
281 restore_default_async_log_severity_mask().unwrap();
282 }
283}