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")]
20extern crate defmt;
21
22#[cfg(feature = "std")]
23mod imp {
24 pub use core::fmt::Display;
25 pub use cu29_traits::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 if format_str.contains("{}") {
194 let mut formatted = format_str.to_string();
195 for param in params.iter() {
196 if !formatted.contains("{}") {
197 break;
198 }
199 formatted = formatted.replacen("{}", param, 1);
200 }
201 if formatted.contains("{}") && !named_params.is_empty() {
202 let mut named = named_params.iter().collect::<Vec<_>>();
203 named.sort_by(|a, b| a.0.cmp(b.0));
204 for (_, value) in named {
205 if !formatted.contains("{}") {
206 break;
207 }
208 formatted = formatted.replacen("{}", value, 1);
209 }
210 }
211 return Ok(format!("{time} [{level:?}]: {formatted}"));
212 }
213
214 let logline = strfmt(format_str, named_params).map_err(|e| {
216 cu29_traits::CuError::new_with_cause(
217 format!("Failed to format log line: {format_str:?} with variables [{named_params:?}]")
218 .as_str(),
219 e,
220 )
221 })?;
222 Ok(format!("{time} [{level:?}]: {logline}"))
223}
224
225#[cfg(feature = "std")]
228pub fn rebuild_logline(all_interned_strings: &[String], entry: &CuLogEntry) -> CuResult<String> {
229 let format_string = &all_interned_strings[entry.msg_index as usize];
230 let mut anon_params: Vec<String> = Vec::new();
231 let mut named_params = HashMap::new();
232
233 for (i, param) in entry.params.iter().enumerate() {
234 let param_as_string = format!("{param}");
235 if entry.paramname_indexes[i] == 0 {
236 anon_params.push(param_as_string);
238 } else {
239 let name = all_interned_strings[entry.paramname_indexes[i] as usize].clone();
241 named_params.insert(name, param_as_string);
242 }
243 }
244 format_logline(
245 entry.time,
246 entry.level,
247 format_string,
248 &anon_params,
249 &named_params,
250 )
251}
252
253#[cfg(all(feature = "defmt", not(feature = "std")))]
255#[macro_export]
256macro_rules! __cu29_defmt_debug {
257 ($fmt:literal $(, $arg:expr)* $(,)?) => {
258 ::defmt::debug!($fmt $(, $arg)*);
259 }
260}
261#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
262#[macro_export]
263macro_rules! __cu29_defmt_debug {
264 ($($tt:tt)*) => {{}};
265}
266
267#[cfg(all(feature = "defmt", not(feature = "std")))]
268#[macro_export]
269macro_rules! __cu29_defmt_info {
270 ($fmt:literal $(, $arg:expr)* $(,)?) => {
271 ::defmt::info!($fmt $(, $arg)*);
272 }
273}
274#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
275#[macro_export]
276macro_rules! __cu29_defmt_info {
277 ($($tt:tt)*) => {{}};
278}
279
280#[cfg(all(feature = "defmt", not(feature = "std")))]
281#[macro_export]
282macro_rules! __cu29_defmt_warn {
283 ($fmt:literal $(, $arg:expr)* $(,)?) => {
284 ::defmt::warn!($fmt $(, $arg)*);
285 }
286}
287#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
288#[macro_export]
289macro_rules! __cu29_defmt_warn {
290 ($($tt:tt)*) => {{}};
291}
292
293#[cfg(all(feature = "defmt", not(feature = "std")))]
294#[macro_export]
295macro_rules! __cu29_defmt_error {
296 ($fmt:literal $(, $arg:expr)* $(,)?) => {
297 ::defmt::error!($fmt $(, $arg)*);
298 }
299}
300#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
301#[macro_export]
302macro_rules! __cu29_defmt_error {
303 ($($tt:tt)*) => {{}};
304}
305
306#[macro_export]
307macro_rules! defmt_debug {
308 ($($tt:tt)*) => { $crate::__cu29_defmt_debug!($($tt)*) };
309}
310
311#[macro_export]
312macro_rules! defmt_info {
313 ($($tt:tt)*) => { $crate::__cu29_defmt_info!($($tt)*) };
314}
315
316#[macro_export]
317macro_rules! defmt_warn {
318 ($($tt:tt)*) => { $crate::__cu29_defmt_warn!($($tt)*) };
319}
320
321#[macro_export]
322macro_rules! defmt_error {
323 ($($tt:tt)*) => { $crate::__cu29_defmt_error!($($tt)*) };
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 #[test]
331 fn test_log_level_ordering() {
332 assert!(CuLogLevel::Critical > CuLogLevel::Error);
333 assert!(CuLogLevel::Error > CuLogLevel::Warning);
334 assert!(CuLogLevel::Warning > CuLogLevel::Info);
335 assert!(CuLogLevel::Info > CuLogLevel::Debug);
336
337 assert!(CuLogLevel::Debug < CuLogLevel::Info);
338 assert!(CuLogLevel::Info < CuLogLevel::Warning);
339 assert!(CuLogLevel::Warning < CuLogLevel::Error);
340 assert!(CuLogLevel::Error < CuLogLevel::Critical);
341 }
342
343 #[test]
344 fn test_log_level_enabled() {
345 assert!(CuLogLevel::Debug.enabled(CuLogLevel::Debug));
347 assert!(CuLogLevel::Info.enabled(CuLogLevel::Debug));
348 assert!(CuLogLevel::Warning.enabled(CuLogLevel::Debug));
349 assert!(CuLogLevel::Error.enabled(CuLogLevel::Debug));
350 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Debug));
351
352 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Info));
354 assert!(CuLogLevel::Info.enabled(CuLogLevel::Info));
355 assert!(CuLogLevel::Warning.enabled(CuLogLevel::Info));
356 assert!(CuLogLevel::Error.enabled(CuLogLevel::Info));
357 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Info));
358
359 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Warning));
361 assert!(!CuLogLevel::Info.enabled(CuLogLevel::Warning));
362 assert!(CuLogLevel::Warning.enabled(CuLogLevel::Warning));
363 assert!(CuLogLevel::Error.enabled(CuLogLevel::Warning));
364 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Warning));
365
366 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Error));
368 assert!(!CuLogLevel::Info.enabled(CuLogLevel::Error));
369 assert!(!CuLogLevel::Warning.enabled(CuLogLevel::Error));
370 assert!(CuLogLevel::Error.enabled(CuLogLevel::Error));
371 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Error));
372
373 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Critical));
375 assert!(!CuLogLevel::Info.enabled(CuLogLevel::Critical));
376 assert!(!CuLogLevel::Warning.enabled(CuLogLevel::Critical));
377 assert!(!CuLogLevel::Error.enabled(CuLogLevel::Critical));
378 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Critical));
379 }
380
381 #[test]
382 fn test_cu_log_entry_with_level() {
383 let entry = CuLogEntry::new(42, CuLogLevel::Warning);
384 assert_eq!(entry.level, CuLogLevel::Warning);
385 assert_eq!(entry.msg_index, 42);
386 }
387}