From a46f8786ed61cf9f3837fcdac38a93a1877ee18e Mon Sep 17 00:00:00 2001 From: Timo Date: Sat, 30 Nov 2024 14:02:16 +0100 Subject: [PATCH 1/2] Add feature to enable log backend --- README.md | 13 +++-- rtt-target/Cargo.toml | 6 ++- rtt-target/src/lib.rs | 32 +++++++++--- rtt-target/src/log.rs | 110 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 12 deletions(-) create mode 100644 rtt-target/src/log.rs diff --git a/README.md b/README.md index efe5366..5f47720 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,15 @@ fn main() { } ``` -`rtt-target` also supports initializing multiple RTT channels, and even has a logger implementation -for [`defmt`](https://defmt.ferrous-systems.com/) that can be used in conjunction with arbitrary -channel setups. The `defmt` integration requires setting `features = ["defmt"]`, and the used -channel needs to be manually configured with `set_defmt_channel`. +`rtt-target` also supports initializing multiple RTT channels, and even has a logger implementations +for [`log`](https://docs.rs/log/latest/log/) and [`defmt`](https://defmt.ferrous-systems.com/) that can be used in conjunction with arbitrary +channel setups. + +The `defmt` integration requires setting `features = ["defmt"]`. Furthermore, you have to either invoke `rtt_init_defmt!` or set up your channel(s) manually and invoke `set_defmt_channel` before using `defmt`. + +The `log` integration requires setting `features = ["log"]`. Furthermore, you have to either invoke `rtt_init_log!` or set up your channel(s) manually and invoke `init_logger`/`init_logger_with_level` before using `log`. + +**Note**: For your platform, particularly if you're using a multi-core MCU, external logger implementations might be better suited than the one provided by this crate via the `log`/`defmt` feature. For more information, please check out the [documentation](https://docs.rs/rtt-target). diff --git a/rtt-target/Cargo.toml b/rtt-target/Cargo.toml index b83b4ed..1cb5ae3 100644 --- a/rtt-target/Cargo.toml +++ b/rtt-target/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rtt-target" description = "Target side implementation of the RTT (Real-Time Transfer) I/O protocol" -version = "0.6.0" +version = "0.6.1" edition = "2018" readme = "../README.md" keywords = ["no-std", "embedded", "debugging", "rtt"] @@ -11,6 +11,8 @@ repository = "https://github.com/probe-rs/rtt-target" [features] default = [] +log = ["dep:log", "dep:once_cell"] +log_racy_init = [] # use log::set_logger_racy instead of log::set_logger [dependencies] ufmt-write = "0.1.0" @@ -18,6 +20,8 @@ critical-section = "1.0.0" portable-atomic = { version = "1.6.0", default-features = false } defmt = { version = "0.3.0", optional = true } +log = {version = "0.4.22", optional = true} +once_cell = { version = "1.20.2" , features = ["critical-section"], default-features = false, optional = true} [package.metadata.docs.rs] all-features = true diff --git a/rtt-target/src/lib.rs b/rtt-target/src/lib.rs index 44f69d3..b7ff42d 100644 --- a/rtt-target/src/lib.rs +++ b/rtt-target/src/lib.rs @@ -64,7 +64,8 @@ //! //! The `defmt` crate can be used to format messages in a way that is more efficient and more //! informative than the standard `format!` macros. To use `defmt` with RTT, the `defmt` feature -//! must be enabled, and the channel must be set up with [`set_defmt_channel`]. +//! must be enabled, and the channel must be set up with [`set_defmt_channel`] or you can use the +//! [`rtt_init_defmt`] macro to initialize rtt and defmt at once. //! //! ```toml //! [dependencies] @@ -72,7 +73,24 @@ //! rtt-target = { version = "0.6", features = ["defmt"] } //! ``` //! -//! # Printing +//! # Log integration +//! +//! Rtt-target also supports integration with the `log` crate. The `log` feature must be enabled to +//! configure a logger that forwards log messages to RTT. +//! The logger can be initialized with `rtt_init_log!`. +//! +//! ``` +//! use rtt_target::rtt_init_log; +//! +//! fn main() -> ! { +//! rtt_init_log!(); // Pass a log::LevelFilter as an argument to set the min log level different from Trace +//! loop { +//! log::info!("Hello, world!"); +//! } +//! } +//! ``` +//! +//! # Plain Printing //! //! For no-hassle output the [`rprint`] and [`rprintln`] macros are provided. They use a single down //! channel defined at initialization time, and a critical section for synchronization, and they @@ -116,11 +134,6 @@ //! Please note that because a critical section is used, printing into a blocking channel will cause //! the application to block and freeze when the buffer is full. //! -//! `rtt-target` also supports initializing multiple RTT channels, and even has a logger -//! implementation for [`defmt`](::defmt) that can be used in conjunction with arbitrary channel setups. -//! The `defmt` integration requires setting `features = ["defmt"]`, and the used channel needs -//! to be manually configured with [`set_defmt_channel`]. -//! //! # Reading //! //! The following example shows how to set up the RTT to read simple input sent from the host @@ -155,6 +168,8 @@ use ufmt_write::uWrite; pub mod debug; #[cfg(feature = "defmt")] mod defmt; +#[cfg(feature = "log")] +mod log; /// Public due to access from macro #[doc(hidden)] pub mod rtt; @@ -167,6 +182,9 @@ pub use print::*; #[cfg(feature = "defmt")] pub use defmt::set_defmt_channel; +#[cfg(feature = "log")] +pub use log::*; + /// RTT up (target to host) channel /// /// Supports writing binary data directly, or writing strings via [`core::fmt`] macros such as diff --git a/rtt-target/src/log.rs b/rtt-target/src/log.rs new file mode 100644 index 0000000..2bae164 --- /dev/null +++ b/rtt-target/src/log.rs @@ -0,0 +1,110 @@ +use crate::rprintln; +use once_cell::sync::OnceCell; + +struct Logger { + level_filter: log::LevelFilter, +} + +impl log::Log for Logger { + /// Returns if logger is enabled. + fn enabled(&self, metadata: &log::Metadata) -> bool { + metadata.level() <= self.level_filter + } + + /// Log the record. + fn log(&self, record: &log::Record) { + if self.enabled(record.metadata()) { + rprintln!( + "{:<5} [{}] {}", + record.level(), + record.target(), + record.args() + ); + } + } + + /// Flush buffered records. + fn flush(&self) { + // Nothing to do here + } +} + +static LOGGER: OnceCell = OnceCell::new(); + +/// Init the logger with maximum level (Trace). +/// +/// Note: Normally there is no need to call this manually, use `rtt_init_log!` instead. +pub fn init_logger() { + init_logger_with_level(log::LevelFilter::Trace); +} + +/// Init the logger with a specific level. +/// +/// Note: Normally there is no need to call this manually, use `rtt_init_log!` instead. +pub fn init_logger_with_level(level_filter: log::LevelFilter) { + // Logger was already initialized. + if LOGGER.get().is_some() { + return; + } + let logger = LOGGER.get_or_init(|| Logger { level_filter }); + + // Use racy init if the feature is enabled or the target doesn't support atomic pointers. + #[cfg(any(not(target_has_atomic = "ptr"), feature = "log_racy_init"))] + unsafe { + init_racy(logger); + } + + // Use the default init otherwise. + #[cfg(all(target_has_atomic = "ptr", not(feature = "log_racy_init")))] + init_default(logger); +} + +#[cfg(all(target_has_atomic = "ptr", not(feature = "log_racy_init")))] +fn init_default(logger: &'static Logger) { + log::set_logger(logger).ok(); + log::set_max_level(logger.level_filter); +} + +// # Safety +// +// This function will call the unsafe functions [log::set_logger_racy] and +// [log::set_max_level_racy] if either the feature `log_racy_init` is enabled or the target doesn't +// support atomic pointers. The [once_cell::OnceCell] should ensure that this is only called +// once. +#[cfg(any(not(target_has_atomic = "ptr"), feature = "log_racy_init"))] +unsafe fn init_racy(logger: &'static Logger) { + log::set_logger_racy(logger).ok(); + log::set_max_level_racy(logger.level_filter); +} + +/// Initializes RTT with a single up channel, sets it as the print channel for the printing macros +/// and sets up a log backend with the given log level. +/// +/// The optional arguments specify the level filter (default: `log::LevelFilter::Trace`), +/// the blocking mode (default: `NoBlockSkip`) and size of the buffer in bytes (default: 1024). +/// +/// See [`rtt_init`] for more details. +/// +/// [`rtt_init`]: crate::rtt_init +#[macro_export] +macro_rules! rtt_init_log { + ($level:path, $mode:path, $size:expr) => { + $crate::rtt_init_print!($mode, $size); + $crate::init_logger_with_level($level); + }; + + ($level:path, $mode:path) => { + $crate::rtt_init_log!($level, $mode, 1024); + }; + + ($level:path) => {{ + use $crate::ChannelMode::NoBlockSkip; + $crate::rtt_init_log!($level, NoBlockSkip, 1024); + }}; + + () => {{ + use log::LevelFilter::Trace; + use $crate::ChannelMode::NoBlockSkip; + $crate::rtt_init_log!(Trace, NoBlockSkip, 1024); + }}; +} From 2f71e06fb87a90ab18def8b4cd4a5afe3808513b Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 2 Dec 2024 12:32:16 +0100 Subject: [PATCH 2/2] Add double curly braces to all macro_rules!, to ensure result is always a block. --- rtt-target/src/defmt.rs | 8 ++++---- rtt-target/src/log.rs | 4 ++-- rtt-target/src/print.rs | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/rtt-target/src/defmt.rs b/rtt-target/src/defmt.rs index 3ae3bec..1c6e92c 100644 --- a/rtt-target/src/defmt.rs +++ b/rtt-target/src/defmt.rs @@ -80,7 +80,7 @@ fn do_write(bytes: &[u8]) { /// [`rtt_init`]: crate::rtt_init #[macro_export] macro_rules! rtt_init_defmt { - ($mode:path, $size:expr) => { + ($mode:path, $size:expr) => {{ let channels = $crate::rtt_init! { up: { 0: { @@ -92,14 +92,14 @@ macro_rules! rtt_init_defmt { }; $crate::set_defmt_channel(channels.up.0); - }; + }}; ($mode:path) => { $crate::rtt_init_defmt!($mode, 1024); }; - () => { + () => {{ use $crate::ChannelMode::NoBlockSkip; $crate::rtt_init_defmt!(NoBlockSkip, 1024); - }; + }}; } diff --git a/rtt-target/src/log.rs b/rtt-target/src/log.rs index 2bae164..5e9ccfe 100644 --- a/rtt-target/src/log.rs +++ b/rtt-target/src/log.rs @@ -88,10 +88,10 @@ unsafe fn init_racy(logger: &'static Logger) { /// [`rtt_init`]: crate::rtt_init #[macro_export] macro_rules! rtt_init_log { - ($level:path, $mode:path, $size:expr) => { + ($level:path, $mode:path, $size:expr) => {{ $crate::rtt_init_print!($mode, $size); $crate::init_logger_with_level($level); - }; + }}; ($level:path, $mode:path) => { $crate::rtt_init_log!($level, $mode, 1024); diff --git a/rtt-target/src/print.rs b/rtt-target/src/print.rs index e2acc77..6111859 100644 --- a/rtt-target/src/print.rs +++ b/rtt-target/src/print.rs @@ -160,7 +160,7 @@ macro_rules! rdbg { /// [`rtt_init`]: crate::rtt_init #[macro_export] macro_rules! rtt_init_print { - ($mode:path, $size:expr) => { + ($mode:path, $size:expr) => {{ let channels = $crate::rtt_init! { up: { 0: { @@ -172,14 +172,14 @@ macro_rules! rtt_init_print { }; $crate::set_print_channel(channels.up.0); - }; + }}; ($mode:path) => { $crate::rtt_init_print!($mode, 1024); }; - () => { + () => {{ use $crate::ChannelMode::NoBlockSkip; $crate::rtt_init_print!(NoBlockSkip, 1024); - }; + }}; }