Skip to main content

logicaffeine_system/
obs.rs

1//! The observation channel for COMPILED (AOT-Rust) programs — Wave 13.
2//!
3//! Emitted-Rust programs compiled in the OBSERVED profile call these functions
4//! for `Note`/`Warn`/`Record`/`During`, so the compiled binary produces the same
5//! observation event stream the interpreter and VM do (the compile-side buffer
6//! lives in `logicaffeine_compile::observe`; this is its standalone-runtime twin,
7//! reachable from generated code via `use logicaffeine_system::*`). Events land
8//! in a thread-local buffer a host sink drains; when nothing is installed,
9//! emission is a cheap no-op, so the release (elided) profile — which never emits
10//! these calls at all — pays exactly nothing.
11
12use std::cell::RefCell;
13
14/// One observation event captured in a compiled program.
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct ObsEvent {
17    /// `"note"` / `"warn"` / `"record"` for a message, or `"span-enter"` /
18    /// `"span-exit"` for a `During` boundary.
19    pub kind: String,
20    /// The that-clause text (or the span label).
21    pub message: String,
22    /// Captured payloads (`with X and Y`), name → rendered value.
23    pub payloads: Vec<(String, String)>,
24}
25
26thread_local! {
27    static BUFFER: RefCell<Option<Vec<ObsEvent>>> = const { RefCell::new(None) };
28}
29
30/// Begin capturing observation events on this thread (a fresh, empty buffer).
31pub fn install() {
32    BUFFER.with(|b| *b.borrow_mut() = Some(Vec::new()));
33}
34
35/// Take and clear the captured events (empty if nothing was installed).
36pub fn drain() -> Vec<ObsEvent> {
37    BUFFER.with(|b| {
38        b.borrow_mut()
39            .as_mut()
40            .map(std::mem::take)
41            .unwrap_or_default()
42    })
43}
44
45/// Is a sink capturing on this thread? (Emission is a no-op otherwise.)
46pub fn capturing() -> bool {
47    BUFFER.with(|b| b.borrow().is_some())
48}
49
50fn push(kind: &str, message: &str, payloads: Vec<(String, String)>) {
51    BUFFER.with(|b| {
52        if let Some(buf) = b.borrow_mut().as_mut() {
53            buf.push(ObsEvent {
54                kind: kind.to_string(),
55                message: message.to_string(),
56                payloads,
57            });
58        }
59    });
60}
61
62/// Emit a `Note`/`Warn`/`Record` message with its captured payloads.
63pub fn record(kind: &str, message: &str, payloads: &[(&str, String)]) {
64    if !capturing() {
65        return;
66    }
67    let owned = payloads
68        .iter()
69        .map(|(n, v)| (n.to_string(), v.clone()))
70        .collect();
71    push(kind, message, owned);
72}
73
74/// Emit a `During` span entry.
75pub fn span_enter(label: &str) {
76    push("span-enter", label, Vec::new());
77}
78
79/// Emit a `During` span exit (always paired with its `span_enter`).
80pub fn span_exit(label: &str) {
81    push("span-exit", label, Vec::new());
82}