Skip to main content

epics_base_rs/runtime/
log.rs

1//! Runtime logging — `errlog` severity surface plus the `rt_*` macros.
2//!
3//! C parity: `modules/libcom/src/error/errlog.{c,h}`.
4//!
5//! The four `rt_*` macros route through the `tracing` facade (the
6//! crate's de-facto logging path) instead of bare `eprintln!`, so an
7//! application's `tracing` subscriber controls level filtering,
8//! formatting, and sinks uniformly.
9//!
10//! The `errlog`-severity API mirrors `errlogSevEnum`,
11//! `errlogSevEnumString`, `errlogSetSevToLog`/`errlogGetSevToLog`, and
12//! `errlogSevPrintf` — a record's error messages can be suppressed
13//! below a configurable severity threshold, exactly as a C IOC does.
14
15use std::sync::atomic::{AtomicU8, Ordering};
16
17/// Error-message severity — C `errlogSevEnum` (`errlog.h:49-53`).
18///
19/// Ordered `Info < Minor < Major < Fatal`; the discriminants match the
20/// C enum values so they can be compared as the C code does.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
22#[repr(u8)]
23pub enum ErrlogSevEnum {
24    /// `errlogInfo` = 0.
25    Info = 0,
26    /// `errlogMinor` = 1.
27    Minor = 1,
28    /// `errlogMajor` = 2.
29    Major = 2,
30    /// `errlogFatal` = 3.
31    Fatal = 3,
32}
33
34impl ErrlogSevEnum {
35    /// String form — C `errlogSevEnumString` (`errlog.h:60-65`).
36    pub fn as_str(self) -> &'static str {
37        match self {
38            ErrlogSevEnum::Info => "info",
39            ErrlogSevEnum::Minor => "minor",
40            ErrlogSevEnum::Major => "major",
41            ErrlogSevEnum::Fatal => "fatal",
42        }
43    }
44
45    fn from_u8(v: u8) -> ErrlogSevEnum {
46        match v {
47            0 => ErrlogSevEnum::Info,
48            1 => ErrlogSevEnum::Minor,
49            2 => ErrlogSevEnum::Major,
50            _ => ErrlogSevEnum::Fatal,
51        }
52    }
53}
54
55/// String representation of an errlog severity.
56///
57/// C parity: `errlogGetSevEnumString` (`errlog.c:391-397`) — an
58/// out-of-range value yields `"unknown"`; the typed Rust enum cannot be
59/// out of range, so this always maps to a real name.
60pub fn errlog_sev_enum_string(severity: ErrlogSevEnum) -> &'static str {
61    severity.as_str()
62}
63
64/// Severity threshold below which `errlog_sev_printf` messages are
65/// suppressed from being logged. C parity: `pvt.sevToLog`, default
66/// `errlogMinor` (`errlog.c` static init).
67static SEV_TO_LOG: AtomicU8 = AtomicU8::new(ErrlogSevEnum::Minor as u8);
68
69/// Set the severity-to-log threshold — C `errlogSetSevToLog`
70/// (`errlog.c:399-405`). Messages with a severity below this value are
71/// suppressed.
72pub fn errlog_set_sev_to_log(severity: ErrlogSevEnum) {
73    SEV_TO_LOG.store(severity as u8, Ordering::Relaxed);
74}
75
76/// Get the current severity-to-log threshold — C `errlogGetSevToLog`
77/// (`errlog.c:407-415`).
78pub fn errlog_get_sev_to_log() -> ErrlogSevEnum {
79    ErrlogSevEnum::from_u8(SEV_TO_LOG.load(Ordering::Relaxed))
80}
81
82/// Emit a pre-formatted error message at the given severity, suppressed
83/// when `severity` is below the [`errlog_get_sev_to_log`] threshold.
84///
85/// C parity: `errlogSevVprintf`/`errlogSevPrintf` (`errlog.c:366-389`)
86/// — the C code prefixes `"sevr=%s "` and routes to the message queue.
87/// Here the prefix is preserved and the message is routed through
88/// `tracing` at a level mapped from the severity. Returns `true` when
89/// the message was emitted, `false` when suppressed by the threshold.
90pub fn errlog_sev_printf(severity: ErrlogSevEnum, message: &str) -> bool {
91    if severity < errlog_get_sev_to_log() {
92        return false;
93    }
94    let line = format!("sevr={} {}", severity.as_str(), message);
95    match severity {
96        ErrlogSevEnum::Info => {
97            tracing::info!(target: "epics_base_rs::errlog", "{line}")
98        }
99        ErrlogSevEnum::Minor => {
100            tracing::warn!(target: "epics_base_rs::errlog", "{line}")
101        }
102        ErrlogSevEnum::Major | ErrlogSevEnum::Fatal => {
103            tracing::error!(target: "epics_base_rs::errlog", "{line}")
104        }
105    }
106    true
107}
108
109/// Debug-level runtime log line. Routes through the `tracing` facade.
110#[macro_export]
111macro_rules! rt_debug {
112    ($($arg:tt)*) => {
113        ::tracing::debug!(target: "epics_base_rs::runtime", "{}", format!($($arg)*));
114    };
115}
116
117/// Info-level runtime log line. Routes through the `tracing` facade.
118#[macro_export]
119macro_rules! rt_info {
120    ($($arg:tt)*) => {
121        ::tracing::info!(target: "epics_base_rs::runtime", "{}", format!($($arg)*));
122    };
123}
124
125/// Warn-level runtime log line. Routes through the `tracing` facade.
126#[macro_export]
127macro_rules! rt_warn {
128    ($($arg:tt)*) => {
129        ::tracing::warn!(target: "epics_base_rs::runtime", "{}", format!($($arg)*));
130    };
131}
132
133/// Error-level runtime log line. Routes through the `tracing` facade.
134#[macro_export]
135macro_rules! rt_error {
136    ($($arg:tt)*) => {
137        ::tracing::error!(target: "epics_base_rs::runtime", "{}", format!($($arg)*));
138    };
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    use serial_test::serial;
145
146    #[test]
147    fn test_log_macros_compile() {
148        rt_debug!("debug message {}", 42);
149        rt_info!("info message");
150        rt_warn!("warn: {}", "something");
151        rt_error!("error: {} {}", "bad", "thing");
152    }
153
154    #[test]
155    fn sev_enum_strings_match_c() {
156        // C `errlogSevEnumString` (errlog.h:60-65).
157        assert_eq!(errlog_sev_enum_string(ErrlogSevEnum::Info), "info");
158        assert_eq!(errlog_sev_enum_string(ErrlogSevEnum::Minor), "minor");
159        assert_eq!(errlog_sev_enum_string(ErrlogSevEnum::Major), "major");
160        assert_eq!(errlog_sev_enum_string(ErrlogSevEnum::Fatal), "fatal");
161    }
162
163    #[test]
164    fn sev_enum_ordering() {
165        assert!(ErrlogSevEnum::Info < ErrlogSevEnum::Minor);
166        assert!(ErrlogSevEnum::Minor < ErrlogSevEnum::Major);
167        assert!(ErrlogSevEnum::Major < ErrlogSevEnum::Fatal);
168    }
169
170    #[test]
171    #[serial(errlog_sev)]
172    fn sev_to_log_threshold_roundtrips() {
173        errlog_set_sev_to_log(ErrlogSevEnum::Major);
174        assert_eq!(errlog_get_sev_to_log(), ErrlogSevEnum::Major);
175        // Restore the C default.
176        errlog_set_sev_to_log(ErrlogSevEnum::Minor);
177        assert_eq!(errlog_get_sev_to_log(), ErrlogSevEnum::Minor);
178    }
179
180    #[test]
181    #[serial(errlog_sev)]
182    fn sev_printf_suppresses_below_threshold() {
183        errlog_set_sev_to_log(ErrlogSevEnum::Major);
184        // Below threshold -> suppressed.
185        assert!(!errlog_sev_printf(ErrlogSevEnum::Info, "quiet"));
186        assert!(!errlog_sev_printf(ErrlogSevEnum::Minor, "quiet"));
187        // At or above threshold -> emitted.
188        assert!(errlog_sev_printf(ErrlogSevEnum::Major, "loud"));
189        assert!(errlog_sev_printf(ErrlogSevEnum::Fatal, "loud"));
190        errlog_set_sev_to_log(ErrlogSevEnum::Minor);
191    }
192}