pw_log_backend_printf/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// 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.

//! `pw_log` backend that calls `libc`'s `printf` to emit log messages.  This
//! module is useful when you have a mixed C/C++ and Rust code base and want a
//! simple logging system that leverages an existing `printf` implementation.
//!
//! *Note*: This uses FFI to call `printf`.  This has two implications:
//! 1. C/C++ macro processing is not done.  If a system's `printf` relies on
//!    macros, this backend will likely need to be forked to make work.
//! 2. FFI calls use `unsafe`.  Attempts are made to use `printf` in sound ways
//!    such as bounding the length of strings explicitly but this is still an
//!    off-ramp from Rust's safety guarantees.
//!
//! Varargs marshaling for call to printf is handled through a type expansion
//! of a series of traits.  It is documented in the [`varargs`] module.
//!
//! TODO: <pwbug.dev/311232605> - Document how to configure facade backends.
#![deny(missing_docs)]

pub mod varargs;

// Re-export dependences of backend proc macro to be accessed via `$crate::__private`.
#[doc(hidden)]
pub mod __private {
    use core::ffi::{c_int, c_uchar};

    pub use pw_bytes::concat_static_strs;
    pub use pw_format_core::{PrintfHexFormatter, PrintfUpperHexFormatter};
    pub use pw_log_backend_printf_macro::{_pw_log_backend, _pw_logf_backend};

    use pw_log_backend_api::LogLevel;

    pub use crate::varargs::{Arguments, VarArgs};

    pub const fn log_level_tag(level: LogLevel) -> &'static str {
        match level {
            LogLevel::Debug => "DBG\0",
            LogLevel::Info => "INF\0",
            LogLevel::Warn => "WRN\0",
            LogLevel::Error => "ERR\0",
            LogLevel::Critical => "CRT\0",
            LogLevel::Fatal => "FTL\0",
        }
    }

    macro_rules! extend_args {
      ($head:ty; $next:ty $(,$rest:ty)* $(,)?) => {
          extend_args!(<$head as VarArgs>::OneMore<$next>; $($rest,)*)
      };
      ($head:ty;) => {
          $head
      };
    }

    /// The printf uses its own formatter trait because it needs strings to
    /// resolve to `%.*s` instead of `%s`.
    ///
    /// The default [`PrintfHexFormatter`] and [`PrintfUpperHexFormatter`] are
    /// used since they are not supported by strings.
    pub trait PrintfFormatter {
        /// The format specifier for this type.
        const FORMAT_ARG: &'static str;
    }

    /// A helper to declare a [`PrintfFormatter`] trait for a given type.
    macro_rules! declare_formatter {
        ($ty:ty, $specifier:literal) => {
            impl PrintfFormatter for $ty {
                const FORMAT_ARG: &'static str = $specifier;
            }
        };
    }

    declare_formatter!(i32, "d");
    declare_formatter!(u32, "u");
    declare_formatter!(&str, ".*s");

    /// A helper to declare an [`Argument<T>`] trait for a given type.
    ///
    /// Useful for cases where `Argument::push_args()` appends a single
    /// argument of type `T`.
    macro_rules! declare_simple_argument {
        ($ty:ty) => {
            impl Arguments<$ty> for $ty {
                type PushArg<Head: VarArgs> = Head::OneMore<$ty>;
                fn push_arg<Head: VarArgs>(head: Head, arg: &$ty) -> Self::PushArg<Head> {
                    // Try expanding `CHECK` which should fail if we've exceeded
                    // 12 arguments in our args tuple.
                    let _ = Self::PushArg::<Head>::CHECK;
                    head.append(*arg)
                }
            }
        };
    }

    declare_simple_argument!(i32);
    declare_simple_argument!(u32);
    declare_simple_argument!(char);

    // &str needs a more complex implementation of [`Argument<T>`] since it needs
    // to append two arguments.
    impl Arguments<&str> for &str {
        type PushArg<Head: VarArgs> = extend_args!(Head; c_int, *const c_uchar);
        fn push_arg<Head: VarArgs>(head: Head, arg: &&str) -> Self::PushArg<Head> {
            // Try expanding `CHECK` which should fail if we've exceeded 12
            // arguments in our args tuple.
            #[allow(clippy::let_unit_value)]
            let _ = Self::PushArg::<Head>::CHECK;
            let arg = *arg;
            head.append(arg.len() as c_int).append(arg.as_ptr().cast())
        }
    }
}

/// Implements the `pw_log` backend api.
#[macro_export]
macro_rules! pw_log_backend {
    ($log_level:expr, $format_string:literal $(, $args:expr)* $(,)?) => {{
        use $crate::__private as __pw_log_backend_crate;
        $crate::__private::_pw_log_backend!($log_level, $format_string,  $($args),*)
    }};
}

/// Implements the `pw_log` backend api.
#[macro_export]
macro_rules! pw_logf_backend {
    ($log_level:expr, $format_string:literal $(, $args:expr)* $(,)?) => {{
        use $crate::__private as __pw_log_backend_crate;
        $crate::__private::_pw_logf_backend!($log_level, $format_string,  $($args),*)
    }};
}

#[cfg(test)]
mod tests {
    use super::__private::*;
    use core::ffi::c_int;

    #[test]
    fn pushed_args_produce_correct_tuple() {
        let string = "test";
        let args = ();
        let args = <&str as Arguments<&str>>::push_arg(args, &(string as &str));
        let args = <u32 as Arguments<u32>>::push_arg(args, &2u32);
        assert_eq!(args, (string.len() as c_int, string.as_ptr().cast(), 2u32));
    }
}