Skip to main content

guinea_trace/
sink.rs

1//! Where records go.
2
3use 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
19/// Hands every record produced on this thread to `observer`, replacing any
20/// observer already set.
21pub 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
34/// Whether devtools are watching this thread.
35pub fn is_observed() -> bool {
36    OBSERVER.with(|slot| slot.borrow().is_some())
37}
38
39/// Whether devtools are watching any thread.
40pub fn is_observed_anywhere() -> bool {
41    OBSERVED_THREADS.load(Ordering::Relaxed) > 0
42}
43
44/// Whether a point recorded anywhere goes somewhere: to devtools, or to
45/// `tracing` as `guinea::` events.
46pub 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
59/// Whether `target` is one [`emit`] writes points under, so a layer that
60/// turns `tracing` events into points can leave them alone.
61pub 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}