Skip to main content

ove/
log.rs

1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! `log::Log` backend for oveRTOS.
8//!
9//! Implements the [`log`] crate's facade ([`log::Log`]) on top of the
10//! substrate's `ove_console_write` and the binding's [`FmtBuf`] stack
11//! buffer.  Call [`init`] once at the start of `ove_main`, then use
12//! `log::info!`/`log::warn!`/`log::error!`/etc. like in any other Rust
13//! project.
14//!
15//! Output format matches the legacy `log_inf!`/`log_wrn!`/`log_err!`
16//! macros (since-deleted), so a console log stream is byte-compatible
17//! with the C `OVE_LOG_*` family:
18//!
19//! ```text
20//! [I] message goes here
21//! [W] warning goes here
22//! [E] error goes here
23//! ```
24//!
25//! Trace/Debug both render as `[D]` since the C side has no separate
26//! trace prefix.
27//!
28//! # Buffer size and truncation
29//!
30//! Each [`log::Log::log`] call allocates a 256-byte stack buffer (no
31//! heap dep).  Messages longer than ~250 bytes (after prefix + newline)
32//! are silently truncated — the existing [`FmtBuf`] guarantees the
33//! buffer remains null-terminated and well-formed.
34//!
35//! # Thread-safety
36//!
37//! `OveLogger` is `Sync` because every `Log::log` call owns a fresh
38//! stack buffer — there is no shared mutable state.  The underlying
39//! `ove_console_write` is the same primitive the C `OVE_LOG_*` macros
40//! call, so per-line atomicity is governed by the backend (POSIX writes
41//! to stderr atomically up to PIPE_BUF; FreeRTOS/Zephyr serialise via
42//! their console mutex).
43
44use crate::FmtBuf;
45use crate::bindings;
46use crate::error::{Error, Result};
47
48/// `log::Log` implementation routing to `ove_console_write`.
49///
50/// Construct only via [`init`] — there's exactly one logger instance
51/// per program (the standard `log` crate contract).
52struct OveLogger;
53
54impl log::Log for OveLogger {
55    fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool {
56        // Filter is enforced by the `log` crate's global max-level
57        // (`log::set_max_level`); per-record `enabled()` is left
58        // permissive so users can override via `log::set_max_level`
59        // without an extra round-trip through us.
60        true
61    }
62
63    fn log(&self, record: &log::Record<'_>) {
64        use core::fmt::Write;
65
66        // 256 bytes matches the C `OVE_LOG_*` macros' stack buffer and
67        // the legacy `log_inf!`/`log_wrn!`/`log_err!` shapes — keeps
68        // console output byte-compatible across the four languages.
69        let mut buf = [0u8; 256];
70        let mut w = FmtBuf::new(&mut buf);
71
72        let prefix: &str = match record.level() {
73            log::Level::Error => "[E] ",
74            log::Level::Warn => "[W] ",
75            log::Level::Info => "[I] ",
76            log::Level::Debug | log::Level::Trace => "[D] ",
77        };
78
79        // Truncation on overflow is fine — `FmtBuf` keeps the null
80        // terminator and stops accepting new bytes.  No need to inspect
81        // the write! Result.
82        let _ = w.write_str(prefix);
83        let _ = core::fmt::write(&mut w, *record.args());
84        let _ = w.write_str("\n");
85
86        // SAFETY: `as_bytes()` returns the buffer slice excluding the
87        // null terminator; the substrate's `ove_console_write` takes a
88        // (ptr, len) pair and does not retain the pointer.
89        unsafe {
90            bindings::ove_console_write(
91                w.as_bytes().as_ptr() as *const _,
92                w.as_bytes().len() as u32,
93            );
94        }
95    }
96
97    fn flush(&self) {
98        // `ove_console_write` is synchronous; nothing to flush.
99    }
100}
101
102static LOGGER: OveLogger = OveLogger;
103
104/// Install the oveRTOS console logger as the global `log::Log` impl.
105///
106/// Call once near the top of `ove_main`.  After this returns, the
107/// `log::info!`/`warn!`/`error!`/etc. family route through
108/// `ove_console_write` exactly like the legacy custom log macros
109/// (`log_inf`/`log_wrn`/`log_err`) used to before this iteration.
110///
111/// The global max level is set to [`log::LevelFilter::Info`] — apps
112/// that want `debug`/`trace` output should call
113/// `log::set_max_level(log::LevelFilter::Trace)` after this.
114///
115/// # Errors
116/// Returns [`Error::InvalidParam`] if the global logger has already
117/// been set (the `log` crate's `set_logger` is a one-shot).  Repeated
118/// init is a programming error — apps that intentionally re-init
119/// should use [`try_init`] and ignore the resulting error.
120pub fn init() -> Result<()> {
121    log::set_logger(&LOGGER).map_err(|_| Error::InvalidParam)?;
122    log::set_max_level(log::LevelFilter::Info);
123    Ok(())
124}
125
126/// Same as [`init`] but discards the "already-set" error.
127///
128/// Useful in test harnesses where setup may run multiple times.
129pub fn try_init() {
130    let _ = log::set_logger(&LOGGER);
131    log::set_max_level(log::LevelFilter::Info);
132}