blob: d3c84c0326222e85a59f46212b34b5eda4fd8242 [file] [log] [blame]
// 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),*);
}};
}