pw_log_backend_printf/lib.rs
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//! `pw_log` backend that calls `libc`'s `printf` to emit log messages. This
16//! module is useful when you have a mixed C/C++ and Rust code base and want a
17//! simple logging system that leverages an existing `printf` implementation.
18//!
19//! *Note*: This uses FFI to call `printf`. This has two implications:
20//! 1. C/C++ macro processing is not done. If a system's `printf` relies on
21//! macros, this backend will likely need to be forked to make work.
22//! 2. FFI calls use `unsafe`. Attempts are made to use `printf` in sound ways
23//! such as bounding the length of strings explicitly but this is still an
24//! off-ramp from Rust's safety guarantees.
25//!
26//! Varargs marshaling for call to printf is handled through a type expansion
27//! of a series of traits. It is documented in the [`varargs`] module.
28//!
29//! TODO: <pwbug.dev/311232605> - Document how to configure facade backends.
30#![deny(missing_docs)]
31
32pub mod varargs;
33
34// Re-export dependences of backend proc macro to be accessed via `$crate::__private`.
35#[doc(hidden)]
36pub mod __private {
37 use core::ffi::{c_int, c_uchar};
38
39 pub use pw_bytes::concat_static_strs;
40 pub use pw_format_core::{PrintfHexFormatter, PrintfUpperHexFormatter};
41 pub use pw_log_backend_printf_macro::{_pw_log_backend, _pw_logf_backend};
42
43 use pw_log_backend_api::LogLevel;
44
45 pub use crate::varargs::{Arguments, VarArgs};
46
47 pub const fn log_level_tag(level: LogLevel) -> &'static str {
48 match level {
49 LogLevel::Debug => "DBG\0",
50 LogLevel::Info => "INF\0",
51 LogLevel::Warn => "WRN\0",
52 LogLevel::Error => "ERR\0",
53 LogLevel::Critical => "CRT\0",
54 LogLevel::Fatal => "FTL\0",
55 }
56 }
57
58 macro_rules! extend_args {
59 ($head:ty; $next:ty $(,$rest:ty)* $(,)?) => {
60 extend_args!(<$head as VarArgs>::OneMore<$next>; $($rest,)*)
61 };
62 ($head:ty;) => {
63 $head
64 };
65 }
66
67 /// The printf uses its own formatter trait because it needs strings to
68 /// resolve to `%.*s` instead of `%s`.
69 ///
70 /// The default [`PrintfHexFormatter`] and [`PrintfUpperHexFormatter`] are
71 /// used since they are not supported by strings.
72 pub trait PrintfFormatter {
73 /// The format specifier for this type.
74 const FORMAT_ARG: &'static str;
75 }
76
77 /// A helper to declare a [`PrintfFormatter`] trait for a given type.
78 macro_rules! declare_formatter {
79 ($ty:ty, $specifier:literal) => {
80 impl PrintfFormatter for $ty {
81 const FORMAT_ARG: &'static str = $specifier;
82 }
83 };
84 }
85
86 declare_formatter!(i32, "d");
87 declare_formatter!(u32, "u");
88 declare_formatter!(&str, ".*s");
89
90 /// A helper to declare an [`Argument<T>`] trait for a given type.
91 ///
92 /// Useful for cases where `Argument::push_args()` appends a single
93 /// argument of type `T`.
94 macro_rules! declare_simple_argument {
95 ($ty:ty) => {
96 impl Arguments<$ty> for $ty {
97 type PushArg<Head: VarArgs> = Head::OneMore<$ty>;
98 fn push_arg<Head: VarArgs>(head: Head, arg: &$ty) -> Self::PushArg<Head> {
99 // Try expanding `CHECK` which should fail if we've exceeded
100 // 12 arguments in our args tuple.
101 let _ = Self::PushArg::<Head>::CHECK;
102 head.append(*arg)
103 }
104 }
105 };
106 }
107
108 declare_simple_argument!(i32);
109 declare_simple_argument!(u32);
110 declare_simple_argument!(char);
111
112 // &str needs a more complex implementation of [`Argument<T>`] since it needs
113 // to append two arguments.
114 impl Arguments<&str> for &str {
115 type PushArg<Head: VarArgs> = extend_args!(Head; c_int, *const c_uchar);
116 fn push_arg<Head: VarArgs>(head: Head, arg: &&str) -> Self::PushArg<Head> {
117 // Try expanding `CHECK` which should fail if we've exceeded 12
118 // arguments in our args tuple.
119 #[allow(clippy::let_unit_value)]
120 let _ = Self::PushArg::<Head>::CHECK;
121 let arg = *arg;
122 head.append(arg.len() as c_int).append(arg.as_ptr().cast())
123 }
124 }
125}
126
127/// Implements the `pw_log` backend api.
128#[macro_export]
129macro_rules! pw_log_backend {
130 ($log_level:expr, $format_string:literal $(, $args:expr)* $(,)?) => {{
131 use $crate::__private as __pw_log_backend_crate;
132 $crate::__private::_pw_log_backend!($log_level, $format_string, $($args),*)
133 }};
134}
135
136/// Implements the `pw_log` backend api.
137#[macro_export]
138macro_rules! pw_logf_backend {
139 ($log_level:expr, $format_string:literal $(, $args:expr)* $(,)?) => {{
140 use $crate::__private as __pw_log_backend_crate;
141 $crate::__private::_pw_logf_backend!($log_level, $format_string, $($args),*)
142 }};
143}
144
145#[cfg(test)]
146mod tests {
147 use super::__private::*;
148 use core::ffi::c_int;
149
150 #[test]
151 fn pushed_args_produce_correct_tuple() {
152 let string = "test";
153 let args = ();
154 let args = <&str as Arguments<&str>>::push_arg(args, &(string as &str));
155 let args = <u32 as Arguments<u32>>::push_arg(args, &2u32);
156 assert_eq!(args, (string.len() as c_int, string.as_ptr().cast(), 2u32));
157 }
158}