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
69struct ApplyWithContext<C, F> {
70    context: *mut C,
71    function: Option<F>,
72    panic: Option<Box<dyn std::any::Any + Send>>,
73}
74
75unsafe extern "C" fn apply_trampoline<F>(context: *mut c_void)
76where
77    F: FnOnce(),
78{
79    let context = &mut *context.cast::<ApplyContext<F>>();
80    let closure = context
81        .closure
82        .take()
83        .expect("OSActivity apply trampoline called at most once");
84    if let Err(panic) = catch_unwind(AssertUnwindSafe(closure)) {
85        context.panic = Some(panic);
86    }
87}
88
89unsafe extern "C" fn apply_with_context_trampoline<C, F>(context: *mut c_void)
90where
91    F: FnOnce(&mut C),
92{
93    let context = &mut *context.cast::<ApplyWithContext<C, F>>();
94    let function = context
95        .function
96        .take()
97        .expect("OSActivity apply_with_context trampoline called at most once");
98    let value = &mut *context.context;
99    if let Err(panic) = catch_unwind(AssertUnwindSafe(|| function(value))) {
100        context.panic = Some(panic);
101    }
102}
103
104impl OSActivity {
105    /// Creates a new activity.
106    ///
107    /// Pass `None` to use `OS_ACTIVITY_CURRENT`, or `Some(&OSActivity::null())`
108    /// to pass the explicit `OS_ACTIVITY_NULL` sentinel.
109    ///
110    /// # Errors
111    ///
112    /// Returns an error if the description contains a NUL byte or the bridge fails.
113    pub fn new(
114        description: &str,
115        parent: Option<&OSActivity>,
116        flags: OSActivityFlags,
117    ) -> Result<Self, LogError> {
118        let description = c_string_arg("description", description)?;
119        let ptr = bridge_ptr_result("OSActivity::new", |error_out| unsafe {
120            ffi::apple_log_os_activity_create(
121                description.as_ptr(),
122                parent.map_or(std::ptr::null_mut(), OSActivity::as_ptr),
123                flags.bits(),
124                error_out,
125            )
126        })?;
127        Ok(Self { ptr })
128    }
129
130    /// Creates a deprecated started activity.
131    ///
132    /// # Errors
133    ///
134    /// Returns an error if the description contains a NUL byte or the bridge fails.
135    pub fn start(description: &str, flags: OSActivityFlags) -> Result<Self, LogError> {
136        let description = c_string_arg("description", description)?;
137        let ptr = bridge_ptr_result("OSActivity::start", |error_out| unsafe {
138            ffi::apple_log_os_activity_start(description.as_ptr(), flags.bits(), error_out)
139        })?;
140        Ok(Self { ptr })
141    }
142
143    /// Synchronously initiates an activity around a closure.
144    ///
145    /// # Errors
146    ///
147    /// Returns an error if the description contains a NUL byte.
148    pub fn initiate<F>(
149        description: &str,
150        flags: OSActivityFlags,
151        closure: F,
152    ) -> Result<(), LogError>
153    where
154        F: FnOnce(),
155    {
156        let description = c_string_arg("description", description)?;
157        let mut context = ApplyContext {
158            closure: Some(closure),
159            panic: None,
160        };
161        unsafe {
162            ffi::apple_log_os_activity_initiate(
163                description.as_ptr(),
164                flags.bits(),
165                std::ptr::addr_of_mut!(context).cast(),
166                Some(apply_trampoline::<F>),
167            );
168        }
169        if let Some(panic) = context.panic {
170            resume_unwind(panic);
171        }
172        Ok(())
173    }
174
175    /// Synchronously initiates an activity and passes a mutable context value to the callback.
176    ///
177    /// # Errors
178    ///
179    /// Returns an error if the description contains a NUL byte.
180    pub fn initiate_f<C, F>(
181        description: &str,
182        flags: OSActivityFlags,
183        context: &mut C,
184        function: F,
185    ) -> Result<(), LogError>
186    where
187        F: FnOnce(&mut C),
188    {
189        let description = c_string_arg("description", description)?;
190        let mut bridge_context = ApplyWithContext {
191            context: std::ptr::from_mut(context),
192            function: Some(function),
193            panic: None,
194        };
195        unsafe {
196            ffi::apple_log_os_activity_initiate_f(
197                description.as_ptr(),
198                flags.bits(),
199                std::ptr::addr_of_mut!(bridge_context).cast(),
200                Some(apply_with_context_trampoline::<C, F>),
201            );
202        }
203        if let Some(panic) = bridge_context.panic {
204            resume_unwind(panic);
205        }
206        Ok(())
207    }
208
209    #[must_use]
210    pub fn current() -> Self {
211        Self {
212            ptr: NonNull::new(unsafe { ffi::apple_log_os_activity_current() })
213                .expect("Swift bridge never returns NULL for OSActivity.current"),
214        }
215    }
216
217    #[must_use]
218    pub fn none() -> Self {
219        Self {
220            ptr: NonNull::new(unsafe { ffi::apple_log_os_activity_none() })
221                .expect("Swift bridge never returns NULL for OSActivity.none"),
222        }
223    }
224
225    #[must_use]
226    pub fn null() -> Self {
227        Self {
228            ptr: NonNull::new(unsafe { ffi::apple_log_os_activity_null() })
229                .expect("Swift bridge never returns NULL for OSActivity.null"),
230        }
231    }
232
233    #[must_use]
234    pub fn identifiers(&self) -> ActivityIds {
235        let mut parent = 0_u64;
236        let current =
237            unsafe { ffi::apple_log_os_activity_get_identifier(self.ptr.as_ptr(), &mut parent) };
238        ActivityIds {
239            current,
240            parent: (parent != 0).then_some(parent),
241        }
242    }
243
244    #[must_use]
245    pub fn identifier(&self) -> u64 {
246        self.identifiers().current
247    }
248
249    pub fn apply<F>(&self, closure: F)
250    where
251        F: FnOnce(),
252    {
253        let mut context = ApplyContext {
254            closure: Some(closure),
255            panic: None,
256        };
257        unsafe {
258            ffi::apple_log_os_activity_apply(
259                self.ptr.as_ptr(),
260                std::ptr::addr_of_mut!(context).cast(),
261                Some(apply_trampoline::<F>),
262            );
263        }
264        if let Some(panic) = context.panic {
265            resume_unwind(panic);
266        }
267    }
268
269    /// Enters the activity for the current scope.
270    ///
271    /// # Errors
272    ///
273    /// Returns an error if the bridge cannot allocate scope state.
274    pub fn enter(&self) -> Result<OSActivityScope, LogError> {
275        let ptr =
276            NonNull::new(unsafe { ffi::apple_log_os_activity_scope_enter(self.ptr.as_ptr()) })
277                .ok_or_else(|| LogError::bridge("OSActivity::enter returned NULL"))?;
278        Ok(OSActivityScope { ptr })
279    }
280
281    pub fn end(&self) {
282        unsafe { ffi::apple_log_os_activity_end(self.ptr.as_ptr()) };
283    }
284
285    pub fn label_user_action(label: &str) {
286        let label = sanitized_c_string(label);
287        unsafe { ffi::apple_log_os_activity_label_useraction(label.as_ptr()) };
288    }
289
290    pub fn set_breadcrumb(name: &str) {
291        let name = sanitized_c_string(name);
292        unsafe { ffi::apple_log_os_activity_set_breadcrumb(name.as_ptr()) };
293    }
294
295    pub(crate) const fn as_ptr(&self) -> *mut c_void {
296        self.ptr.as_ptr()
297    }
298}
299
300impl Drop for OSActivity {
301    fn drop(&mut self) {
302        unsafe { ffi::apple_log_os_activity_release(self.ptr.as_ptr()) };
303    }
304}
305
306/// Returns the current thread's active activity id.
307#[must_use]
308pub fn active_activity_id() -> u64 {
309    active_activity_ids().current
310}
311
312/// Returns the current thread's active activity id and parent activity id.
313#[must_use]
314pub fn active_activity_ids() -> ActivityIds {
315    let mut parent = 0_u64;
316    let current = unsafe { ffi::apple_log_os_activity_get_active_identifiers(&mut parent) };
317    ActivityIds {
318        current,
319        parent: (parent != 0).then_some(parent),
320    }
321}