Skip to main content

leodos_libcfs/
log.rs

1//! Safe, ergonomic logging facilities for cFS.
2//!
3//! ## System log and console
4//!
5//! The [`log!`] and [`printf!`] macros write to the cFE System Log
6//! and the OSAL console, respectively.
7//!
8//! ## Event Services (EVS)
9//!
10//! The [`info!`], [`warn!`], and [`err!`] macros send EVS events
11//! with `println!`-like formatting. The event ID is derived from
12//! the source line number automatically.
13//!
14//! ```rust,ignore
15//! info!("system nominal")?;
16//! warn!("temperature high: {} C", temp)?;
17//! err!("{} failed", subsystem)?;
18//! ```
19
20use crate::error::{CfsError, OsalError};
21use crate::error::Result;
22use crate::ffi;
23use crate::status::check;
24use heapless::CString;
25
26/// The maximum size of a single `OS_printf` message, from OSAL configuration.
27pub const MAX_PRINTF_MSG_SIZE: usize = ffi::OS_BUFFER_SIZE as usize;
28
29/// Writes a message string to the OSAL console (`OS_printf`).
30///
31/// This is a low-level wrapper around the C `OS_printf` function. The `printf!`
32/// macro is generally more convenient to use. This function does not return an
33/// error and is considered a "best-effort" logging mechanism.
34///
35/// # Arguments
36/// * `message`: The string to write. It will be truncated if its byte length
37///   exceeds `MAX_PRINTF_MSG_SIZE`.
38pub fn printf(message: &str) {
39    let mut c_message = CString::<MAX_PRINTF_MSG_SIZE>::new();
40
41    // extend_from_bytes will truncate if the message is too long, which is
42    // acceptable behavior for this best-effort printf wrapper.
43    let _ = c_message.extend_from_bytes(message.as_bytes());
44
45    unsafe {
46        // We call the variadic C function by passing the fully formatted
47        // Rust string as a single argument to a simple "%s" format specifier.
48        ffi::OS_printf("%s\0".as_ptr() as *const libc::c_char, c_message.as_ptr());
49    }
50}
51
52/// Enables output from the `printf!` macro and the underlying `OS_printf` function.
53pub fn printf_enable() {
54    unsafe {
55        ffi::OS_printf_enable();
56    }
57}
58
59/// Disables output from the `printf!` macro and the underlying `OS_printf` function.
60pub fn printf_disable() {
61    unsafe {
62        ffi::OS_printf_disable();
63    }
64}
65
66/// A macro to write a formatted string to the cFE System Log.
67///
68/// This macro provides a `println!`-like interface for `CFE_ES_WriteToSysLog`.
69/// It handles the necessary string formatting and C string conversion.
70///
71/// # Usage
72///
73/// ```rust,ignore
74/// use libcfs::log::syslog;
75/// use libcfs::error::Result;
76///
77/// fn my_function() -> Result<()> {
78///     // Simple literal message
79///     log!("Starting task...")?;
80///
81///     let event_count = 10;
82///     let status = 0;
83///
84///     // Formatted message
85///     log!("Processed {} events with status {}", event_count, status)?;
86///
87///     Ok(())
88/// }
89/// ```
90///
91/// # Returns
92///
93/// This macro evaluates to a `libcfs::error::Result<()>`. It will return an
94/// error if the message cannot be formatted (e.g., too long for the internal
95/// buffer) or if `CFE_ES_WriteToSysLog` returns an error.
96#[macro_export]
97macro_rules! log {
98    // Match a single literal string with no format arguments.
99    ($msg:literal) => {
100        $crate::log::syslog($msg)
101    };
102    // Match a format string plus arguments, like `println!`.
103    ($($arg:tt)*) => {{
104        use core::fmt::Write;
105        // The temporary buffer size is determined by the CFE mission config.
106        let mut buffer: heapless::String<{ $crate::log::SYSLOG_MAX_MSG_SIZE as usize }> = heapless::String::new();
107
108        // Attempt to format the arguments into our heapless string.
109        if write!(&mut buffer, $($arg)*).is_ok() {
110            $crate::log::syslog(&buffer)
111        } else {
112            // This error occurs if the formatted string is too large for the buffer.
113            Err($crate::error::CfsError::ValidationFailure)
114        }
115    }};
116}
117
118/// The maximum size of a single cFE System Log message, from cFE configuration.
119pub const SYSLOG_MAX_MSG_SIZE: usize = ffi::CFE_PLATFORM_ES_SYSTEM_LOG_SIZE as usize;
120
121/// Writes a message to the cFE system log.
122///
123/// This is useful for logging critical events, especially during initialization
124/// before Event Services (EVS) are available, or in error paths where EVS
125/// might fail.
126///
127/// The `log!` macro provides a more convenient, `println!`-like interface
128/// for this functionality.
129pub fn syslog(message: &str) -> Result<()> {
130    #[cfg(feature = "nos3")]
131    {
132        stdout_write(message);
133        stdout_write("\n");
134    }
135
136    let mut c_string = CString::<256>::new();
137    c_string
138        .extend_from_bytes(message.as_bytes())
139        .map_err(|_| CfsError::Osal(OsalError::NameTooLong))?;
140
141    check(unsafe { ffi::CFE_ES_WriteToSysLog(c_string.as_ptr()) })?;
142    Ok(())
143}
144
145#[cfg(feature = "nos3")]
146fn stdout_write(s: &str) {
147    extern "C" {
148        fn write(fd: i32, buf: *const u8, count: usize) -> isize;
149    }
150    unsafe { write(1, s.as_ptr(), s.len()); }
151}
152
153/// A macro to write a formatted string to the OSAL console (`OS_printf`).
154///
155/// This macro provides a `println!`-like interface for `OS_printf`, which is
156/// useful for debugging during development. It is a "fire-and-forget" macro
157/// that does not return a result.
158///
159/// # Usage
160///
161/// ```rust,ignore
162/// use libcfs::log::printf;
163///
164/// fn my_debug_function() {
165///     // Simple literal message
166///     printf!("Debug function entered.");
167///
168///     let value = 42;
169///
170///     // Formatted message
171///     printf!("The current debug value is: {}", value);
172/// }
173/// ```
174#[macro_export]
175macro_rules! printf {
176    // Match a single literal string with no format arguments.
177    ($msg:literal) => {
178        $crate::log::printf($msg)
179    };
180    // Match a format string plus arguments, like `println!`.
181    ($($arg:tt)*) => {{
182        use core::fmt::Write;
183        // The temporary buffer size is determined by the OSAL config.
184        let mut buffer: heapless::String<{ $crate::ffi::OS_BUFFER_SIZE as usize }> = heapless::String::new();
185
186        // Format the arguments. We ignore the result because printf is best-effort.
187        // If it fails, an empty or truncated string will be printed.
188        let _ = write!(&mut buffer, $($arg)*);
189
190        $crate::log::printf(&buffer);
191    }};
192}
193
194/// The maximum formatted EVS message size, from cFE mission config.
195pub const EVS_MAX_MSG_SIZE: usize = ffi::CFE_MISSION_EVS_MAX_MESSAGE_LENGTH as usize;
196
197/// Sends an informational EVS event (`EventType::Info`).
198///
199/// Event ID is derived from the call-site line number.
200///
201/// ```rust,ignore
202/// info!("system nominal")?;
203/// info!("processed {} packets", count)?;
204/// ```
205#[macro_export]
206macro_rules! info {
207    ($msg:literal) => {
208        $crate::cfe::evs::event::send(
209            line!() as u16,
210            $crate::cfe::evs::event::EventType::Info,
211            $msg,
212        )
213    };
214    ($($arg:tt)*) => {{
215        use core::fmt::Write;
216        let mut buf: heapless::String<{ $crate::log::EVS_MAX_MSG_SIZE }> = heapless::String::new();
217        if write!(&mut buf, $($arg)*).is_ok() {
218            $crate::cfe::evs::event::send(
219                line!() as u16,
220                $crate::cfe::evs::event::EventType::Info,
221                &buf,
222            )
223        } else {
224            Err($crate::error::CfsError::ValidationFailure)
225        }
226    }};
227}
228
229/// Sends a warning EVS event (`EventType::Error` — non-catastrophic).
230///
231/// cFS EVS has no dedicated warning level; this maps to
232/// `EventType::Error` which is defined as "not catastrophic."
233///
234/// ```rust,ignore
235/// warn!("temperature high: {} C", temp)?;
236/// ```
237#[macro_export]
238macro_rules! warn {
239    ($msg:literal) => {
240        $crate::cfe::evs::event::send(
241            line!() as u16,
242            $crate::cfe::evs::event::EventType::Error,
243            $msg,
244        )
245    };
246    ($($arg:tt)*) => {{
247        use core::fmt::Write;
248        let mut buf: heapless::String<{ $crate::log::EVS_MAX_MSG_SIZE }> = heapless::String::new();
249        if write!(&mut buf, $($arg)*).is_ok() {
250            $crate::cfe::evs::event::send(
251                line!() as u16,
252                $crate::cfe::evs::event::EventType::Error,
253                &buf,
254            )
255        } else {
256            Err($crate::error::CfsError::ValidationFailure)
257        }
258    }};
259}
260
261/// Sends a critical EVS event (`EventType::Critical`).
262///
263/// ```rust,ignore
264/// err!("{} failed", subsystem)?;
265/// ```
266#[macro_export]
267macro_rules! err {
268    ($msg:literal) => {
269        $crate::cfe::evs::event::send(
270            line!() as u16,
271            $crate::cfe::evs::event::EventType::Critical,
272            $msg,
273        )
274    };
275    ($($arg:tt)*) => {{
276        use core::fmt::Write;
277        let mut buf: heapless::String<{ $crate::log::EVS_MAX_MSG_SIZE }> = heapless::String::new();
278        if write!(&mut buf, $($arg)*).is_ok() {
279            $crate::cfe::evs::event::send(
280                line!() as u16,
281                $crate::cfe::evs::event::EventType::Critical,
282                &buf,
283            )
284        } else {
285            Err($crate::error::CfsError::ValidationFailure)
286        }
287    }};
288}