1 // Copyright 2023 The Pigweed Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 // use this file except in compliance with the License. You may obtain a copy of 5 // the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations under 13 // the License. 14 15 //! An example `pw_log` backend that uses `pw_tokenizer`. 16 //! 17 //! Log output is base64 encoded and printed using ARM semihosting. 18 #![no_std] 19 20 #[doc(hidden)] 21 pub mod __private { 22 use cortex_m_semihosting::hprintln; 23 use pw_log_backend_api::LogLevel; 24 use pw_status::Result; 25 use pw_stream::{Cursor, Write}; 26 use pw_tokenizer::MessageWriter; 27 28 // Re-export for use by the `pw_logf_backend!` macro. 29 pub use pw_tokenizer::{tokenize_core_fmt_to_writer, tokenize_printf_to_writer}; 30 31 const ENCODE_BUFFER_SIZE: usize = 32; 32 33 // A simple implementation of [`pw_tokenizer::MessageWriter`] that writes 34 // data to a buffer. On message finalization, it base64 encodes the data 35 // and prints it using `hprintln!`. 36 pub struct LogMessageWriter { 37 cursor: Cursor<[u8; ENCODE_BUFFER_SIZE]>, 38 } 39 40 impl MessageWriter for LogMessageWriter { new() -> Self41 fn new() -> Self { 42 Self { 43 cursor: Cursor::new([0u8; ENCODE_BUFFER_SIZE]), 44 } 45 } 46 write(&mut self, data: &[u8]) -> Result<()>47 fn write(&mut self, data: &[u8]) -> Result<()> { 48 self.cursor.write_all(data) 49 } 50 remaining(&self) -> usize51 fn remaining(&self) -> usize { 52 self.cursor.remaining() 53 } 54 finalize(self) -> Result<()>55 fn finalize(self) -> Result<()> { 56 let write_len = self.cursor.position(); 57 let data = self.cursor.into_inner(); 58 59 // Pigweed's detokenization tools recognize base64 encoded data 60 // prefixed with a `$` as tokenized data interspersed with plain text. 61 let mut encode_buffer = [0u8; pw_base64::encoded_size(ENCODE_BUFFER_SIZE)]; 62 if let Ok(s) = pw_base64::encode_str(&data[..write_len], &mut encode_buffer) { 63 hprintln!("${}", s); 64 } 65 66 Ok(()) 67 } 68 } 69 log_level_tag(level: LogLevel) -> &'static str70 pub const fn log_level_tag(level: LogLevel) -> &'static str { 71 match level { 72 LogLevel::Debug => "DBG", 73 LogLevel::Info => "INF", 74 LogLevel::Warn => "WRN", 75 LogLevel::Error => "ERR", 76 LogLevel::Critical => "CRT", 77 LogLevel::Fatal => "FTL", 78 } 79 } 80 } 81 82 // Implement the `pw_log` backend API. 83 // 84 // Since we're logging to a shared/ambient resource we can use 85 // tokenize_*_to_writer!` instead of `tokenize_*_to_buffer!` and avoid the 86 // overhead of initializing any intermediate buffers or objects. 87 // 88 // Uses `pw_format` special `PW_FMT_CONCAT` operator to prepend a place to 89 // print the log level. 90 #[macro_export] 91 macro_rules! pw_log_backend { 92 ($log_level:expr, $format_string:literal $(, $args:expr)* $(,)?) => {{ 93 let _ = $crate::__private::tokenize_core_fmt_to_writer!( 94 $crate::__private::LogMessageWriter, 95 "[{}] " PW_FMT_CONCAT $format_string, 96 $crate::__private::log_level_tag($log_level) as &str, 97 $($args),*); 98 }}; 99 } 100 101 #[macro_export] 102 macro_rules! pw_logf_backend { 103 ($log_level:expr, $format_string:literal $(, $args:expr)* $(,)?) => {{ 104 let _ = $crate::__private::tokenize_printf_to_writer!( 105 $crate::__private::LogMessageWriter, 106 "[%s] " PW_FMT_CONCAT $format_string, 107 $crate::__private::log_level_tag($log_level), 108 $($args),*); 109 }}; 110 } 111