// Copyright 2023 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. //! An example `pw_log` backend that uses `pw_tokenizer`. //! //! Log output is base64 encoded and printed using ARM semihosting. #![no_std] #[doc(hidden)] pub mod __private { use cortex_m_semihosting::hprintln; use pw_log_backend_api::LogLevel; use pw_status::Result; use pw_stream::{Cursor, Write}; use pw_tokenizer::MessageWriter; // Re-export for use by the `pw_logf_backend!` macro. pub use pw_tokenizer::{tokenize_core_fmt_to_writer, tokenize_printf_to_writer}; const ENCODE_BUFFER_SIZE: usize = 32; // A simple implementation of [`pw_tokenizer::MessageWriter`] that writes // data to a buffer. On message finalization, it base64 encodes the data // and prints it using `hprintln!`. pub struct LogMessageWriter { cursor: Cursor<[u8; ENCODE_BUFFER_SIZE]>, } impl MessageWriter for LogMessageWriter { fn new() -> Self { Self { cursor: Cursor::new([0u8; ENCODE_BUFFER_SIZE]), } } fn write(&mut self, data: &[u8]) -> Result<()> { self.cursor.write_all(data) } fn remaining(&self) -> usize { self.cursor.remaining() } fn finalize(self) -> Result<()> { let write_len = self.cursor.position(); let data = self.cursor.into_inner(); // Pigweed's detokenization tools recognize base64 encoded data // prefixed with a `$` as tokenized data interspersed with plain text. let mut encode_buffer = [0u8; pw_base64::encoded_size(ENCODE_BUFFER_SIZE)]; if let Ok(s) = pw_base64::encode_str(&data[..write_len], &mut encode_buffer) { hprintln!("${}", s); } Ok(()) } } pub const fn log_level_tag(level: LogLevel) -> &'static str { match level { LogLevel::Debug => "DBG", LogLevel::Info => "INF", LogLevel::Warn => "WRN", LogLevel::Error => "ERR", LogLevel::Critical => "CRT", LogLevel::Fatal => "FTL", } } } // Implement the `pw_log` backend API. // // Since we're logging to a shared/ambient resource we can use // tokenize_*_to_writer!` instead of `tokenize_*_to_buffer!` and avoid the // overhead of initializing any intermediate buffers or objects. // // Uses `pw_format` special `PW_FMT_CONCAT` operator to prepend a place to // print the log level. #[macro_export] macro_rules! pw_log_backend { ($log_level:expr, $format_string:literal $(, $args:expr)* $(,)?) => {{ let _ = $crate::__private::tokenize_core_fmt_to_writer!( $crate::__private::LogMessageWriter, "[{}] " PW_FMT_CONCAT $format_string, $crate::__private::log_level_tag($log_level) as &str, $($args),*); }}; } #[macro_export] macro_rules! pw_logf_backend { ($log_level:expr, $format_string:literal $(, $args:expr)* $(,)?) => {{ let _ = $crate::__private::tokenize_printf_to_writer!( $crate::__private::LogMessageWriter, "[%s] " PW_FMT_CONCAT $format_string, $crate::__private::log_level_tag($log_level), $($args),*); }}; }