1#![allow(
2 clippy::cast_possible_wrap,
3 clippy::cast_sign_loss,
4 clippy::missing_panics_doc
5)]
6
7use core::ffi::c_void;
8use core::ops::{BitOr, BitOrAssign};
9use std::path::Path;
10use std::ptr::NonNull;
11use std::time::{Duration, SystemTime};
12
13use crate::bridge_support::{
14 bridge_ptr_result, c_string_arg, path_c_string, secs_to_system_time, system_time_to_secs,
15 take_optional_c_string, take_owned_bytes,
16};
17use crate::error::LogError;
18use crate::ffi;
19use crate::os_log_entry_activity::OSLogEntryActivity;
20use crate::os_log_entry_boundary::OSLogEntryBoundary;
21use crate::os_log_entry_log::OSLogEntryLog;
22use crate::os_log_entry_signpost::OSLogEntrySignpost;
23
24struct OSLogEntryList {
25 ptr: NonNull<c_void>,
26}
27
28impl Drop for OSLogEntryList {
29 fn drop(&mut self) {
30 unsafe { ffi::apple_log_os_log_entry_list_release(self.ptr.as_ptr()) };
31 }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub enum OSLogStoreScope {
37 System,
38 CurrentProcessIdentifier,
39}
40
41impl OSLogStoreScope {
42 const fn raw(self) -> i32 {
43 match self {
44 Self::System => 0,
45 Self::CurrentProcessIdentifier => 1,
46 }
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
52pub struct OSLogEnumeratorOptions(usize);
53
54impl OSLogEnumeratorOptions {
55 pub const NONE: Self = Self(0);
56 pub const REVERSE: Self = Self(1);
57
58 #[must_use]
59 pub const fn bits(self) -> usize {
60 self.0
61 }
62
63 #[must_use]
64 pub const fn contains(self, other: Self) -> bool {
65 (self.0 & other.0) == other.0
66 }
67}
68
69impl BitOr for OSLogEnumeratorOptions {
70 type Output = Self;
71
72 fn bitor(self, rhs: Self) -> Self::Output {
73 Self(self.0 | rhs.0)
74 }
75}
76
77impl BitOrAssign for OSLogEnumeratorOptions {
78 fn bitor_assign(&mut self, rhs: Self) {
79 self.0 |= rhs.0;
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85pub enum OSLogStoreCategory {
86 Undefined,
87 Metadata,
88 ShortTerm,
89 LongTermAuto,
90 LongTerm1,
91 LongTerm3,
92 LongTerm7,
93 LongTerm14,
94 LongTerm30,
95}
96
97impl OSLogStoreCategory {
98 pub(crate) const fn from_raw(raw: i32) -> Self {
99 match raw {
100 1 => Self::Metadata,
101 2 => Self::ShortTerm,
102 3 => Self::LongTermAuto,
103 4 => Self::LongTerm1,
104 5 => Self::LongTerm3,
105 6 => Self::LongTerm7,
106 7 => Self::LongTerm14,
107 8 => Self::LongTerm30,
108 _ => Self::Undefined,
109 }
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
115pub enum OSLogMessageArgumentCategory {
116 Undefined,
117 Data,
118 Double,
119 Int64,
120 String,
121 UInt64,
122}
123
124impl OSLogMessageArgumentCategory {
125 const fn from_raw(raw: i32) -> Self {
126 match raw {
127 1 => Self::Data,
128 2 => Self::Double,
129 3 => Self::Int64,
130 4 => Self::String,
131 5 => Self::UInt64,
132 _ => Self::Undefined,
133 }
134 }
135}
136
137#[derive(Debug, Clone, PartialEq)]
139pub enum OSLogMessageArgument {
140 Undefined,
141 Data(Vec<u8>),
142 Double(f64),
143 Signed(i64),
144 String(String),
145 Unsigned(u64),
146}
147
148pub struct OSLogMessageComponent {
150 ptr: NonNull<c_void>,
151}
152
153impl OSLogMessageComponent {
154 #[must_use]
155 pub fn format_substring(&self) -> String {
156 unsafe {
157 take_optional_c_string(
158 ffi::apple_log_os_log_message_component_copy_format_substring(self.ptr.as_ptr()),
159 )
160 }
161 .unwrap_or_default()
162 }
163
164 #[must_use]
165 pub fn placeholder(&self) -> String {
166 unsafe {
167 take_optional_c_string(ffi::apple_log_os_log_message_component_copy_placeholder(
168 self.ptr.as_ptr(),
169 ))
170 }
171 .unwrap_or_default()
172 }
173
174 #[must_use]
175 pub fn argument_category(&self) -> OSLogMessageArgumentCategory {
176 OSLogMessageArgumentCategory::from_raw(unsafe {
177 ffi::apple_log_os_log_message_component_get_argument_category(self.ptr.as_ptr())
178 })
179 }
180
181 #[must_use]
182 pub fn argument(&self) -> OSLogMessageArgument {
183 match self.argument_category() {
184 OSLogMessageArgumentCategory::Undefined => OSLogMessageArgument::Undefined,
185 OSLogMessageArgumentCategory::Data => {
186 let mut length = 0_isize;
187 let bytes = unsafe {
188 take_owned_bytes(
189 ffi::apple_log_os_log_message_component_copy_data(
190 self.ptr.as_ptr(),
191 &mut length,
192 ),
193 length.max(0) as usize,
194 )
195 };
196 OSLogMessageArgument::Data(bytes)
197 }
198 OSLogMessageArgumentCategory::Double => OSLogMessageArgument::Double(unsafe {
199 ffi::apple_log_os_log_message_component_get_double(self.ptr.as_ptr())
200 }),
201 OSLogMessageArgumentCategory::Int64 => OSLogMessageArgument::Signed(unsafe {
202 ffi::apple_log_os_log_message_component_get_int64(self.ptr.as_ptr())
203 }),
204 OSLogMessageArgumentCategory::String => {
205 let value = unsafe {
206 take_optional_c_string(ffi::apple_log_os_log_message_component_copy_string(
207 self.ptr.as_ptr(),
208 ))
209 }
210 .unwrap_or_default();
211 OSLogMessageArgument::String(value)
212 }
213 OSLogMessageArgumentCategory::UInt64 => OSLogMessageArgument::Unsigned(unsafe {
214 ffi::apple_log_os_log_message_component_get_uint64(self.ptr.as_ptr())
215 }),
216 }
217 }
218}
219
220impl Drop for OSLogMessageComponent {
221 fn drop(&mut self) {
222 unsafe { ffi::apple_log_os_log_message_component_release(self.ptr.as_ptr()) };
223 }
224}
225
226pub trait OSLogEntryCommon {
228 fn raw_entry_ptr(&self) -> *mut c_void;
229
230 #[must_use]
231 fn composed_message(&self) -> String {
232 unsafe {
233 take_optional_c_string(ffi::apple_log_os_log_entry_copy_composed_message(
234 self.raw_entry_ptr(),
235 ))
236 }
237 .unwrap_or_default()
238 }
239
240 #[must_use]
241 fn date(&self) -> SystemTime {
242 secs_to_system_time(unsafe {
243 ffi::apple_log_os_log_entry_get_date_seconds(self.raw_entry_ptr())
244 })
245 }
246
247 #[must_use]
248 fn store_category(&self) -> OSLogStoreCategory {
249 OSLogStoreCategory::from_raw(unsafe {
250 ffi::apple_log_os_log_entry_get_store_category(self.raw_entry_ptr())
251 })
252 }
253}
254
255pub trait OSLogEntryFromProcess: OSLogEntryCommon {
257 #[must_use]
258 fn activity_identifier(&self) -> u64 {
259 unsafe { ffi::apple_log_os_log_entry_get_activity_identifier(self.raw_entry_ptr()) }
260 }
261
262 #[must_use]
263 fn process(&self) -> String {
264 unsafe {
265 take_optional_c_string(ffi::apple_log_os_log_entry_copy_process(
266 self.raw_entry_ptr(),
267 ))
268 }
269 .unwrap_or_default()
270 }
271
272 #[must_use]
273 fn process_identifier(&self) -> i32 {
274 unsafe { ffi::apple_log_os_log_entry_get_process_identifier(self.raw_entry_ptr()) }
275 }
276
277 #[must_use]
278 fn sender(&self) -> String {
279 unsafe {
280 take_optional_c_string(ffi::apple_log_os_log_entry_copy_sender(
281 self.raw_entry_ptr(),
282 ))
283 }
284 .unwrap_or_default()
285 }
286
287 #[must_use]
288 fn thread_identifier(&self) -> u64 {
289 unsafe { ffi::apple_log_os_log_entry_get_thread_identifier(self.raw_entry_ptr()) }
290 }
291}
292
293pub trait OSLogEntryWithPayload: OSLogEntryFromProcess {
295 #[must_use]
296 fn category(&self) -> String {
297 unsafe {
298 take_optional_c_string(ffi::apple_log_os_log_entry_copy_category(
299 self.raw_entry_ptr(),
300 ))
301 }
302 .unwrap_or_default()
303 }
304
305 #[must_use]
306 fn format_string(&self) -> String {
307 unsafe {
308 take_optional_c_string(ffi::apple_log_os_log_entry_copy_format_string(
309 self.raw_entry_ptr(),
310 ))
311 }
312 .unwrap_or_default()
313 }
314
315 #[must_use]
316 fn subsystem(&self) -> String {
317 unsafe {
318 take_optional_c_string(ffi::apple_log_os_log_entry_copy_subsystem(
319 self.raw_entry_ptr(),
320 ))
321 }
322 .unwrap_or_default()
323 }
324
325 #[must_use]
326 fn components(&self) -> Vec<OSLogMessageComponent> {
327 let count = unsafe { ffi::apple_log_os_log_entry_component_count(self.raw_entry_ptr()) };
328 (0..count.max(0) as usize)
329 .filter_map(|index| {
330 NonNull::new(unsafe {
331 ffi::apple_log_os_log_entry_component_get(self.raw_entry_ptr(), index as isize)
332 })
333 .map(|ptr| OSLogMessageComponent { ptr })
334 })
335 .collect()
336 }
337}
338
339pub struct OSLogPosition {
341 ptr: NonNull<c_void>,
342}
343
344impl OSLogPosition {
345 const fn from_raw(ptr: NonNull<c_void>) -> Self {
346 Self { ptr }
347 }
348
349 pub(crate) const fn as_ptr(&self) -> *mut c_void {
350 self.ptr.as_ptr()
351 }
352}
353
354impl Drop for OSLogPosition {
355 fn drop(&mut self) {
356 unsafe { ffi::apple_log_os_log_position_release(self.ptr.as_ptr()) };
357 }
358}
359
360pub enum OSLogStoreEntry {
362 Log(OSLogEntryLog),
363 Signpost(OSLogEntrySignpost),
364 Boundary(OSLogEntryBoundary),
365 Activity(OSLogEntryActivity),
366}
367
368pub struct OSLogStore {
370 ptr: NonNull<c_void>,
371}
372
373impl OSLogStore {
374 pub fn local() -> Result<Self, LogError> {
380 let ptr = bridge_ptr_result("OSLogStore::local", |error_out| unsafe {
381 ffi::apple_log_os_log_store_local(error_out)
382 })?;
383 Ok(Self { ptr })
384 }
385
386 pub fn new(scope: OSLogStoreScope) -> Result<Self, LogError> {
392 let ptr = bridge_ptr_result("OSLogStore::new", |error_out| unsafe {
393 ffi::apple_log_os_log_store_create(scope.raw(), error_out)
394 })?;
395 Ok(Self { ptr })
396 }
397
398 pub fn from_url(path: impl AsRef<Path>) -> Result<Self, LogError> {
404 let path = path_c_string(path.as_ref())?;
405 let ptr = bridge_ptr_result("OSLogStore::from_url", |error_out| unsafe {
406 ffi::apple_log_os_log_store_from_url(path.as_ptr(), error_out)
407 })?;
408 Ok(Self { ptr })
409 }
410
411 #[must_use]
412 pub fn position_at(&self, time: SystemTime) -> OSLogPosition {
413 let ptr = NonNull::new(unsafe {
414 ffi::apple_log_os_log_store_position_date(self.ptr.as_ptr(), system_time_to_secs(time))
415 })
416 .expect("Swift bridge never returns NULL for OSLogStore::position_at");
417 OSLogPosition::from_raw(ptr)
418 }
419
420 #[must_use]
421 pub fn position_time_interval_since_end(&self, duration: Duration) -> OSLogPosition {
422 let ptr = NonNull::new(unsafe {
423 ffi::apple_log_os_log_store_position_since_end(
424 self.ptr.as_ptr(),
425 duration.as_secs_f64(),
426 )
427 })
428 .expect("Swift bridge never returns NULL for OSLogStore::position_time_interval_since_end");
429 OSLogPosition::from_raw(ptr)
430 }
431
432 #[must_use]
433 pub fn position_time_interval_since_latest_boot(&self, duration: Duration) -> OSLogPosition {
434 let ptr = NonNull::new(unsafe {
435 ffi::apple_log_os_log_store_position_since_latest_boot(
436 self.ptr.as_ptr(),
437 duration.as_secs_f64(),
438 )
439 })
440 .expect(
441 "Swift bridge never returns NULL for OSLogStore::position_time_interval_since_latest_boot",
442 );
443 OSLogPosition::from_raw(ptr)
444 }
445
446 pub fn get_entries(
452 &self,
453 options: OSLogEnumeratorOptions,
454 position: Option<&OSLogPosition>,
455 predicate: Option<&str>,
456 ) -> Result<Vec<OSLogStoreEntry>, LogError> {
457 let predicate = predicate
458 .map(|value| c_string_arg("predicate", value))
459 .transpose()?;
460 let list = OSLogEntryList {
461 ptr: bridge_ptr_result("OSLogStore::get_entries", |error_out| unsafe {
462 ffi::apple_log_os_log_store_get_entries(
463 self.ptr.as_ptr(),
464 options.bits(),
465 position.map_or(std::ptr::null_mut(), OSLogPosition::as_ptr),
466 predicate
467 .as_ref()
468 .map_or(std::ptr::null(), |value| value.as_ptr()),
469 error_out,
470 )
471 })?,
472 };
473 let count =
474 unsafe { ffi::apple_log_os_log_entry_list_count(list.ptr.as_ptr()) }.max(0) as usize;
475 let mut entries = Vec::with_capacity(count);
476 for index in 0..count {
477 let Some(entry_ptr) = NonNull::new(unsafe {
478 ffi::apple_log_os_log_entry_list_get(list.ptr.as_ptr(), index as isize)
479 }) else {
480 continue;
481 };
482 let entry = match unsafe { ffi::apple_log_os_log_entry_kind(entry_ptr.as_ptr()) } {
483 1 => OSLogStoreEntry::Log(OSLogEntryLog::from_raw(entry_ptr)),
484 2 => OSLogStoreEntry::Signpost(OSLogEntrySignpost::from_raw(entry_ptr)),
485 3 => OSLogStoreEntry::Boundary(OSLogEntryBoundary::from_raw(entry_ptr)),
486 4 => OSLogStoreEntry::Activity(OSLogEntryActivity::from_raw(entry_ptr)),
487 _ => {
488 unsafe { ffi::apple_log_os_log_entry_release(entry_ptr.as_ptr()) };
489 continue;
490 }
491 };
492 entries.push(entry);
493 }
494 Ok(entries)
495 }
496
497 pub fn entries(
503 &self,
504 options: OSLogEnumeratorOptions,
505 position: Option<&OSLogPosition>,
506 predicate: Option<&str>,
507 ) -> Result<Vec<OSLogStoreEntry>, LogError> {
508 self.get_entries(options, position, predicate)
509 }
510}
511
512impl Drop for OSLogStore {
513 fn drop(&mut self) {
514 unsafe { ffi::apple_log_os_log_store_release(self.ptr.as_ptr()) };
515 }
516}