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#[cfg(feature = "async")]
12use std::future::Future;
13
14/// Current activity id plus its optional parent id.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct ActivityIds {
17    pub current: u64,
18    pub parent: Option<u64>,
19}
20
21/// Flags for activity creation and deprecated activity start APIs.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
23pub struct OSActivityFlags(u32);
24
25impl OSActivityFlags {
26    pub const DEFAULT: Self = Self(0);
27    pub const DETACHED: Self = Self(0x1);
28    pub const IF_NONE_PRESENT: Self = Self(0x2);
29
30    #[must_use]
31    pub const fn bits(self) -> u32 {
32        self.0
33    }
34}
35
36impl BitOr for OSActivityFlags {
37    type Output = Self;
38
39    fn bitor(self, rhs: Self) -> Self::Output {
40        Self(self.0 | rhs.0)
41    }
42}
43
44impl BitOrAssign for OSActivityFlags {
45    fn bitor_assign(&mut self, rhs: Self) {
46        self.0 |= rhs.0;
47    }
48}
49
50/// Scoped activity guard returned by `OSActivity::enter`.
51pub struct OSActivityScope {
52    ptr: NonNull<c_void>,
53}
54
55impl Drop for OSActivityScope {
56    fn drop(&mut self) {
57        unsafe { ffi::apple_log_os_activity_scope_leave(self.ptr.as_ptr()) };
58    }
59}
60
61/// Safe wrapper around `os_activity_t`.
62pub struct OSActivity {
63    ptr: NonNull<c_void>,
64}
65
66struct ApplyContext<F> {
67    closure: Option<F>,
68    panic: Option<Box<dyn std::any::Any + Send>>,
69}
70
71struct ApplyWithContext<C, F> {
72    context: *mut C,
73    function: Option<F>,
74    panic: Option<Box<dyn std::any::Any + Send>>,
75}
76
77unsafe extern "C" fn apply_trampoline<F>(context: *mut c_void)
78where
79    F: FnOnce(),
80{
81    let context = &mut *context.cast::<ApplyContext<F>>();
82    let closure = context
83        .closure
84        .take()
85        .expect("OSActivity apply trampoline called at most once");
86    if let Err(panic) = catch_unwind(AssertUnwindSafe(closure)) {
87        context.panic = Some(panic);
88    }
89}
90
91unsafe extern "C" fn apply_with_context_trampoline<C, F>(context: *mut c_void)
92where
93    F: FnOnce(&mut C),
94{
95    let context = &mut *context.cast::<ApplyWithContext<C, F>>();
96    let function = context
97        .function
98        .take()
99        .expect("OSActivity apply_with_context trampoline called at most once");
100    let value = &mut *context.context;
101    if let Err(panic) = catch_unwind(AssertUnwindSafe(|| function(value))) {
102        context.panic = Some(panic);
103    }
104}
105
106impl OSActivity {
107    /// Creates a new activity.
108    ///
109    /// Pass `None` to use `OS_ACTIVITY_CURRENT`, or `Some(&OSActivity::null())`
110    /// to pass the explicit `OS_ACTIVITY_NULL` sentinel.
111    ///
112    /// # Errors
113    ///
114    /// Returns an error if the description contains a NUL byte or the bridge fails.
115    pub fn new(
116        description: &str,
117        parent: Option<&OSActivity>,
118        flags: OSActivityFlags,
119    ) -> Result<Self, LogError> {
120        let description = c_string_arg("description", description)?;
121        let ptr = bridge_ptr_result("OSActivity::new", |error_out| unsafe {
122            ffi::apple_log_os_activity_create(
123                description.as_ptr(),
124                parent.map_or(std::ptr::null_mut(), OSActivity::as_ptr),
125                flags.bits(),
126                error_out,
127            )
128        })?;
129        Ok(Self { ptr })
130    }
131
132    /// Creates a deprecated started activity.
133    ///
134    /// # Errors
135    ///
136    /// Returns an error if the description contains a NUL byte or the bridge fails.
137    pub fn start(description: &str, flags: OSActivityFlags) -> Result<Self, LogError> {
138        let description = c_string_arg("description", description)?;
139        let ptr = bridge_ptr_result("OSActivity::start", |error_out| unsafe {
140            ffi::apple_log_os_activity_start(description.as_ptr(), flags.bits(), error_out)
141        })?;
142        Ok(Self { ptr })
143    }
144
145    /// Synchronously initiates an activity around a closure.
146    ///
147    /// # Errors
148    ///
149    /// Returns an error if the description contains a NUL byte.
150    pub fn initiate<F>(
151        description: &str,
152        flags: OSActivityFlags,
153        closure: F,
154    ) -> Result<(), LogError>
155    where
156        F: FnOnce(),
157    {
158        let description = c_string_arg("description", description)?;
159        let mut context = ApplyContext {
160            closure: Some(closure),
161            panic: None,
162        };
163        unsafe {
164            ffi::apple_log_os_activity_initiate(
165                description.as_ptr(),
166                flags.bits(),
167                std::ptr::addr_of_mut!(context).cast(),
168                Some(apply_trampoline::<F>),
169            );
170        }
171        if let Some(panic) = context.panic {
172            resume_unwind(panic);
173        }
174        Ok(())
175    }
176
177    /// Synchronously initiates an activity and passes a mutable context value to the callback.
178    ///
179    /// # Errors
180    ///
181    /// Returns an error if the description contains a NUL byte.
182    pub fn initiate_f<C, F>(
183        description: &str,
184        flags: OSActivityFlags,
185        context: &mut C,
186        function: F,
187    ) -> Result<(), LogError>
188    where
189        F: FnOnce(&mut C),
190    {
191        let description = c_string_arg("description", description)?;
192        let mut bridge_context = ApplyWithContext {
193            context: std::ptr::from_mut(context),
194            function: Some(function),
195            panic: None,
196        };
197        unsafe {
198            ffi::apple_log_os_activity_initiate_f(
199                description.as_ptr(),
200                flags.bits(),
201                std::ptr::addr_of_mut!(bridge_context).cast(),
202                Some(apply_with_context_trampoline::<C, F>),
203            );
204        }
205        if let Some(panic) = bridge_context.panic {
206            resume_unwind(panic);
207        }
208        Ok(())
209    }
210
211    #[must_use]
212    pub fn current() -> Self {
213        Self {
214            ptr: NonNull::new(unsafe { ffi::apple_log_os_activity_current() })
215                .expect("Swift bridge never returns NULL for OSActivity.current"),
216        }
217    }
218
219    #[must_use]
220    pub fn none() -> Self {
221        Self {
222            ptr: NonNull::new(unsafe { ffi::apple_log_os_activity_none() })
223                .expect("Swift bridge never returns NULL for OSActivity.none"),
224        }
225    }
226
227    #[must_use]
228    pub fn null() -> Self {
229        Self {
230            ptr: NonNull::new(unsafe { ffi::apple_log_os_activity_null() })
231                .expect("Swift bridge never returns NULL for OSActivity.null"),
232        }
233    }
234
235    #[must_use]
236    pub fn identifiers(&self) -> ActivityIds {
237        let mut parent = 0_u64;
238        let current =
239            unsafe { ffi::apple_log_os_activity_get_identifier(self.ptr.as_ptr(), &mut parent) };
240        ActivityIds {
241            current,
242            parent: (parent != 0).then_some(parent),
243        }
244    }
245
246    #[must_use]
247    pub fn identifier(&self) -> u64 {
248        self.identifiers().current
249    }
250
251    pub fn apply<F>(&self, closure: F)
252    where
253        F: FnOnce(),
254    {
255        let mut context = ApplyContext {
256            closure: Some(closure),
257            panic: None,
258        };
259        unsafe {
260            ffi::apple_log_os_activity_apply(
261                self.ptr.as_ptr(),
262                std::ptr::addr_of_mut!(context).cast(),
263                Some(apply_trampoline::<F>),
264            );
265        }
266        if let Some(panic) = context.panic {
267            resume_unwind(panic);
268        }
269    }
270
271    /// Wrap a future so this activity is entered around every poll.
272    #[cfg(feature = "async")]
273    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
274    pub const fn instrument_future<F>(self, future: F) -> crate::async_api::ActivityFuture<F>
275    where
276        F: Future,
277    {
278        crate::async_api::ActivityFuture::new(self, future)
279    }
280
281    /// Enters the activity for the current scope.
282    ///
283    /// # Errors
284    ///
285    /// Returns an error if the bridge cannot allocate scope state.
286    pub fn enter(&self) -> Result<OSActivityScope, LogError> {
287        let ptr =
288            NonNull::new(unsafe { ffi::apple_log_os_activity_scope_enter(self.ptr.as_ptr()) })
289                .ok_or_else(|| LogError::bridge("OSActivity::enter returned NULL"))?;
290        Ok(OSActivityScope { ptr })
291    }
292
293    pub fn end(&self) {
294        unsafe { ffi::apple_log_os_activity_end(self.ptr.as_ptr()) };
295    }
296
297    pub fn label_user_action(label: &str) {
298        let label = sanitized_c_string(label);
299        unsafe { ffi::apple_log_os_activity_label_useraction(label.as_ptr()) };
300    }
301
302    pub fn set_breadcrumb(name: &str) {
303        let name = sanitized_c_string(name);
304        unsafe { ffi::apple_log_os_activity_set_breadcrumb(name.as_ptr()) };
305    }
306
307    pub(crate) const fn as_ptr(&self) -> *mut c_void {
308        self.ptr.as_ptr()
309    }
310}
311
312impl Drop for OSActivity {
313    fn drop(&mut self) {
314        unsafe { ffi::apple_log_os_activity_release(self.ptr.as_ptr()) };
315    }
316}
317
318/// Returns the current thread's active activity id.
319#[must_use]
320pub fn active_activity_id() -> u64 {
321    active_activity_ids().current
322}
323
324/// Returns the current thread's active activity id and parent activity id.
325#[must_use]
326pub fn active_activity_ids() -> ActivityIds {
327    let mut parent = 0_u64;
328    let current = unsafe { ffi::apple_log_os_activity_get_active_identifiers(&mut parent) };
329    ActivityIds {
330        current,
331        parent: (parent != 0).then_some(parent),
332    }
333}