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, Clone, 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
230 .get(entry.msg_index as usize)
231 .ok_or_else(|| {
232 cu29_traits::CuError::from(format!(
233 "Invalid message index {} (interned strings length {})",
234 entry.msg_index,
235 all_interned_strings.len()
236 ))
237 })?;
238 if entry.paramname_indexes.len() != entry.params.len() {
239 return Err(cu29_traits::CuError::from(format!(
240 "Mismatched parameter metadata: {} names for {} params",
241 entry.paramname_indexes.len(),
242 entry.params.len()
243 )));
244 }
245
246 let mut anon_params = Vec::with_capacity(entry.params.len());
247 let mut named_params = HashMap::with_capacity(entry.params.len());
248
249 for (i, param) in entry.params.iter().enumerate() {
250 let param_as_string = format!("{param}");
251 if entry.paramname_indexes[i] == 0 {
252 anon_params.push(param_as_string);
254 } else {
255 let name = all_interned_strings
257 .get(entry.paramname_indexes[i] as usize)
258 .ok_or_else(|| {
259 cu29_traits::CuError::from(format!(
260 "Invalid parameter name index {} (interned strings length {})",
261 entry.paramname_indexes[i],
262 all_interned_strings.len()
263 ))
264 })?
265 .clone();
266 named_params.insert(name, param_as_string);
267 }
268 }
269 format_logline(
270 entry.time,
271 entry.level,
272 format_string,
273 &anon_params,
274 &named_params,
275 )
276}
277
278#[cfg(all(feature = "defmt", not(feature = "std")))]
280#[macro_export]
281macro_rules! __cu29_defmt_debug {
282 ($fmt:literal $(, $arg:expr)* $(,)?) => {
283 ::defmt::debug!($fmt $(, $arg)*);
284 }
285}
286#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
287#[macro_export]
288macro_rules! __cu29_defmt_debug {
289 ($($tt:tt)*) => {{}};
290}
291
292#[cfg(all(feature = "defmt", not(feature = "std")))]
293#[macro_export]
294macro_rules! __cu29_defmt_info {
295 ($fmt:literal $(, $arg:expr)* $(,)?) => {
296 ::defmt::info!($fmt $(, $arg)*);
297 }
298}
299#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
300#[macro_export]
301macro_rules! __cu29_defmt_info {
302 ($($tt:tt)*) => {{}};
303}
304
305#[cfg(all(feature = "defmt", not(feature = "std")))]
306#[macro_export]
307macro_rules! __cu29_defmt_warn {
308 ($fmt:literal $(, $arg:expr)* $(,)?) => {
309 ::defmt::warn!($fmt $(, $arg)*);
310 }
311}
312#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
313#[macro_export]
314macro_rules! __cu29_defmt_warn {
315 ($($tt:tt)*) => {{}};
316}
317
318#[cfg(all(feature = "defmt", not(feature = "std")))]
319#[macro_export]
320macro_rules! __cu29_defmt_error {
321 ($fmt:literal $(, $arg:expr)* $(,)?) => {
322 ::defmt::error!($fmt $(, $arg)*);
323 }
324}
325#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
326#[macro_export]
327macro_rules! __cu29_defmt_error {
328 ($($tt:tt)*) => {{}};
329}
330
331#[macro_export]
332macro_rules! defmt_debug {
333 ($($tt:tt)*) => { $crate::__cu29_defmt_debug!($($tt)*) };
334}
335
336#[macro_export]
337macro_rules! defmt_info {
338 ($($tt:tt)*) => { $crate::__cu29_defmt_info!($($tt)*) };
339}
340
341#[macro_export]
342macro_rules! defmt_warn {
343 ($($tt:tt)*) => { $crate::__cu29_defmt_warn!($($tt)*) };
344}
345
346#[macro_export]
347macro_rules! defmt_error {
348 ($($tt:tt)*) => { $crate::__cu29_defmt_error!($($tt)*) };
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354
355 #[test]
356 fn test_log_level_ordering() {
357 assert!(CuLogLevel::Critical > CuLogLevel::Error);
358 assert!(CuLogLevel::Error > CuLogLevel::Warning);
359 assert!(CuLogLevel::Warning > CuLogLevel::Info);
360 assert!(CuLogLevel::Info > CuLogLevel::Debug);
361
362 assert!(CuLogLevel::Debug < CuLogLevel::Info);
363 assert!(CuLogLevel::Info < CuLogLevel::Warning);
364 assert!(CuLogLevel::Warning < CuLogLevel::Error);
365 assert!(CuLogLevel::Error < CuLogLevel::Critical);
366 }
367
368 #[test]
369 fn test_log_level_enabled() {
370 assert!(CuLogLevel::Debug.enabled(CuLogLevel::Debug));
372 assert!(CuLogLevel::Info.enabled(CuLogLevel::Debug));
373 assert!(CuLogLevel::Warning.enabled(CuLogLevel::Debug));
374 assert!(CuLogLevel::Error.enabled(CuLogLevel::Debug));
375 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Debug));
376
377 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Info));
379 assert!(CuLogLevel::Info.enabled(CuLogLevel::Info));
380 assert!(CuLogLevel::Warning.enabled(CuLogLevel::Info));
381 assert!(CuLogLevel::Error.enabled(CuLogLevel::Info));
382 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Info));
383
384 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Warning));
386 assert!(!CuLogLevel::Info.enabled(CuLogLevel::Warning));
387 assert!(CuLogLevel::Warning.enabled(CuLogLevel::Warning));
388 assert!(CuLogLevel::Error.enabled(CuLogLevel::Warning));
389 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Warning));
390
391 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Error));
393 assert!(!CuLogLevel::Info.enabled(CuLogLevel::Error));
394 assert!(!CuLogLevel::Warning.enabled(CuLogLevel::Error));
395 assert!(CuLogLevel::Error.enabled(CuLogLevel::Error));
396 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Error));
397
398 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Critical));
400 assert!(!CuLogLevel::Info.enabled(CuLogLevel::Critical));
401 assert!(!CuLogLevel::Warning.enabled(CuLogLevel::Critical));
402 assert!(!CuLogLevel::Error.enabled(CuLogLevel::Critical));
403 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Critical));
404 }
405
406 #[test]
407 fn test_cu_log_entry_with_level() {
408 let entry = CuLogEntry::new(42, CuLogLevel::Warning);
409 assert_eq!(entry.level, CuLogLevel::Warning);
410 assert_eq!(entry.msg_index, 42);
411 }
412}