Skip to main content

apple_log/
os_activity.rs

1#![allow(clippy::missing_panics_doc, clippy::use_self)]
2
3use core::ffi::c_void;
4use core::ops::{BitOr, BitOrAssign};
5use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
6use std::ptr::NonNull;
7
8use crate::bridge_support::{bridge_ptr_result, c_string_arg, sanitized_c_string};
9use crate::error::LogError;
10use crate::ffi;
11
12/// Current activity id plus its optional parent id.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct ActivityIds {
15    pub current: u64,
16    pub parent: Option<u64>,
17}
18
19/// Flags for activity creation and deprecated activity start APIs.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
21pub struct OSActivityFlags(u32);
22
23impl OSActivityFlags {
24    pub const DEFAULT: Self = Self(0);
25    pub const DETACHED: Self = Self(0x1);
26    pub const IF_NONE_PRESENT: Self = Self(0x2);
27
28    #[must_use]
29    pub const fn bits(self) -> u32 {
30        self.0
31    }
32}
33
34impl BitOr for OSActivityFlags {
35    type Output = Self;
36
37    fn bitor(self, rhs: Self) -> Self::Output {
38        Self(self.0 | rhs.0)
39    }
40}
41
42impl BitOrAssign for OSActivityFlags {
43    fn bitor_assign(&mut self, rhs: Self) {
44        self.0 |= rhs.0;
45    }
46}
47
48/// Scoped activity guard returned by `OSActivity::enter`.
49pub struct OSActivityScope {
50    ptr: NonNull<c_void>,
51}
52
53impl Drop for OSActivityScope {
54    fn drop(&mut self) {
55        unsafe { ffi::apple_log_os_activity_scope_leave(self.ptr.as_ptr()) };
56    }
57}
58
59/// Safe wrapper around `os_activity_t`.
60pub struct OSActivity {
61    ptr: NonNull<c_void>,
62}
63
64struct ApplyContext<F> {
65    closure: Option<F>,
66    panic: Option<Box<dyn std::any::Any + Send>>,
67}
68
69unsafe extern "C" fn apply_trampoline<F>(context: *mut c_void)
70where
71    F: FnOnce(),
72{
73    let context = &mut *context.cast::<ApplyContext<F>>();
74    let closure = context
75        .closure
76        .take()
77        .expect("OSActivity apply trampoline called at most once");
78    if let Err(panic) = catch_unwind(AssertUnwindSafe(closure)) {
79        context.panic = Some(panic);
80    }
81}
82
83impl OSActivity {
84    /// Creates a new activity.
85    ///
86    /// # Errors
87    ///
88    /// Returns an error if the description contains a NUL byte or the bridge fails.
89    pub fn new(
90        description: &str,
91        parent: Option<&OSActivity>,
92        flags: OSActivityFlags,
93    ) -> Result<Self, LogError> {
94        let description = c_string_arg("description", description)?;
95        let ptr = bridge_ptr_result("OSActivity::new", |error_out| unsafe {
96            ffi::apple_log_os_activity_create(
97                description.as_ptr(),
98                parent.map_or(std::ptr::null_mut(), OSActivity::as_ptr),
99                flags.bits(),
100                error_out,
101            )
102        })?;
103        Ok(Self { ptr })
104    }
105
106    /// Creates a deprecated started activity.
107    ///
108    /// # Errors
109    ///
110    /// Returns an error if the description contains a NUL byte or the bridge fails.
111    pub fn start(description: &str, flags: OSActivityFlags) -> Result<Self, LogError> {
112        let description = c_string_arg("description", description)?;
113        let ptr = bridge_ptr_result("OSActivity::start", |error_out| unsafe {
114            ffi::apple_log_os_activity_start(description.as_ptr(), flags.bits(), error_out)
115        })?;
116        Ok(Self { ptr })
117    }
118
119    #[must_use]
120    pub fn current() -> Self {
121        Self {
122            ptr: NonNull::new(unsafe { ffi::apple_log_os_activity_current() })
123                .expect("Swift bridge never returns NULL for OSActivity.current"),
124        }
125    }
126
127    #[must_use]
128    pub fn none() -> Self {
129        Self {
130            ptr: NonNull::new(unsafe { ffi::apple_log_os_activity_none() })
131                .expect("Swift bridge never returns NULL for OSActivity.none"),
132        }
133    }
134
135    #[must_use]
136    pub fn identifiers(&self) -> ActivityIds {
137        let mut parent = 0_u64;
138        let current = unsafe { ffi::apple_log_os_activity_get_identifier(self.ptr.as_ptr(), &mut parent) };
139        ActivityIds {
140            current,
141            parent: (parent != 0).then_some(parent),
142        }
143    }
144
145    #[must_use]
146    pub fn identifier(&self) -> u64 {
147        self.identifiers().current
148    }
149
150    pub fn apply<F>(&self, closure: F)
151    where
152        F: FnOnce(),
153    {
154        let mut context = ApplyContext {
155            closure: Some(closure),
156            panic: None,
157        };
158        unsafe {
159            ffi::apple_log_os_activity_apply(
160                self.ptr.as_ptr(),
161                std::ptr::addr_of_mut!(context).cast(),
162                Some(apply_trampoline::<F>),
163            );
164        }
165        if let Some(panic) = context.panic {
166            resume_unwind(panic);
167        }
168    }
169
170    /// Enters the activity for the current scope.
171    ///
172    /// # Errors
173    ///
174    /// Returns an error if the bridge cannot allocate scope state.
175    pub fn enter(&self) -> Result<OSActivityScope, LogError> {
176        let ptr = NonNull::new(unsafe { ffi::apple_log_os_activity_scope_enter(self.ptr.as_ptr()) })
177            .ok_or_else(|| LogError::bridge("OSActivity::enter returned NULL"))?;
178        Ok(OSActivityScope { ptr })
179    }
180
181    pub fn end(&self) {
182        unsafe { ffi::apple_log_os_activity_end(self.ptr.as_ptr()) };
183    }
184
185    pub fn label_user_action(label: &str) {
186        let label = sanitized_c_string(label);
187        unsafe { ffi::apple_log_os_activity_label_useraction(label.as_ptr()) };
188    }
189
190    pub fn set_breadcrumb(name: &str) {
191        let name = sanitized_c_string(name);
192        unsafe { ffi::apple_log_os_activity_set_breadcrumb(name.as_ptr()) };
193    }
194
195    pub(crate) const fn as_ptr(&self) -> *mut c_void {
196        self.ptr.as_ptr()
197    }
198}
199
200impl Drop for OSActivity {
201    fn drop(&mut self) {
202        unsafe { ffi::apple_log_os_activity_release(self.ptr.as_ptr()) };
203    }
204}
205
206/// Returns the current thread's active activity id.
207#[must_use]
208pub fn active_activity_id() -> u64 {
209    active_activity_ids().current
210}
211
212/// Returns the current thread's active activity id and parent activity id.
213#[must_use]
214pub fn active_activity_ids() -> ActivityIds {
215    let mut parent = 0_u64;
216    let current = unsafe { ffi::apple_log_os_activity_get_active_identifiers(&mut parent) };
217    ActivityIds {
218        current,
219        parent: (parent != 0).then_some(parent),
220    }
221}