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            use alloc::string::ToString;
195            let formatted = alloc::format!(concat!($fmt, "\r\n"));
196            if let Ok(c_str) = alloc::ffi::CString::new(formatted) {
197                $crate::log::ffi::printf_on_uart(b"%s\0".as_ptr() as *const core::ffi::c_char, c_str.as_ptr());
198            }
199        }
200    }};
201    ($fmt:expr, $($arg:tt)*) => {{
202        unsafe {
203            use alloc::string::ToString;
204            let formatted = alloc::format!(concat!($fmt, "\r\n"), $($arg)*);
205            if let Ok(c_str) = alloc::ffi::CString::new(formatted) {
206                $crate::log::ffi::printf_on_uart(b"%s\0".as_ptr() as *const core::ffi::c_char, c_str.as_ptr());
207            }
208        }
209    }};
210}
211
212/// Sets the log level threshold.
213///
214/// Only log messages at or above this level will be displayed.
215///
216/// # Parameters
217///
218/// * `level` - Log level (use constants from `log_levels` module)
219///
220/// # Examples
221///
222/// ```ignore
223/// use osal_rs::log::*;
224/// 
225/// // Show only warnings and errors
226/// set_level_log(log_levels::LEVEL_WARNING);
227/// 
228/// // Show all messages
229/// set_level_log(log_levels::LEVEL_DEBUG);
230/// ```
231pub fn set_level_log(level: u8) {
232    unsafe {
233        MASK =
234            (MASK & log_levels::FLAG_STATE_ON) | (level & !log_levels::FLAG_STATE_ON);
235    }
236}
237
238/// Enables or disables all logging.
239///
240/// When disabled, all log macros become no-ops for zero runtime cost.
241///
242/// # Parameters
243///
244/// * `enabled` - `true` to enable logging, `false` to disable
245///
246/// # Examples
247///
248/// ```ignore
249/// use osal_rs::log::set_enable_log;
250/// 
251/// set_enable_log(false);  // Disable all logging
252/// // ... logs will not be printed ...
253/// set_enable_log(true);   // Re-enable logging
254/// ```
255pub fn set_enable_log(enabled: bool) {
256    unsafe {
257        if enabled {
258            MASK |= log_levels::FLAG_STATE_ON;
259        } else {
260            MASK &= !log_levels::FLAG_STATE_ON;
261        }
262    }
263}
264
265/// Checks if logging is currently enabled.
266///
267/// # Returns
268///
269/// `true` if logging is enabled, `false` otherwise
270///
271/// # Examples
272///
273/// ```ignore
274/// use osal_rs::log::get_enable_log;
275/// 
276/// if get_enable_log() {
277///     println!("Logging is active");
278/// }
279/// ```
280pub fn get_enable_log() -> bool {
281    unsafe { (MASK & log_levels::FLAG_STATE_ON) != 0 }
282}
283
284/// Checks if a specific log level is enabled.
285///
286/// # Parameters
287///
288/// * `log_type` - Log level flag to check
289///
290/// # Returns
291///
292/// `true` if the log level is enabled, `false` otherwise
293///
294/// # Examples
295///
296/// ```ignore
297/// use osal_rs::log::*;
298/// 
299/// if is_enabled_log(log_levels::FLAG_DEBUG) {
300///     // Debug logging is active
301/// }
302/// ```
303pub fn is_enabled_log(log_type: u8) -> bool {
304    unsafe { (MASK & log_levels::FLAG_STATE_ON) != 0 && (MASK & log_type) != 0 }
305}
306
307/// Gets the current log level threshold.
308///
309/// # Returns
310///
311/// Current log level mask (without state and color flags)
312///
313/// # Examples
314///
315/// ```ignore
316/// use osal_rs::log::*;
317/// 
318/// let level = get_level_log();
319/// ```
320pub fn get_level_log() -> u8 {
321    unsafe { MASK & !log_levels::FLAG_STATE_ON & !log_levels::FLAG_COLOR_ON }
322}
323
324/// Enables or disables color output.
325///
326/// When enabled, log messages are color-coded by severity:
327/// - DEBUG: Cyan
328/// - INFO: Green  
329/// - WARNING: Yellow
330/// - ERROR: Red
331/// - FATAL: Magenta
332///
333/// # Parameters
334///
335/// * `enabled` - `true` to enable colors, `false` for plain text
336///
337/// # Examples
338///
339/// ```ignore
340/// use osal_rs::log::set_enable_color;
341/// 
342/// set_enable_color(true);   // Enable colored output
343/// set_enable_color(false);  // Disable colors
344/// ```
345pub fn set_enable_color(enabled: bool) {
346    unsafe {
347        if enabled {
348            MASK |= log_levels::FLAG_COLOR_ON;
349        } else {
350            MASK &= !log_levels::FLAG_COLOR_ON;
351        }
352    }
353}
354
355
356
357/// Core logging function that outputs formatted log messages.
358///
359/// This is the low-level function called by all log macros. It handles:
360/// - Thread-safe output using a busy-wait lock
361/// - Color formatting based on log level
362/// - Timestamp prefixing with millisecond precision
363/// - Tag prefixing for message categorization
364///
365/// # Parameters
366///
367/// * `tag` - Category or module name for the log message
368/// * `log_type` - Log level flag (DEBUG, INFO, WARNING, ERROR, FATAL)
369/// * `to_print` - The message to log
370///
371/// # Examples
372///
373/// ```ignore
374/// use osal_rs::log::*;
375/// 
376/// sys_log("APP", log_levels::FLAG_INFO, "Application started");
377/// ```
378///
379/// # Note
380///
381/// Prefer using the log macros (`log_info!`, `log_error!`, etc.) instead of
382/// calling this function directly.
383pub fn sys_log(tag: &str, log_type: u8, to_print: &str) {
384    unsafe {
385        while BUSY != 0 {}
386        BUSY = 1;
387
388        let mut color_reset = COLOR_RESET;
389        let color = if MASK & log_levels::FLAG_COLOR_ON == log_levels::FLAG_COLOR_ON {
390
391            match log_type {
392                log_levels::FLAG_DEBUG => COLOR_CYAN,
393                log_levels::FLAG_INFO => COLOR_GREEN,
394                log_levels::FLAG_WARNING => COLOR_YELLOW,
395                log_levels::FLAG_ERROR => COLOR_RED,
396                log_levels::FLAG_FATAL => COLOR_MAGENTA,
397                _ => COLOR_RESET,
398            }
399        } else {
400            color_reset = "";
401            ""
402        };
403
404
405        let now = System::get_current_time_us();
406
407
408        #[cfg(not(feature = "std"))]
409        {
410            let formatted = format!("{color}({millis}ms)[{tag}] {to_print}{color_reset}{RETURN}", millis=now.as_millis());
411            if let Ok(c_str) = CString::new(formatted) {
412                printf_on_uart(b"%s\0".as_ptr() as *const c_char, c_str.as_ptr());
413            }
414        }
415
416        #[cfg(feature = "std")]
417        {
418            print!("{}[{}] ", color, tag);
419            core::fmt::write(&mut core::fmt::Formatter::new(), args).unwrap();
420            print!("{}", COLOR_RESET);
421            print!("\r\n");
422        }
423
424        BUSY = 0;
425    }
426}
427
428/// Logs a DEBUG level message.
429///
430/// Debug messages are the most verbose and typically used during development.
431/// Color: Cyan (if colors are enabled)
432///
433/// # Parameters
434///
435/// * `app_tag` - Category or module identifier
436/// * `fmt` - Format string
437/// * `arg` - Optional format arguments
438///
439/// # Examples
440///
441/// ```ignore
442/// use osal_rs::log_debug;
443/// 
444/// log_debug!("APP", "Initializing subsystem");
445/// log_debug!("APP", "Counter: {}, Status: {}", counter, status);
446/// ```
447#[macro_export]
448macro_rules! log_debug {
449    ($app_tag:expr, $fmt:expr $(, $($arg:tt)*)?) => {{
450        if $crate::log::is_enabled_log($crate::log::log_levels::FLAG_DEBUG) {
451            let msg = alloc::format!($fmt $(, $($arg)*)?);
452            $crate::log::sys_log($app_tag, $crate::log::log_levels::FLAG_DEBUG, &msg);
453        }
454    }};
455}
456
457/// Logs an INFO level message.
458///
459/// Informational messages about normal application operation.
460/// Color: Green (if colors are enabled)
461///
462/// # Parameters
463///
464/// * `app_tag` - Category or module identifier
465/// * `fmt` - Format string  
466/// * `arg` - Optional format arguments
467///
468/// # Examples
469///
470/// ```ignore
471/// use osal_rs::log_info;
472/// 
473/// log_info!("APP", "System initialized successfully");
474/// log_info!("NET", "Connected to server at {}", ip_addr);
475/// ```
476#[macro_export]
477macro_rules! log_info {
478    ($app_tag:expr, $fmt:expr $(, $($arg:tt)*)?) => {{
479        if $crate::log::is_enabled_log($crate::log::log_levels::FLAG_INFO) {
480            let msg = alloc::format!($fmt $(, $($arg)*)?);
481            $crate::log::sys_log($app_tag, $crate::log::log_levels::FLAG_INFO, &msg);
482        }
483    }};
484}
485
486/// Logs a WARNING level message.
487///
488/// Warning messages indicate potential issues that don't prevent operation.
489/// Color: Yellow (if colors are enabled)
490///
491/// # Parameters
492///
493/// * `app_tag` - Category or module identifier
494/// * `fmt` - Format string
495/// * `arg` - Optional format arguments
496///
497/// # Examples
498///
499/// ```ignore
500/// use osal_rs::log_warning;
501/// 
502/// log_warning!("MEM", "Memory usage above 80%");
503/// log_warning!("SENSOR", "Temperature high: {} C", temp);
504/// ```
505#[macro_export]
506macro_rules! log_warning {
507    ($app_tag:expr, $fmt:expr $(, $($arg:tt)*)?) => {{
508        if $crate::log::is_enabled_log($crate::log::log_levels::FLAG_WARNING) {
509            let msg = alloc::format!($fmt $(, $($arg)*)?);
510            $crate::log::sys_log($app_tag, $crate::log::log_levels::FLAG_WARNING, &msg);
511        }
512    }};
513}
514
515/// Logs an ERROR level message.
516///
517/// Error messages indicate failures that affect functionality.
518/// Color: Red (if colors are enabled)
519///
520/// # Parameters
521///
522/// * `app_tag` - Category or module identifier
523/// * `fmt` - Format string
524/// * `arg` - Optional format arguments
525///
526/// # Examples
527///
528/// ```ignore
529/// use osal_rs::log_error;
530/// 
531/// log_error!("FS", "Failed to open file");
532/// log_error!("NET", "Connection timeout: {}", error);
533/// ```
534#[macro_export]
535macro_rules! log_error {
536    ($app_tag:expr, $fmt:expr $(, $($arg:tt)*)?) => {{
537        if $crate::log::is_enabled_log($crate::log::log_levels::FLAG_ERROR) {
538            let msg = alloc::format!($fmt $(, $($arg)*)?);
539            $crate::log::sys_log($app_tag, $crate::log::log_levels::FLAG_ERROR, &msg);
540        }
541    }};
542}
543
544/// Logs a FATAL level message.
545///
546/// Fatal messages indicate critical errors that prevent continued operation.
547/// Color: Magenta (if colors are enabled)
548///
549/// # Parameters
550///
551/// * `app_tag` - Category or module identifier
552/// * `fmt` - Format string
553/// * `arg` - Optional format arguments
554///
555/// # Examples
556///
557/// ```ignore
558/// use osal_rs::log_fatal;
559/// 
560/// log_fatal!("SYS", "Kernel panic!");
561/// log_fatal!("HW", "Hardware fault detected: {}", fault_code);
562/// ```
563#[macro_export]
564macro_rules! log_fatal {
565    ($app_tag:expr, $fmt:expr $(, $($arg:tt)*)?) => {{
566        if $crate::log::is_enabled_log($crate::log::log_levels::FLAG_FATAL) {
567            let msg = alloc::format!($fmt $(, $($arg)*)?);
568            $crate::log::sys_log($app_tag, $crate::log::log_levels::FLAG_FATAL, &msg);
569        }
570    }};
571}