Skip to main content

rlx_runtime/
obs.rs

1// RLX — versatile ML compiler + runtime.
2// Copyright (C) 2026 Eugene Hauptmann, Nataliya Kosmyna.
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5//! Structured observability / audit spine (`tracing` feature, default-off).
6//!
7//! RLX's diagnostics are ~2600 ad-hoc `eprintln!`/`println!` sites: unstructured
8//! and impossible to feed into an audit trail. This module is the typed, opt-in
9//! alternative — a thin facade over the [`tracing`] crate that emits STRUCTURED
10//! events with a stable name plus a metadata detail. It is the foundation for
11//! the audit-logging a HIPAA/FDA validated pipeline needs (per-inference
12//! records: model + weight hash, engine version, device, shapes, timing).
13//!
14//! ## PHI-safety invariant (load-bearing)
15//! Events carry METADATA ONLY — ids, content hashes, shapes, dtypes, op kinds,
16//! counts, timings. NEVER pass tensor contents, input values, decoded text, or
17//! raw file paths that could embed PHI. Reference data by a content hash, never
18//! the data itself. A leaked value in a log is a reportable breach, so the facade
19//! deliberately takes a formatted *detail* the caller controls rather than
20//! slurping arbitrary state.
21//!
22//! ## Zero-overhead default
23//! Without the `tracing` feature every entry point compiles to a no-op (the
24//! optimizer removes the call), exactly like [`crate::kernel_trace`]. Turn on
25//! `--features tracing` to get structured logs; a downstream product installs
26//! its own [`tracing`] subscriber to route events to an append-only audit sink.
27//!
28//! ```ignore
29//! rlx_runtime::obs::event("inference", format_args!(
30//!     "model={model_id} weight_sha={weight_sha} engine={engine} device={dev}"
31//! ));
32//! ```
33//!
34//! This is a *foundation*: one representative call site (`maybe_log_fusion`) is
35//! wired through it. Migrating the remaining diagnostics off `eprintln!` is
36//! tracked follow-up — each converted site becomes audit-visible for free.
37
38/// Install a default `tracing` subscriber (idempotent). No-op without the
39/// `tracing` feature. A downstream product typically installs its OWN
40/// subscriber (to route to an audit sink) and never calls this; it exists so
41/// `--features tracing` is usable standalone (tests, local debugging).
42#[cfg(feature = "tracing")]
43pub fn init() {
44    use std::sync::OnceLock;
45    static INIT: OnceLock<()> = OnceLock::new();
46    INIT.get_or_init(|| {
47        // Best-effort: if the host app already installed a subscriber, keep it.
48        let _ = tracing_subscriber::fmt().with_target(true).try_init();
49    });
50}
51
52/// No-op without the `tracing` feature.
53#[cfg(not(feature = "tracing"))]
54pub fn init() {}
55
56/// Emit a structured observability/audit event. `kind` is a stable event name
57/// (e.g. `"compile.fusion"`, `"inference"`); `detail` is a caller-formatted,
58/// METADATA-ONLY payload (see the PHI-safety invariant). No-op without the
59/// `tracing` feature — the optimizer removes the call.
60#[cfg(feature = "tracing")]
61#[inline]
62pub fn event(kind: &str, detail: std::fmt::Arguments<'_>) {
63    tracing::info!(target: "rlx", event = %kind, detail = %detail);
64}
65
66/// No-op without the `tracing` feature.
67#[cfg(not(feature = "tracing"))]
68#[inline]
69pub fn event(_kind: &str, _detail: std::fmt::Arguments<'_>) {}
70
71/// Structured event with a caller-controlled metadata detail. Expands to a
72/// no-op without the `tracing` feature (the optimizer removes it), mirroring
73/// [`crate::ktrace`]. Keep the detail metadata-only — never tensor values.
74#[macro_export]
75macro_rules! obs_event {
76    ($kind:expr, $($arg:tt)+) => {{
77        $crate::obs::event($kind, format_args!($($arg)+));
78    }};
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    /// The facade must be safe to call in BOTH build configs: a no-op without
86    /// the feature, a real `tracing` event with it. (With the feature on and no
87    /// subscriber installed, `tracing` events are simply dropped — still safe.)
88    #[test]
89    fn event_is_callable_in_any_config() {
90        init();
91        event("test.event", format_args!("k={} shape={}", 1, "[2,3]"));
92        obs_event!(
93            "test.event2",
94            "engine={} weight_sha={}",
95            "0.2.14",
96            "deadbeef"
97        );
98    }
99}