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
44pub fn is_recorded_anywhere() -> bool {
47 is_observed_anywhere() || tracing::enabled!(target: "guinea", Level::DEBUG)
48}
49
50fn observer() -> Option<Observer> {
51 OBSERVER.with(|slot| slot.borrow().clone())
52}
53
54pub(crate) fn wanted() -> bool {
55 OBSERVER.with(|slot| slot.borrow().is_some())
56 || tracing::enabled!(target: "guinea", Level::DEBUG)
57}
58
59pub fn is_point_target(target: &str) -> bool {
62 target.starts_with("guinea::")
63}
64
65macro_rules! point {
66 ($target:literal, $record:expr $(, $($field:tt)+)?) => {
67 tracing::debug!(
68 target: $target,
69 id = $record.id.get(),
70 parent = $record.parent.map(Cause::get)
71 $(, $($field)+)?
72 )
73 };
74}
75
76fn write(record: &Record) {
77 match &record.point {
78 Point::Action { message } => point!("guinea::action", record, action = %message),
79 Point::Send { actor, message } => {
80 point!("guinea::send", record, actor = %actor, msg = %message)
81 }
82 Point::Handle { actor, message } => {
83 point!("guinea::handle", record, actor = %actor, msg = %message)
84 }
85 Point::Spawn {
86 actor,
87 actor_id,
88 output,
89 } => point!(
90 "guinea::spawn",
91 record,
92 actor = %actor,
93 actor_id,
94 output = %output
95 ),
96 Point::Settled {
97 actor,
98 actor_id,
99 output,
100 took_us,
101 } => point!(
102 "guinea::settled",
103 record,
104 actor = %actor,
105 actor_id,
106 output = %output,
107 took_us
108 ),
109 Point::Cancelled {
110 actor,
111 actor_id,
112 output,
113 took_us,
114 } => point!(
115 "guinea::cancelled",
116 record,
117 actor = %actor,
118 actor_id,
119 output = %output,
120 took_us
121 ),
122 Point::Source {
123 actor,
124 actor_id,
125 output,
126 } => point!(
127 "guinea::source",
128 record,
129 actor = %actor,
130 actor_id,
131 output = %output
132 ),
133 Point::Arrived {
134 actor,
135 actor_id,
136 output,
137 source,
138 } => point!(
139 "guinea::arrived",
140 record,
141 actor = %actor,
142 actor_id,
143 output = %output,
144 source
145 ),
146 Point::Pull {
147 actor,
148 actor_id,
149 output,
150 source,
151 } => point!(
152 "guinea::pull",
153 record,
154 actor = %actor,
155 actor_id,
156 output = %output,
157 source
158 ),
159 Point::Closed {
160 actor,
161 actor_id,
162 output,
163 took_us,
164 gone,
165 } => point!(
166 "guinea::closed",
167 record,
168 actor = %actor,
169 actor_id,
170 output = %output,
171 took_us,
172 gone
173 ),
174 Point::Publish {
175 event,
176 bus,
177 subscribers,
178 } => point!(
179 "guinea::publish",
180 record,
181 event = %event,
182 bus = %bus,
183 subscribers
184 ),
185 Point::Deliver { event, bus } => {
186 point!("guinea::deliver", record, event = %event, bus = %bus)
187 }
188 Point::Push { reducer } => point!("guinea::push", record, reducer = %reducer),
189 Point::Navigate { root, to } => point!("guinea::navigate", record, root = %root, to = %to),
190 Point::Tick { timer } => point!("guinea::tick", record, timer),
191 Point::Store {
192 op,
193 path,
194 field,
195 outside,
196 } => point!(
197 "guinea::store",
198 record,
199 op = %op,
200 path = %path,
201 field = field.as_deref(),
202 outside
203 ),
204 Point::Render { segment, took_us } => {
205 point!("guinea::render", record, segment = %segment, took_us)
206 }
207 Point::Span {
208 name,
209 file,
210 line,
211 module,
212 fields,
213 ..
214 } => point!(
215 "guinea::span",
216 record,
217 span = %name,
218 module = *module,
219 file = *file,
220 line = *line,
221 fields = %fields
222 ),
223 Point::Note(text) => point!("guinea::note", record, "{text}"),
224 Point::Log { .. } => {}
225 }
226}
227
228pub(crate) fn emit(trace: Trace) {
229 match &trace {
230 Trace::Begin(record) | Trace::Mark(record) => write(record),
231 Trace::End { id, took } => tracing::trace!(
232 target: "guinea::end",
233 id = id.get(),
234 took_us = took.as_micros() as u64
235 ),
236 }
237
238 if let Some(observer) = observer() {
239 observer(&trace);
240 }
241}