1use std::cell::RefCell;
4use std::rc::Rc;
5use std::sync::atomic::{AtomicUsize, Ordering};
6
7use tracing::Level;
8
9use crate::{Cause, Point, Record, Trace};
10
11pub type Observer = Rc<dyn Fn(&Trace)>;
12
13thread_local! {
14 static OBSERVER: RefCell<Option<Observer>> = const { RefCell::new(None) };
15}
16
17static OBSERVED_THREADS: AtomicUsize = AtomicUsize::new(0);
18
19pub fn observe(observer: impl Fn(&Trace) + 'static) {
22 let before = OBSERVER.with(|slot| slot.borrow_mut().replace(Rc::new(observer)));
23 if before.is_none() {
24 OBSERVED_THREADS.fetch_add(1, Ordering::Relaxed);
25 }
26}
27
28pub fn stop_observing() {
29 if OBSERVER.with(|slot| slot.borrow_mut().take()).is_some() {
30 OBSERVED_THREADS.fetch_sub(1, Ordering::Relaxed);
31 }
32}
33
34pub fn is_observed() -> bool {
36 OBSERVER.with(|slot| slot.borrow().is_some())
37}
38
39pub fn is_observed_anywhere() -> bool {
41 OBSERVED_THREADS.load(Ordering::Relaxed) > 0
42}
43
44fn observer() -> Option<Observer> {
45 OBSERVER.with(|slot| slot.borrow().clone())
46}
47
48pub(crate) fn wanted() -> bool {
49 OBSERVER.with(|slot| slot.borrow().is_some())
50 || tracing::enabled!(target: "guinea", Level::DEBUG)
51}
52
53pub fn is_point_target(target: &str) -> bool {
56 target.starts_with("guinea::")
57}
58
59macro_rules! point {
60 ($target:literal, $record:expr $(, $($field:tt)+)?) => {
61 tracing::debug!(
62 target: $target,
63 id = $record.id.get(),
64 parent = $record.parent.map(Cause::get)
65 $(, $($field)+)?
66 )
67 };
68}
69
70fn write(record: &Record) {
71 match &record.point {
72 Point::Action { message } => point!("guinea::action", record, action = %message),
73 Point::Send { actor, message } => {
74 point!("guinea::send", record, actor = %actor, msg = %message)
75 }
76 Point::Handle { actor, message } => {
77 point!("guinea::handle", record, actor = %actor, msg = %message)
78 }
79 Point::Spawn {
80 actor,
81 actor_id,
82 output,
83 } => point!(
84 "guinea::spawn",
85 record,
86 actor = %actor,
87 actor_id,
88 output = %output
89 ),
90 Point::Settled {
91 actor,
92 actor_id,
93 output,
94 took_us,
95 } => point!(
96 "guinea::settled",
97 record,
98 actor = %actor,
99 actor_id,
100 output = %output,
101 took_us
102 ),
103 Point::Cancelled {
104 actor,
105 actor_id,
106 output,
107 took_us,
108 } => point!(
109 "guinea::cancelled",
110 record,
111 actor = %actor,
112 actor_id,
113 output = %output,
114 took_us
115 ),
116 Point::Publish {
117 event,
118 bus,
119 subscribers,
120 } => point!(
121 "guinea::publish",
122 record,
123 event = %event,
124 bus = %bus,
125 subscribers
126 ),
127 Point::Deliver { event, bus } => {
128 point!("guinea::deliver", record, event = %event, bus = %bus)
129 }
130 Point::Push { reducer } => point!("guinea::push", record, reducer = %reducer),
131 Point::Navigate { root, to } => point!("guinea::navigate", record, root = %root, to = %to),
132 Point::Tick { timer } => point!("guinea::tick", record, timer),
133 Point::Store {
134 op,
135 path,
136 field,
137 outside,
138 } => point!(
139 "guinea::store",
140 record,
141 op = %op,
142 path = %path,
143 field = field.as_deref(),
144 outside
145 ),
146 Point::Render { segment, took_us } => {
147 point!("guinea::render", record, segment = %segment, took_us)
148 }
149 Point::Note(text) => point!("guinea::note", record, "{text}"),
150 Point::Log { .. } => {}
151 }
152}
153
154pub(crate) fn emit(trace: Trace) {
155 match &trace {
156 Trace::Begin(record) | Trace::Mark(record) => write(record),
157 Trace::End { id, took } => tracing::trace!(
158 target: "guinea::end",
159 id = id.get(),
160 took_us = took.as_micros() as u64
161 ),
162 }
163
164 if let Some(observer) = observer() {
165 observer(&trace);
166 }
167}