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(
168 ffi::apple_log_os_log_message_component_copy_placeholder(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(self.ptr.as_ptr(), &mut length),
190 length.max(0) as usize,
191 )
192 };
193 OSLogMessageArgument::Data(bytes)
194 }
195 OSLogMessageArgumentCategory::Double => OSLogMessageArgument::Double(unsafe {
196 ffi::apple_log_os_log_message_component_get_double(self.ptr.as_ptr())
197 }),
198 OSLogMessageArgumentCategory::Int64 => OSLogMessageArgument::Signed(unsafe {
199 ffi::apple_log_os_log_message_component_get_int64(self.ptr.as_ptr())
200 }),
201 OSLogMessageArgumentCategory::String => {
202 let value = unsafe {
203 take_optional_c_string(
204 ffi::apple_log_os_log_message_component_copy_string(self.ptr.as_ptr()),
205 )
206 }
207 .unwrap_or_default();
208 OSLogMessageArgument::String(value)
209 }
210 OSLogMessageArgumentCategory::UInt64 => OSLogMessageArgument::Unsigned(unsafe {
211 ffi::apple_log_os_log_message_component_get_uint64(self.ptr.as_ptr())
212 }),
213 }
214 }
215}
216
217impl Drop for OSLogMessageComponent {
218 fn drop(&mut self) {
219 unsafe { ffi::apple_log_os_log_message_component_release(self.ptr.as_ptr()) };
220 }
221}
222
223pub trait OSLogEntryCommon {
225 fn raw_entry_ptr(&self) -> *mut c_void;
226
227 #[must_use]
228 fn composed_message(&self) -> String {
229 unsafe { take_optional_c_string(ffi::apple_log_os_log_entry_copy_composed_message(self.raw_entry_ptr())) }
230 .unwrap_or_default()
231 }
232
233 #[must_use]
234 fn date(&self) -> SystemTime {
235 secs_to_system_time(unsafe { ffi::apple_log_os_log_entry_get_date_seconds(self.raw_entry_ptr()) })
236 }
237
238 #[must_use]
239 fn store_category(&self) -> OSLogStoreCategory {
240 OSLogStoreCategory::from_raw(unsafe {
241 ffi::apple_log_os_log_entry_get_store_category(self.raw_entry_ptr())
242 })
243 }
244}
245
246pub trait OSLogEntryFromProcess: OSLogEntryCommon {
248 #[must_use]
249 fn activity_identifier(&self) -> u64 {
250 unsafe { ffi::apple_log_os_log_entry_get_activity_identifier(self.raw_entry_ptr()) }
251 }
252
253 #[must_use]
254 fn process(&self) -> String {
255 unsafe { take_optional_c_string(ffi::apple_log_os_log_entry_copy_process(self.raw_entry_ptr())) }
256 .unwrap_or_default()
257 }
258
259 #[must_use]
260 fn process_identifier(&self) -> i32 {
261 unsafe { ffi::apple_log_os_log_entry_get_process_identifier(self.raw_entry_ptr()) }
262 }
263
264 #[must_use]
265 fn sender(&self) -> String {
266 unsafe { take_optional_c_string(ffi::apple_log_os_log_entry_copy_sender(self.raw_entry_ptr())) }
267 .unwrap_or_default()
268 }
269
270 #[must_use]
271 fn thread_identifier(&self) -> u64 {
272 unsafe { ffi::apple_log_os_log_entry_get_thread_identifier(self.raw_entry_ptr()) }
273 }
274}
275
276pub trait OSLogEntryWithPayload: OSLogEntryFromProcess {
278 #[must_use]
279 fn category(&self) -> String {
280 unsafe { take_optional_c_string(ffi::apple_log_os_log_entry_copy_category(self.raw_entry_ptr())) }
281 .unwrap_or_default()
282 }
283
284 #[must_use]
285 fn format_string(&self) -> String {
286 unsafe {
287 take_optional_c_string(ffi::apple_log_os_log_entry_copy_format_string(self.raw_entry_ptr()))
288 }
289 .unwrap_or_default()
290 }
291
292 #[must_use]
293 fn subsystem(&self) -> String {
294 unsafe { take_optional_c_string(ffi::apple_log_os_log_entry_copy_subsystem(self.raw_entry_ptr())) }
295 .unwrap_or_default()
296 }
297
298 #[must_use]
299 fn components(&self) -> Vec<OSLogMessageComponent> {
300 let count = unsafe { ffi::apple_log_os_log_entry_component_count(self.raw_entry_ptr()) };
301 (0..count.max(0) as usize)
302 .filter_map(|index| {
303 NonNull::new(unsafe {
304 ffi::apple_log_os_log_entry_component_get(self.raw_entry_ptr(), index as isize)
305 })
306 .map(|ptr| OSLogMessageComponent { ptr })
307 })
308 .collect()
309 }
310}
311
312pub struct OSLogPosition {
314 ptr: NonNull<c_void>,
315}
316
317impl OSLogPosition {
318 const fn from_raw(ptr: NonNull<c_void>) -> Self {
319 Self { ptr }
320 }
321
322 pub(crate) const fn as_ptr(&self) -> *mut c_void {
323 self.ptr.as_ptr()
324 }
325}
326
327impl Drop for OSLogPosition {
328 fn drop(&mut self) {
329 unsafe { ffi::apple_log_os_log_position_release(self.ptr.as_ptr()) };
330 }
331}
332
333pub enum OSLogStoreEntry {
335 Log(OSLogEntryLog),
336 Signpost(OSLogEntrySignpost),
337 Boundary(OSLogEntryBoundary),
338 Activity(OSLogEntryActivity),
339}
340
341pub struct OSLogStore {
343 ptr: NonNull<c_void>,
344}
345
346impl OSLogStore {
347 pub fn local() -> Result<Self, LogError> {
353 let ptr = bridge_ptr_result("OSLogStore::local", |error_out| unsafe {
354 ffi::apple_log_os_log_store_local(error_out)
355 })?;
356 Ok(Self { ptr })
357 }
358
359 pub fn new(scope: OSLogStoreScope) -> Result<Self, LogError> {
365 let ptr = bridge_ptr_result("OSLogStore::new", |error_out| unsafe {
366 ffi::apple_log_os_log_store_create(scope.raw(), error_out)
367 })?;
368 Ok(Self { ptr })
369 }
370
371 pub fn from_url(path: impl AsRef<Path>) -> Result<Self, LogError> {
377 let path = path_c_string(path.as_ref())?;
378 let ptr = bridge_ptr_result("OSLogStore::from_url", |error_out| unsafe {
379 ffi::apple_log_os_log_store_from_url(path.as_ptr(), error_out)
380 })?;
381 Ok(Self { ptr })
382 }
383
384 #[must_use]
385 pub fn position_at(&self, time: SystemTime) -> OSLogPosition {
386 let ptr = NonNull::new(unsafe {
387 ffi::apple_log_os_log_store_position_date(self.ptr.as_ptr(), system_time_to_secs(time))
388 })
389 .expect("Swift bridge never returns NULL for OSLogStore::position_at");
390 OSLogPosition::from_raw(ptr)
391 }
392
393 #[must_use]
394 pub fn position_time_interval_since_end(&self, duration: Duration) -> OSLogPosition {
395 let ptr = NonNull::new(unsafe {
396 ffi::apple_log_os_log_store_position_since_end(self.ptr.as_ptr(), duration.as_secs_f64())
397 })
398 .expect("Swift bridge never returns NULL for OSLogStore::position_time_interval_since_end");
399 OSLogPosition::from_raw(ptr)
400 }
401
402 #[must_use]
403 pub fn position_time_interval_since_latest_boot(&self, duration: Duration) -> OSLogPosition {
404 let ptr = NonNull::new(unsafe {
405 ffi::apple_log_os_log_store_position_since_latest_boot(
406 self.ptr.as_ptr(),
407 duration.as_secs_f64(),
408 )
409 })
410 .expect(
411 "Swift bridge never returns NULL for OSLogStore::position_time_interval_since_latest_boot",
412 );
413 OSLogPosition::from_raw(ptr)
414 }
415
416 pub fn get_entries(
422 &self,
423 options: OSLogEnumeratorOptions,
424 position: Option<&OSLogPosition>,
425 predicate: Option<&str>,
426 ) -> Result<Vec<OSLogStoreEntry>, LogError> {
427 let predicate = predicate.map(|value| c_string_arg("predicate", value)).transpose()?;
428 let list = OSLogEntryList {
429 ptr: bridge_ptr_result("OSLogStore::get_entries", |error_out| unsafe {
430 ffi::apple_log_os_log_store_get_entries(
431 self.ptr.as_ptr(),
432 options.bits(),
433 position.map_or(std::ptr::null_mut(), OSLogPosition::as_ptr),
434 predicate.as_ref().map_or(std::ptr::null(), |value| value.as_ptr()),
435 error_out,
436 )
437 })?,
438 };
439 let count = unsafe { ffi::apple_log_os_log_entry_list_count(list.ptr.as_ptr()) }.max(0) as usize;
440 let mut entries = Vec::with_capacity(count);
441 for index in 0..count {
442 let Some(entry_ptr) = NonNull::new(unsafe {
443 ffi::apple_log_os_log_entry_list_get(list.ptr.as_ptr(), index as isize)
444 }) else {
445 continue;
446 };
447 let entry = match unsafe { ffi::apple_log_os_log_entry_kind(entry_ptr.as_ptr()) } {
448 1 => OSLogStoreEntry::Log(OSLogEntryLog::from_raw(entry_ptr)),
449 2 => OSLogStoreEntry::Signpost(OSLogEntrySignpost::from_raw(entry_ptr)),
450 3 => OSLogStoreEntry::Boundary(OSLogEntryBoundary::from_raw(entry_ptr)),
451 4 => OSLogStoreEntry::Activity(OSLogEntryActivity::from_raw(entry_ptr)),
452 _ => {
453 unsafe { ffi::apple_log_os_log_entry_release(entry_ptr.as_ptr()) };
454 continue;
455 }
456 };
457 entries.push(entry);
458 }
459 Ok(entries)
460 }
461
462 pub fn entries(
468 &self,
469 options: OSLogEnumeratorOptions,
470 position: Option<&OSLogPosition>,
471 predicate: Option<&str>,
472 ) -> Result<Vec<OSLogStoreEntry>, LogError> {
473 self.get_entries(options, position, predicate)
474 }
475}
476
477impl Drop for OSLogStore {
478 fn drop(&mut self) {
479 unsafe { ffi::apple_log_os_log_store_release(self.ptr.as_ptr()) };
480 }
481}