rosace_trace/log.rs
1//! User-facing logging — the ergonomic front-end to the trace bus.
2//!
3//! `info!`/`warn!`/`error!`/`debug!`/`log!` emit a [`RosaceTrace::Log`] onto the
4//! same [`TRACING_BUS`](crate::TRACING_BUS) the framework's own traces use, so
5//! **one interceptor carries framework traces AND app logs** to every sink:
6//! the colored console, the DevTools panel (flight recorder), a future
7//! browser-tools socket, and any third-party subscriber. This is the whole
8//! point of building logging INTO `rosace-trace` rather than pulling an
9//! external logging crate — one bus, one interceptor model, zero extra deps.
10//!
11//! Unlike [`trace!`](crate::trace) (framework events, compiled out of release),
12//! logs flow in **release too**, gated only by a runtime [max level](set_max_level).
13
14use std::sync::atomic::{AtomicU8, Ordering};
15
16use crate::event::{LogLevel, RosaceTrace};
17
18/// The current max verbosity: records with `level as u8 <= MAX_LEVEL` are
19/// emitted. Default = `Info` (2). Raise to `Debug`/`Trace` for more, lower to
20/// `Warn`/`Error` for less. `255` here means "uninitialised → use the default".
21static MAX_LEVEL: AtomicU8 = AtomicU8::new(u8::MAX);
22
23const DEFAULT_MAX_LEVEL: u8 = LogLevel::Info as u8;
24
25/// Set the global max log level. Records more verbose than this are dropped
26/// before any allocation. (Also settable via `ROSACE_LOG=error|warn|info|debug|trace`
27/// through [`init_from_env`].)
28pub fn set_max_level(level: LogLevel) {
29 MAX_LEVEL.store(level as u8, Ordering::Relaxed);
30}
31
32/// The current max log level as a raw `u8` (fast path for the macros).
33#[inline]
34pub fn max_level() -> u8 {
35 let v = MAX_LEVEL.load(Ordering::Relaxed);
36 if v == u8::MAX {
37 DEFAULT_MAX_LEVEL
38 } else {
39 v
40 }
41}
42
43/// True if a record at `level` would be emitted — lets a macro skip formatting
44/// entirely when the level is filtered out (zero cost for disabled logs).
45#[inline]
46pub fn enabled(level: LogLevel) -> bool {
47 (level as u8) <= max_level()
48}
49
50/// Read `ROSACE_LOG` (`error`/`warn`/`info`/`debug`/`trace`, case-insensitive)
51/// and set the max level from it. Called once at app launch; a no-op if unset
52/// or unrecognized (keeps the default). Native-only (`std::env`).
53#[cfg(not(target_arch = "wasm32"))]
54pub fn init_from_env() {
55 if let Ok(v) = std::env::var("ROSACE_LOG") {
56 let level = match v.trim().to_ascii_lowercase().as_str() {
57 "error" => Some(LogLevel::Error),
58 "warn" => Some(LogLevel::Warn),
59 "info" => Some(LogLevel::Info),
60 "debug" => Some(LogLevel::Debug),
61 "trace" => Some(LogLevel::Trace),
62 _ => None,
63 };
64 if let Some(l) = level {
65 set_max_level(l);
66 }
67 }
68}
69
70/// The macro back-end: format + emit a log record onto the bus. Called only
71/// after the level check passes, so the `format!` allocation happens solely for
72/// records that will actually be delivered.
73#[doc(hidden)]
74pub fn __emit(level: LogLevel, target: &'static str, args: std::fmt::Arguments) {
75 crate::TRACING_BUS.emit(RosaceTrace::Log {
76 level,
77 target,
78 message: std::fmt::format(args),
79 timestamp: web_time::Instant::now(),
80 });
81}
82
83/// Log at an explicit [`LogLevel`]. The level-specific macros
84/// (`info!` etc.) forward here.
85///
86/// ```rust
87/// use rosace_trace::{log, event::LogLevel};
88/// log!(LogLevel::Info, "loaded {} items", 3);
89/// ```
90#[macro_export]
91macro_rules! log {
92 ($level:expr, $($arg:tt)+) => {{
93 let __lvl = $level;
94 if $crate::log::enabled(__lvl) {
95 $crate::log::__emit(__lvl, ::core::module_path!(), ::core::format_args!($($arg)+));
96 }
97 }};
98}
99
100/// Log at `Error`.
101#[macro_export]
102macro_rules! error {
103 ($($arg:tt)+) => { $crate::log!($crate::event::LogLevel::Error, $($arg)+) };
104}
105/// Log at `Warn`.
106#[macro_export]
107macro_rules! warn {
108 ($($arg:tt)+) => { $crate::log!($crate::event::LogLevel::Warn, $($arg)+) };
109}
110/// Log at `Info`.
111#[macro_export]
112macro_rules! info {
113 ($($arg:tt)+) => { $crate::log!($crate::event::LogLevel::Info, $($arg)+) };
114}
115/// Log at `Debug`.
116#[macro_export]
117macro_rules! debug {
118 ($($arg:tt)+) => { $crate::log!($crate::event::LogLevel::Debug, $($arg)+) };
119}
120/// Log at `Trace` (note: distinct from [`trace!`](crate::trace), which emits
121/// structured framework events; this logs a message).
122#[macro_export]
123macro_rules! log_trace {
124 ($($arg:tt)+) => { $crate::log!($crate::event::LogLevel::Trace, $($arg)+) };
125}