1#![cfg_attr(not(feature = "std"), no_std)]
2#[cfg(not(feature = "std"))]
3extern crate alloc;
4extern crate core;
5
6use bincode::{Decode, Encode};
7use cu29_clock::CuTime;
8use cu29_value::Value;
9use serde::{Deserialize, Serialize};
10use smallvec::SmallVec;
11
12#[cfg(not(feature = "std"))]
13mod imp {
14 pub use core::fmt::Display;
15 pub use core::fmt::Formatter;
16 pub use core::fmt::Result as FmtResult;
17}
18
19#[cfg(feature = "defmt")]
20pub use defmt;
21
22#[cfg(feature = "std")]
23mod imp {
24 pub use core::fmt::Display;
25 pub use cu29_traits::{CuError, CuResult};
26 pub use std::collections::HashMap;
28 pub use std::fmt::Formatter;
29 pub use std::fmt::Result as FmtResult;
30 pub use strfmt::strfmt;
32}
33
34use imp::*;
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
38pub enum CuLogLevel {
39 Debug = 0,
41 Info = 1,
43 Warning = 2,
45 Error = 3,
47 Critical = 4,
49}
50
51impl CuLogLevel {
52 #[inline]
58 pub const fn enabled(self, max_level: CuLogLevel) -> bool {
59 self as u8 >= max_level as u8
60 }
61}
62
63#[allow(dead_code)]
64pub const ANONYMOUS: u32 = 0;
65
66pub const MAX_LOG_PARAMS_ON_STACK: usize = 10;
67
68#[derive(Debug, Serialize, Deserialize, PartialEq)]
70pub struct CuLogEntry {
71 pub time: CuTime,
73
74 pub level: CuLogLevel,
76
77 pub msg_index: u32,
79
80 pub paramname_indexes: SmallVec<[u32; MAX_LOG_PARAMS_ON_STACK]>,
82
83 pub params: SmallVec<[Value; MAX_LOG_PARAMS_ON_STACK]>,
85}
86
87impl Encode for CuLogEntry {
88 fn encode<E: bincode::enc::Encoder>(
89 &self,
90 encoder: &mut E,
91 ) -> Result<(), bincode::error::EncodeError> {
92 self.time.encode(encoder)?;
93 (self.level as u8).encode(encoder)?;
94 self.msg_index.encode(encoder)?;
95
96 (self.paramname_indexes.len() as u64).encode(encoder)?;
97 for &index in &self.paramname_indexes {
98 index.encode(encoder)?;
99 }
100
101 (self.params.len() as u64).encode(encoder)?;
102 for param in &self.params {
103 param.encode(encoder)?;
104 }
105
106 Ok(())
107 }
108}
109
110impl<Context> Decode<Context> for CuLogEntry {
111 fn decode<D: bincode::de::Decoder>(
112 decoder: &mut D,
113 ) -> Result<Self, bincode::error::DecodeError> {
114 let time = CuTime::decode(decoder)?;
115 let level_raw = u8::decode(decoder)?;
116 let level = match level_raw {
117 0 => CuLogLevel::Debug,
118 1 => CuLogLevel::Info,
119 2 => CuLogLevel::Warning,
120 3 => CuLogLevel::Error,
121 4 => CuLogLevel::Critical,
122 _ => CuLogLevel::Debug, };
124 let msg_index = u32::decode(decoder)?;
125
126 let paramname_len = u64::decode(decoder)? as usize;
127 let mut paramname_indexes = SmallVec::with_capacity(paramname_len);
128 for _ in 0..paramname_len {
129 paramname_indexes.push(u32::decode(decoder)?);
130 }
131
132 let params_len = u64::decode(decoder)? as usize;
133 let mut params = SmallVec::with_capacity(params_len);
134 for _ in 0..params_len {
135 params.push(Value::decode(decoder)?);
136 }
137
138 Ok(CuLogEntry {
139 time,
140 level,
141 msg_index,
142 paramname_indexes,
143 params,
144 })
145 }
146}
147
148impl Display for CuLogEntry {
150 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
151 write!(
152 f,
153 "CuLogEntry {{ level: {:?}, msg_index: {}, paramname_indexes: {:?}, params: {:?} }}",
154 self.level, self.msg_index, self.paramname_indexes, self.params
155 )
156 }
157}
158
159impl CuLogEntry {
160 pub fn new(msg_index: u32, level: CuLogLevel) -> Self {
162 CuLogEntry {
163 time: 0.into(), level,
166 msg_index,
167 paramname_indexes: SmallVec::new(),
168 params: SmallVec::new(),
169 }
170 }
171
172 pub fn add_param(&mut self, paramname_index: u32, param: Value) {
175 self.paramname_indexes.push(paramname_index);
176 self.params.push(param);
177 }
178}
179
180#[inline]
183#[cfg(feature = "std")]
184pub fn format_logline(
185 time: CuTime,
186 level: CuLogLevel,
187 format_str: &str,
188 params: &[String],
189 named_params: &HashMap<String, String>,
190) -> CuResult<String> {
191 let mut format_str = format_str.to_string();
192
193 for param in params.iter() {
194 format_str = format_str.replacen("{}", param, 1);
195 }
196
197 if named_params.is_empty() {
198 return Ok(format_str);
199 }
200
201 let logline = strfmt(&format_str, named_params).map_err(|e| {
202 CuError::new_with_cause(
203 format!("Failed to format log line: {format_str:?} with variables [{named_params:?}]")
204 .as_str(),
205 e,
206 )
207 })?;
208 Ok(format!("{time} [{level:?}]: {logline}"))
209}
210
211#[cfg(feature = "std")]
214pub fn rebuild_logline(all_interned_strings: &[String], entry: &CuLogEntry) -> CuResult<String> {
215 let format_string = &all_interned_strings[entry.msg_index as usize];
216 let mut anon_params: Vec<String> = Vec::new();
217 let mut named_params = HashMap::new();
218
219 for (i, param) in entry.params.iter().enumerate() {
220 let param_as_string = format!("{param}");
221 if entry.paramname_indexes[i] == 0 {
222 anon_params.push(param_as_string);
224 } else {
225 let name = all_interned_strings[entry.paramname_indexes[i] as usize].clone();
227 named_params.insert(name, param_as_string);
228 }
229 }
230 format_logline(
231 entry.time,
232 entry.level,
233 format_string,
234 &anon_params,
235 &named_params,
236 )
237}
238
239#[cfg(all(feature = "defmt", not(feature = "std")))]
241#[macro_export]
242macro_rules! __cu29_defmt_debug { ($fmt:literal $(, $arg:expr)* $(,)?) => { defmt::debug!($fmt $(, $arg)*); } }
243#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
244#[macro_export]
245macro_rules! __cu29_defmt_debug {
246 ($($tt:tt)*) => {{}};
247}
248
249#[cfg(all(feature = "defmt", not(feature = "std")))]
250#[macro_export]
251macro_rules! __cu29_defmt_info { ($fmt:literal $(, $arg:expr)* $(,)?) => { defmt::info! ($fmt $(, $arg)*); } }
252#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
253#[macro_export]
254macro_rules! __cu29_defmt_info {
255 ($($tt:tt)*) => {{}};
256}
257
258#[cfg(all(feature = "defmt", not(feature = "std")))]
259#[macro_export]
260macro_rules! __cu29_defmt_warn { ($fmt:literal $(, $arg:expr)* $(,)?) => { defmt::warn! ($fmt $(, $arg)*); } }
261#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
262#[macro_export]
263macro_rules! __cu29_defmt_warn {
264 ($($tt:tt)*) => {{}};
265}
266
267#[cfg(all(feature = "defmt", not(feature = "std")))]
268#[macro_export]
269macro_rules! __cu29_defmt_error { ($fmt:literal $(, $arg:expr)* $(,)?) => { defmt::error!($fmt $(, $arg)*); } }
270#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
271#[macro_export]
272macro_rules! __cu29_defmt_error {
273 ($($tt:tt)*) => {{}};
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[test]
281 fn test_log_level_ordering() {
282 assert!(CuLogLevel::Critical > CuLogLevel::Error);
283 assert!(CuLogLevel::Error > CuLogLevel::Warning);
284 assert!(CuLogLevel::Warning > CuLogLevel::Info);
285 assert!(CuLogLevel::Info > CuLogLevel::Debug);
286
287 assert!(CuLogLevel::Debug < CuLogLevel::Info);
288 assert!(CuLogLevel::Info < CuLogLevel::Warning);
289 assert!(CuLogLevel::Warning < CuLogLevel::Error);
290 assert!(CuLogLevel::Error < CuLogLevel::Critical);
291 }
292
293 #[test]
294 fn test_log_level_enabled() {
295 assert!(CuLogLevel::Debug.enabled(CuLogLevel::Debug));
297 assert!(CuLogLevel::Info.enabled(CuLogLevel::Debug));
298 assert!(CuLogLevel::Warning.enabled(CuLogLevel::Debug));
299 assert!(CuLogLevel::Error.enabled(CuLogLevel::Debug));
300 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Debug));
301
302 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Info));
304 assert!(CuLogLevel::Info.enabled(CuLogLevel::Info));
305 assert!(CuLogLevel::Warning.enabled(CuLogLevel::Info));
306 assert!(CuLogLevel::Error.enabled(CuLogLevel::Info));
307 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Info));
308
309 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Warning));
311 assert!(!CuLogLevel::Info.enabled(CuLogLevel::Warning));
312 assert!(CuLogLevel::Warning.enabled(CuLogLevel::Warning));
313 assert!(CuLogLevel::Error.enabled(CuLogLevel::Warning));
314 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Warning));
315
316 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Error));
318 assert!(!CuLogLevel::Info.enabled(CuLogLevel::Error));
319 assert!(!CuLogLevel::Warning.enabled(CuLogLevel::Error));
320 assert!(CuLogLevel::Error.enabled(CuLogLevel::Error));
321 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Error));
322
323 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Critical));
325 assert!(!CuLogLevel::Info.enabled(CuLogLevel::Critical));
326 assert!(!CuLogLevel::Warning.enabled(CuLogLevel::Critical));
327 assert!(!CuLogLevel::Error.enabled(CuLogLevel::Critical));
328 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Critical));
329 }
330
331 #[test]
332 fn test_cu_log_entry_with_level() {
333 let entry = CuLogEntry::new(42, CuLogLevel::Warning);
334 assert_eq!(entry.level, CuLogLevel::Warning);
335 assert_eq!(entry.msg_index, 42);
336 }
337}