Skip to main content

apple_log/
logger.rs

1#![allow(clippy::missing_panics_doc, clippy::should_implement_trait)]
2
3use core::ffi::c_void;
4use std::ptr::NonNull;
5
6use crate::bridge_support::{bridge_ptr_result, c_string_arg, sanitized_c_string};
7use crate::error::LogError;
8use crate::ffi;
9use crate::os_log::{Level, OSLog};
10use crate::os_signpost_id::OSSignpostId;
11
12/// Controls whether logged string payloads are persisted in clear text.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum Privacy {
15    Public,
16    Private,
17}
18
19impl Privacy {
20    const fn raw(self) -> i32 {
21        match self {
22            Self::Public => 0,
23            Self::Private => 1,
24        }
25    }
26}
27
28const fn severity_for_level(level: Level) -> i32 {
29    match level {
30        Level::Default => 0,
31        Level::Debug => 2,
32        Level::Info => 3,
33        Level::Error => 6,
34        Level::Fault => 8,
35    }
36}
37
38/// Swift `Logger` backed by an `OSLog` handle.
39pub struct Logger {
40    ptr: NonNull<c_void>,
41}
42
43impl Logger {
44    fn bridge_default() -> Self {
45        Self {
46            ptr: NonNull::new(unsafe {
47                // SAFETY: ffi::apple_log_logger_default is a thin wrapper that returns
48                // a non-null OSLog/Logger handle from the Swift bridge. It is safe to call
49                // and always returns a valid pointer.
50                ffi::apple_log_logger_default()
51            })
52            .expect("Swift bridge never returns NULL for Logger.default"),
53        }
54    }
55
56    /// Creates a logger for a subsystem/category pair.
57    ///
58    /// # Errors
59    ///
60    /// Returns an error if either argument contains a NUL byte or the bridge fails.
61    pub fn new(subsystem: &str, category: &str) -> Result<Self, LogError> {
62        let subsystem = c_string_arg("subsystem", subsystem)?;
63        let category = c_string_arg("category", category)?;
64        let ptr = bridge_ptr_result("Logger::new", |error_out| unsafe {
65            // SAFETY: c_string_arg validates both arguments are valid C strings.
66            // The Swift bridge ffi::apple_log_logger_create is thread-safe and takes
67            // valid C pointers plus an error_out pointer populated by bridge_ptr_result.
68            ffi::apple_log_logger_create(subsystem.as_ptr(), category.as_ptr(), error_out)
69        })?;
70        Ok(Self { ptr })
71    }
72
73    /// Creates a logger from an existing `OSLog` handle.
74    ///
75    /// # Errors
76    ///
77    /// Returns an error if the bridge fails to create the logger wrapper.
78    pub fn from_os_log(log: &OSLog) -> Result<Self, LogError> {
79        let ptr = bridge_ptr_result("Logger::from_os_log", |error_out| unsafe {
80            // SAFETY: log.as_ptr() returns a valid OSLog pointer from the OSLog wrapper.
81            // The Swift bridge is thread-safe and error_out is populated by bridge_ptr_result.
82            ffi::apple_log_logger_from_os_log(log.as_ptr(), error_out)
83        })?;
84        Ok(Self { ptr })
85    }
86
87    /// Returns `Logger()`.
88    #[must_use]
89    pub fn default() -> Self {
90        Self::bridge_default()
91    }
92
93    /// Returns a disabled logger.
94    #[must_use]
95    pub fn disabled() -> Self {
96        Self {
97            ptr: NonNull::new(unsafe {
98                // SAFETY: ffi::apple_log_logger_disabled is a thin wrapper that returns
99                // a non-null Logger handle from the Swift bridge. It is safe to call
100                // and always returns a valid pointer.
101                ffi::apple_log_logger_disabled()
102            })
103            .expect("Swift bridge never returns NULL for Logger.disabled"),
104        }
105    }
106
107    fn write(&self, severity: i32, privacy: Privacy, message: &str) {
108        let message = sanitized_c_string(message);
109        unsafe {
110            // SAFETY: self.ptr is a valid non-null Logger pointer from the Swift bridge.
111            // sanitized_c_string validates the message is a valid C string.
112            // The function is thread-safe and does not mutate through self.
113            ffi::apple_log_logger_log(self.ptr.as_ptr(), severity, privacy.raw(), message.as_ptr());
114        }
115    }
116
117    /// Emits a message at one of the classic `OSLogType` levels.
118    pub fn log(&self, level: Level, message: &str) {
119        self.write(severity_for_level(level), Privacy::Public, message);
120    }
121
122    /// Emits a message with an explicit privacy level.
123    pub fn log_with_privacy(&self, level: Level, message: &str, privacy: Privacy) {
124        self.write(severity_for_level(level), privacy, message);
125    }
126
127    pub fn trace(&self, message: &str) {
128        self.write(1, Privacy::Public, message);
129    }
130
131    pub fn debug(&self, message: &str) {
132        self.write(2, Privacy::Public, message);
133    }
134
135    pub fn info(&self, message: &str) {
136        self.write(3, Privacy::Public, message);
137    }
138
139    pub fn notice(&self, message: &str) {
140        self.write(4, Privacy::Public, message);
141    }
142
143    pub fn warning(&self, message: &str) {
144        self.write(5, Privacy::Public, message);
145    }
146
147    pub fn error(&self, message: &str) {
148        self.write(6, Privacy::Public, message);
149    }
150
151    pub fn critical(&self, message: &str) {
152        self.write(7, Privacy::Public, message);
153    }
154
155    pub fn fault(&self, message: &str) {
156        self.write(8, Privacy::Public, message);
157    }
158
159    /// Returns whether the underlying `OSLog` handle enables this classic level.
160    #[must_use]
161    pub fn is_enabled(&self, level: Level) -> bool {
162        unsafe {
163            // SAFETY: self.ptr is a valid non-null Logger pointer. The FFI function
164            // is a read-only query and safe to call from any thread.
165            ffi::apple_log_logger_is_enabled(self.ptr.as_ptr(), level as u8)
166        }
167    }
168
169    /// Generates a signpost identifier associated with this logger's log handle.
170    #[must_use]
171    pub fn signpost_id(&self) -> OSSignpostId {
172        OSSignpostId::from_u64(unsafe {
173            // SAFETY: self.ptr is a valid non-null Logger pointer.
174            // This is a read-only operation that generates a signpost ID.
175            ffi::apple_log_logger_signpost_id(self.ptr.as_ptr())
176        })
177    }
178
179    /// Generates a signpost identifier derived from a pointer.
180    #[must_use]
181    pub fn signpost_id_from_pointer<T>(&self, pointer: *const T) -> OSSignpostId {
182        OSSignpostId::from_u64(unsafe {
183            // SAFETY: self.ptr is a valid non-null Logger pointer. The pointer argument
184            // is cast to c_void which is safe for any pointer type. The function only
185            // reads from the pointer value (not dereferences it) to generate a signpost ID.
186            ffi::apple_log_logger_signpost_id_from_pointer(self.ptr.as_ptr(), pointer.cast())
187        })
188    }
189
190    /// Returns whether signposts are enabled for this logger's log handle.
191    #[must_use]
192    pub fn signposts_enabled(&self) -> bool {
193        unsafe {
194            // SAFETY: self.ptr is a valid non-null Logger pointer.
195            // This is a read-only query that is safe to call from any thread.
196            ffi::apple_log_logger_signposts_enabled(self.ptr.as_ptr())
197        }
198    }
199
200    pub fn signpost_event(&self, id: OSSignpostId, name: &str, message: &str) {
201        let name = sanitized_c_string(name);
202        let message = sanitized_c_string(message);
203        unsafe {
204            // SAFETY: self.ptr is a valid non-null Logger pointer. Both name and message
205            // are validated as proper C strings by sanitized_c_string. The function is thread-safe.
206            ffi::apple_log_logger_signpost_event(
207                self.ptr.as_ptr(),
208                id.as_u64(),
209                name.as_ptr(),
210                message.as_ptr(),
211            );
212        }
213    }
214
215    pub fn signpost_interval_begin(&self, id: OSSignpostId, name: &str) {
216        let name = sanitized_c_string(name);
217        unsafe {
218            // SAFETY: self.ptr is a valid non-null Logger pointer. name is validated
219            // as a proper C string by sanitized_c_string. The function is thread-safe.
220            ffi::apple_log_logger_signpost_interval_begin(
221                self.ptr.as_ptr(),
222                id.as_u64(),
223                name.as_ptr(),
224            );
225        }
226    }
227
228    pub fn signpost_animation_interval_begin(&self, id: OSSignpostId, name: &str) {
229        let name = sanitized_c_string(name);
230        unsafe {
231            // SAFETY: self.ptr is a valid non-null Logger pointer. name is validated
232            // as a proper C string by sanitized_c_string. The function is thread-safe.
233            ffi::apple_log_logger_signpost_animation_interval_begin(
234                self.ptr.as_ptr(),
235                id.as_u64(),
236                name.as_ptr(),
237            );
238        }
239    }
240
241    pub fn signpost_interval_end(&self, id: OSSignpostId, name: &str) {
242        let name = sanitized_c_string(name);
243        unsafe {
244            // SAFETY: self.ptr is a valid non-null Logger pointer. name is validated
245            // as a proper C string by sanitized_c_string. The function is thread-safe.
246            ffi::apple_log_logger_signpost_interval_end(
247                self.ptr.as_ptr(),
248                id.as_u64(),
249                name.as_ptr(),
250            );
251        }
252    }
253
254    pub(crate) const fn as_ptr(&self) -> *mut c_void {
255        self.ptr.as_ptr()
256    }
257}
258
259impl Default for Logger {
260    fn default() -> Self {
261        Self::bridge_default()
262    }
263}
264
265impl Drop for Logger {
266    fn drop(&mut self) {
267        unsafe {
268            // SAFETY: self.ptr is a valid non-null Logger pointer that was created by
269            // the Swift bridge. apple_log_logger_release is safe to call exactly once
270            // per Logger and properly cleans up the Swift bridge reference count.
271            ffi::apple_log_logger_release(self.ptr.as_ptr());
272        }
273    }
274}