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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct ActivityIds {
15 pub current: u64,
16 pub parent: Option<u64>,
17}
18
19#[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
48pub 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
59pub 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 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 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 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 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 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#[must_use]
308pub fn active_activity_id() -> u64 {
309 active_activity_ids().current
310}
311
312#[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}