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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct ActivityIds {
17 pub current: u64,
18 pub parent: Option<u64>,
19}
20
21#[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
50pub 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
61pub 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 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 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 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 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 #[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 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#[must_use]
320pub fn active_activity_id() -> u64 {
321 active_activity_ids().current
322}
323
324#[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}