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::Closed {
147 actor,
148 actor_id,
149 output,
150 took_us,
151 gone,
152 } => point!(
153 "guinea::closed",
154 record,
155 actor = %actor,
156 actor_id,
157 output = %output,
158 took_us,
159 gone
160 ),
161 Point::Publish {
162 event,
163 bus,
164 subscribers,
165 } => point!(
166 "guinea::publish",
167 record,
168 event = %event,
169 bus = %bus,
170 subscribers
171 ),
172 Point::Deliver { event, bus } => {
173 point!("guinea::deliver", record, event = %event, bus = %bus)
174 }
175 Point::Push { reducer } => point!("guinea::push", record, reducer = %reducer),
176 Point::Navigate { root, to } => point!("guinea::navigate", record, root = %root, to = %to),
177 Point::Tick { timer } => point!("guinea::tick", record, timer),
178 Point::Store {
179 op,
180 path,
181 field,
182 outside,
183 } => point!(
184 "guinea::store",
185 record,
186 op = %op,
187 path = %path,
188 field = field.as_deref(),
189 outside
190 ),
191 Point::Render { segment, took_us } => {
192 point!("guinea::render", record, segment = %segment, took_us)
193 }
194 Point::Span {
195 name,
196 file,
197 line,
198 module,
199 fields,
200 ..
201 } => point!(
202 "guinea::span",
203 record,
204 span = %name,
205 module = *module,
206 file = *file,
207 line = *line,
208 fields = %fields
209 ),
210 Point::Note(text) => point!("guinea::note", record, "{text}"),
211 Point::Log { .. } => {}
212 }
213}
214
215pub(crate) fn emit(trace: Trace) {
216 match &trace {
217 Trace::Begin(record) | Trace::Mark(record) => write(record),
218 Trace::End { id, took } => tracing::trace!(
219 target: "guinea::end",
220 id = id.get(),
221 took_us = took.as_micros() as u64
222 ),
223 }
224
225 if let Some(observer) = observer() {
226 observer(&trace);
227 }
228}