Skip to main content

osal_rs/
log.rs

1/***************************************************************************
2 *
3 * osal-rs
4 * Copyright (C) 2023/2026 Antonio Salsi <passy.linux@zresa.it>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 *
18 ***************************************************************************/
19
20//! Logging system for embedded environments.
21//!
22//! Provides a flexible logging system with multiple severity levels and color support.
23//! Designed for no-std environments with UART output support.
24//!
25//! # Features
26//!
27//! - Multiple log levels (DEBUG, INFO, WARNING, ERROR, FATAL)
28//! - Color-coded output support (ANSI colors)
29//! - Timestamp logging with millisecond precision
30//! - Thread-safe logging with busy-wait synchronization
31//! - Configurable log level masking
32//! - Zero-cost when logs are disabled
33//!
34//! # Examples
35//!
36//! ## Basic logging
37//!
38//! ```ignore
39//! use osal_rs::{log_info, log_error, log_debug};
40//! 
41//! log_info!("APP", "Application started");
42//! log_debug!("APP", "Counter value: {}", 42);
43//! log_error!("APP", "Failed to initialize: {}", error_msg);
44//! ```
45//!
46//! ## Configuring log levels
47//!
48//! ```ignore
49//! use osal_rs::log::*;
50//! 
51//! // Set log level to WARNING and above
52//! set_level_log(log_levels::LEVEL_WARNING);
53//! 
54//! // Enable/disable logging
55//! set_enable_log(true);
56//! 
57//! // Enable/disable color output
58//! set_enable_color(true);
59//! ```
60//!
61//! ## Using print macros
62//!
63//! ```ignore
64//! use osal_rs::{print, println};
65//! 
66//! print!("Hello");
67//! println!(" World!");
68//! println!("Value: {}", 123);
69//! ```
70
71#[cfg(not(feature = "std"))]
72pub mod ffi {
73    use core::ffi::{c_char, c_int};
74
75    unsafe extern "C" {
76        /// FFI function to print formatted strings to UART.
77        ///
78        /// This is the low-level C function that interfaces with the hardware UART.
79        pub fn printf_on_uart(format: *const c_char, ...) -> c_int;
80
81    }
82}
83
84use core::ffi::c_char;
85
86use alloc::{ffi::CString, format};
87
88use crate::log::ffi::printf_on_uart;
89use crate::os::{System, SystemFn};
90
91
92/// ANSI escape code for red text color
93const COLOR_RED: &str = "\x1b[31m";
94/// ANSI escape code for green text color
95const COLOR_GREEN: &str = "\x1b[32m";
96/// ANSI escape code for yellow text color
97const COLOR_YELLOW: &str = "\x1b[33m";
98/// ANSI escape code for blue text color
99const COLOR_BLUE: &str = "\x1b[34m";
100/// ANSI escape code for magenta text color
101const COLOR_MAGENTA: &str = "\x1b[35m";
102/// ANSI escape code for cyan text color
103const COLOR_CYAN: &str = "\x1b[36m";
104/// ANSI escape code to reset all text attributes
105const COLOR_RESET: &str = "\x1b[0m";
106/// Carriage return + line feed for proper terminal output
107pub const RETURN: &str = "\r\n";
108
109/// Log level flags and level configurations.
110///
111/// This module defines bit flags for different log levels and combined
112/// level masks for filtering log messages.
113pub mod log_levels {
114    /// Flag for DEBUG level messages (most verbose)
115    pub const FLAG_DEBUG: u8 = 1 << 0;
116    /// Flag for INFO level messages
117    pub const FLAG_INFO: u8 = 1 << 1;
118    /// Flag for WARNING level messages
119    pub const FLAG_WARNING: u8 = 1 << 2;
120    /// Flag for ERROR level messages
121    pub const FLAG_ERROR: u8 = 1 << 3;
122    /// Flag for FATAL level messages (most severe)
123    pub const FLAG_FATAL: u8 = 1 << 4;
124    /// Flag to enable color output
125    pub const FLAG_COLOR_ON: u8 = 1 << 6;
126    /// Flag to enable/disable logging entirely
127    pub const FLAG_STATE_ON: u8 = 1 << 7;
128
129    /// DEBUG level: Shows all messages
130    pub const LEVEL_DEBUG: u8 = FLAG_DEBUG | FLAG_INFO | FLAG_WARNING | FLAG_ERROR | FLAG_FATAL;
131    /// INFO level: Shows INFO and above
132    pub const LEVEL_INFO: u8 = FLAG_INFO | FLAG_WARNING | FLAG_ERROR | FLAG_FATAL;
133    /// WARNING level: Shows WARNING and above
134    pub const LEVEL_WARNING: u8 = FLAG_WARNING | FLAG_ERROR | FLAG_FATAL;
135    /// ERROR level: Shows ERROR and FATAL only
136    pub const LEVEL_ERROR: u8 = FLAG_ERROR | FLAG_FATAL;
137
138    /// FATAL level: Shows only FATAL messages
139    pub const LEVEL_FATAL: u8 = FLAG_FATAL;
140}
141
142/// Global log level mask with color and state flags enabled by default
143static mut MASK: u8 = log_levels::LEVEL_DEBUG | log_levels::FLAG_COLOR_ON | log_levels::FLAG_STATE_ON;
144/// Simple busy flag for thread-safe logging (0 = free, non-zero = busy)
145static mut BUSY: u8 = 0;
146
147/// Prints formatted text to UART without a newline.
148///
149/// This macro is only available in no-std mode. In std mode, use the standard `print!` macro.
150///
151/// # Examples
152///
153/// ```ignore
154/// use osal_rs::print;
155/// 
156/// print!("Hello");
157/// print!(" World: {}", 42);
158/// ```
159#[cfg(not(feature = "std"))]
160#[macro_export]
161macro_rules! print {
162    ($($arg:tt)*) => {{
163        unsafe {
164            use alloc::string::ToString;
165            let formatted = alloc::format!($($arg)*);
166            if let Ok(c_str) = alloc::ffi::CString::new(formatted) {
167                $crate::log::ffi::printf_on_uart(b"%s\0".as_ptr() as *const core::ffi::c_char, c_str.as_ptr());
168            }
169        }
170    }};
171}
172
173/// Prints formatted text to UART with a newline (\r\n).
174///
175/// This macro is only available in no-std mode. In std mode, use the standard `println!` macro.
176///
177/// # Examples
178///
179/// ```ignore
180/// use osal_rs::println;
181/// 
182/// println!("Hello World");
183/// println!("Value: {}", 42);
184/// println!();  // Just a newline
185/// ```
186#[cfg(not(feature = "std"))]
187#[macro_export]
188macro_rules! println {
189    () => {
190        $crate::print!("\r\n")
191    };
192    ($fmt:expr) => {{
193        unsafe {
194            let formatted = alloc::format!(concat!($fmt, "\r\n"));
195            if let Ok(c_str) = alloc::ffi::CString::new(formatted) {
196                $crate::log::ffi::printf_on_uart(b"%s\0".as_ptr() as *const core::ffi::c_char, c_str.as_ptr());
197            }
198        }
199    }};
200    ($fmt:expr, $($arg:tt)*) => {{
201        unsafe {
202            let formatted = alloc::format!(concat!($fmt, "\r\n"), $($arg)*);
203            if let Ok(c_str) = alloc::ffi::CString::new(formatted) {
204                $crate::log::ffi::printf_on_uart(b"%s\0".as_ptr() as *const core::ffi::c_char, c_str.as_ptr());
205            }
206        }
207    }};
208}
209
210/// Sets the log level threshold.
211///
212/// Only log messages at or above this level will be displayed.
213///
214/// # Parameters
215///
216/// * `level` - Log level (use constants from `log_levels` module)
217///
218/// # Examples
219///
220/// ```ignore
221/// use osal_rs::log::*;
222/// 
223/// // Show only warnings and errors
224/// set_level_log(log_levels::LEVEL_WARNING);
225/// 
226/// // Show all messages
227/// set_level_log(log_levels::LEVEL_DEBUG);
228/// ```
229pub fn set_level_log(level: u8) {
230    unsafe {
231        MASK =
232            (MASK & log_levels::FLAG_STATE_ON) | (level & !log_levels::FLAG_STATE_ON);
233    }
234}
235
236/// Enables or disables all logging.
237///
238/// When disabled, all log macros become no-ops for zero runtime cost.
239///
240/// # Parameters
241///
242/// * `enabled` - `true` to enable logging, `false` to disable
243///
244/// # Examples
245///
246/// ```ignore
247/// use osal_rs::log::set_enable_log;
248/// 
249/// set_enable_log(false);  // Disable all logging
250/// // ... logs will not be printed ...
251/// set_enable_log(true);   // Re-enable logging
252/// ```
253pub fn set_enable_log(enabled: bool) {
254    unsafe {
255        if enabled {
256            MASK |= log_levels::FLAG_STATE_ON;
257        } else {
258            MASK &= !log_levels::FLAG_STATE_ON;
259        }
260    }
261}
262
263/// Checks if logging is currently enabled.
264///
265/// # Returns
266///
267/// `true` if logging is enabled, `false` otherwise
268///
269/// # Examples
270///
271/// ```ignore
272/// use osal_rs::log::get_enable_log;
273/// 
274/// if get_enable_log() {
275///     println!("Logging is active");
276/// }
277/// ```
278pub fn get_enable_log() -> bool {
279    unsafe { (MASK & log_levels::FLAG_STATE_ON) != 0 }
280}
281
282/// Checks if a specific log level is enabled.
283///
284/// # Parameters
285///
286/// * `log_type` - Log level flag to check
287///
288/// # Returns
289///
290/// `true` if the log level is enabled, `false` otherwise
291///
292/// # Examples
293///
294/// ```ignore
295/// use osal_rs::log::*;
296/// 
297/// if is_enabled_log(log_levels::FLAG_DEBUG) {
298///     // Debug logging is active
299/// }
300/// ```
301pub fn is_enabled_log(log_type: u8) -> bool {
302    unsafe { (MASK & log_levels::FLAG_STATE_ON) != 0 && (MASK & log_type) != 0 }
303}
304
305/// Gets the current log level threshold.
306///
307/// # Returns
308///
309/// Current log level mask (without state and color flags)
310///
311/// # Examples
312///
313/// ```ignore
314/// use osal_rs::log::*;
315/// 
316/// let level = get_level_log();
317/// ```
318pub fn get_level_log() -> u8 {
319    unsafe { MASK & !log_levels::FLAG_STATE_ON & !log_levels::FLAG_COLOR_ON }
320}
321
322/// Enables or disables color output.
323///
324/// When enabled, log messages are color-coded by severity:
325/// - DEBUG: Cyan
326/// - INFO: Green  
327/// - WARNING: Yellow
328/// - ERROR: Red
329/// - FATAL: Magenta
330///
331/// # Parameters
332///
333/// * `enabled` - `true` to enable colors, `false` for plain text
334///
335/// # Examples
336///
337/// ```ignore
338/// use osal_rs::log::set_enable_color;
339/// 
340/// set_enable_color(true);   // Enable colored output
341/// set_enable_color(false);  // Disable colors
342/// ```
343pub fn set_enable_color(enabled: bool) {
344    unsafe {
345        if enabled {
346            MASK |= log_levels::FLAG_COLOR_ON;
347        } else {
348            MASK &= !log_levels::FLAG_COLOR_ON;
349        }
350    }
351}
352
353
354
355/// Core logging function that outputs formatted log messages.
356///
357/// This is the low-level function called by all log macros. It handles:
358/// - Thread-safe output using a busy-wait lock
359/// - Color formatting based on log level
360/// - Timestamp prefixing with millisecond precision
361/// - Tag prefixing for message categorization
362///
363/// # Parameters
364///
365/// * `tag` - Category or module name for the log message
366/// * `log_type` - Log level flag (DEBUG, INFO, WARNING, ERROR, FATAL)
367/// * `to_print` - The message to log
368///
369/// # Examples
370///
371/// ```ignore
372/// use osal_rs::log::*;
373/// 
374/// sys_log("APP", log_levels::FLAG_INFO, "Application started");
375/// ```
376///
377/// # Note
378///
379/// Prefer using the log macros (`log_info!`, `log_error!`, etc.) instead of
380/// calling this function directly.
381pub fn sys_log(tag: &str, log_type: u8, to_print: &str) {
382    unsafe {
383        while BUSY != 0 {}
384        BUSY = 1;
385
386        let mut color_reset = COLOR_RESET;
387        let color = if MASK & log_levels::FLAG_COLOR_ON == log_levels::FLAG_COLOR_ON {
388
389            match log_type {
390                log_levels::FLAG_DEBUG => COLOR_CYAN,
391                log_levels::FLAG_INFO => COLOR_GREEN,
392                log_levels::FLAG_WARNING => COLOR_YELLOW,
393                log_levels::FLAG_ERROR => COLOR_RED,
394                log_levels::FLAG_FATAL => COLOR_MAGENTA,
395                _ => COLOR_RESET,
396            }
397        } else {
398            color_reset = "";
399            ""
400        };
401
402
403        let now = System::get_current_time_us();
404
405
406        #[cfg(not(feature = "std"))]
407        {
408            let formatted = format!("{color}({millis}ms)[{tag}] {to_print}{color_reset}{RETURN}", millis=now.as_millis());
409            if let Ok(c_str) = CString::new(formatted) {
410                printf_on_uart(b"%s\0".as_ptr() as *const c_char, c_str.as_ptr());
411            }
412        }
413
414        #[cfg(feature = "std")]
415        {
416            print!("{}[{}] ", color, tag);
417            core::fmt::write(&mut core::fmt::Formatter::new(), args).unwrap();
418            print!("{}", COLOR_RESET);
419            print!("\r\n");
420        }
421
422        BUSY = 0;
423    }
424}
425
426/// Logs a DEBUG level message.
427///
428/// Debug messages are the most verbose and typically used during development.
429/// Color: Cyan (if colors are enabled)
430///
431/// # Parameters
432///
433/// * `app_tag` - Category or module identifier
434/// * `fmt` - Format string
435/// * `arg` - Optional format arguments
436///
437/// # Examples
438///
439/// ```ignore
440/// use osal_rs::log_debug;
441/// 
442/// log_debug!("APP", "Initializing subsystem");
443/// log_debug!("APP", "Counter: {}, Status: {}", counter, status);
444/// ```
445#[macro_export]
446macro_rules! log_debug {
447    ($app_tag:expr, $fmt:expr $(, $($arg:tt)*)?) => {{
448        if $crate::log::is_enabled_log($crate::log::log_levels::FLAG_DEBUG) {
449            let msg = alloc::format!($fmt $(, $($arg)*)?);
450            $crate::log::sys_log($app_tag, $crate::log::log_levels::FLAG_DEBUG, &msg);
451        }
452    }};
453}
454
455/// Logs an INFO level message.
456///
457/// Informational messages about normal application operation.
458/// Color: Green (if colors are enabled)
459///
460/// # Parameters
461///
462/// * `app_tag` - Category or module identifier
463/// * `fmt` - Format string  
464/// * `arg` - Optional format arguments
465///
466/// # Examples
467///
468/// ```ignore
469/// use osal_rs::log_info;
470/// 
471/// log_info!("APP", "System initialized successfully");
472/// log_info!("NET", "Connected to server at {}", ip_addr);
473/// ```
474#[macro_export]
475macro_rules! log_info {
476    ($app_tag:expr, $fmt:expr $(, $($arg:tt)*)?) => {{
477        if $crate::log::is_enabled_log($crate::log::log_levels::FLAG_INFO) {
478            let msg = alloc::format!($fmt $(, $($arg)*)?);
479            $crate::log::sys_log($app_tag, $crate::log::log_levels::FLAG_INFO, &msg);
480        }
481    }};
482}
483
484/// Logs a WARNING level message.
485///
486/// Warning messages indicate potential issues that don't prevent operation.
487/// Color: Yellow (if colors are enabled)
488///
489/// # Parameters
490///
491/// * `app_tag` - Category or module identifier
492/// * `fmt` - Format string
493/// * `arg` - Optional format arguments
494///
495/// # Examples
496///
497/// ```ignore
498/// use osal_rs::log_warning;
499/// 
500/// log_warning!("MEM", "Memory usage above 80%");
501/// log_warning!("SENSOR", "Temperature high: {} C", temp);
502/// ```
503#[macro_export]
504macro_rules! log_warning {
505    ($app_tag:expr, $fmt:expr $(, $($arg:tt)*)?) => {{
506        if $crate::log::is_enabled_log($crate::log::log_levels::FLAG_WARNING) {
507            let msg = alloc::format!($fmt $(, $($arg)*)?);
508            $crate::log::sys_log($app_tag, $crate::log::log_levels::FLAG_WARNING, &msg);
509        }
510    }};
511}
512
513/// Logs an ERROR level message.
514///
515/// Error messages indicate failures that affect functionality.
516/// Color: Red (if colors are enabled)
517///
518/// # Parameters
519///
520/// * `app_tag` - Category or module identifier
521/// * `fmt` - Format string
522/// * `arg` - Optional format arguments
523///
524/// # Examples
525///
526/// ```ignore
527/// use osal_rs::log_error;
528/// 
529/// log_error!("FS", "Failed to open file");
530/// log_error!("NET", "Connection timeout: {}", error);
531/// ```
532#[macro_export]
533macro_rules! log_error {
534    ($app_tag:expr, $fmt:expr $(, $($arg:tt)*)?) => {{
535        if $crate::log::is_enabled_log($crate::log::log_levels::FLAG_ERROR) {
536            let msg = alloc::format!($fmt $(, $($arg)*)?);
537            $crate::log::sys_log($app_tag, $crate::log::log_levels::FLAG_ERROR, &msg);
538        }
539    }};
540}
541
542/// Logs a FATAL level message.
543///
544/// Fatal messages indicate critical errors that prevent continued operation.
545/// Color: Magenta (if colors are enabled)
546///
547/// # Parameters
548///
549/// * `app_tag` - Category or module identifier
550/// * `fmt` - Format string
551/// * `arg` - Optional format arguments
552///
553/// # Examples
554///
555/// ```ignore
556/// use osal_rs::log_fatal;
557/// 
558/// log_fatal!("SYS", "Kernel panic!");
559/// log_fatal!("HW", "Hardware fault detected: {}", fault_code);
560/// ```
561#[macro_export]
562macro_rules! log_fatal {
563    ($app_tag:expr, $fmt:expr $(, $($arg:tt)*)?) => {{
564        if $crate::log::is_enabled_log($crate::log::log_levels::FLAG_FATAL) {
565            let msg = alloc::format!($fmt $(, $($arg)*)?);
566            $crate::log::sys_log($app_tag, $crate::log::log_levels::FLAG_FATAL, &msg);
567        }
568    }};
569}