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::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 { ($fmt:literal $(, $arg:expr)* $(,)?) => { defmt::debug!($fmt $(, $arg)*); } }
257#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
258#[macro_export]
259macro_rules! __cu29_defmt_debug {
260 ($($tt:tt)*) => {{}};
261}
262
263#[cfg(all(feature = "defmt", not(feature = "std")))]
264#[macro_export]
265macro_rules! __cu29_defmt_info { ($fmt:literal $(, $arg:expr)* $(,)?) => { defmt::info! ($fmt $(, $arg)*); } }
266#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
267#[macro_export]
268macro_rules! __cu29_defmt_info {
269 ($($tt:tt)*) => {{}};
270}
271
272#[cfg(all(feature = "defmt", not(feature = "std")))]
273#[macro_export]
274macro_rules! __cu29_defmt_warn { ($fmt:literal $(, $arg:expr)* $(,)?) => { defmt::warn! ($fmt $(, $arg)*); } }
275#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
276#[macro_export]
277macro_rules! __cu29_defmt_warn {
278 ($($tt:tt)*) => {{}};
279}
280
281#[cfg(all(feature = "defmt", not(feature = "std")))]
282#[macro_export]
283macro_rules! __cu29_defmt_error { ($fmt:literal $(, $arg:expr)* $(,)?) => { defmt::error!($fmt $(, $arg)*); } }
284#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
285#[macro_export]
286macro_rules! __cu29_defmt_error {
287 ($($tt:tt)*) => {{}};
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
295 fn test_log_level_ordering() {
296 assert!(CuLogLevel::Critical > CuLogLevel::Error);
297 assert!(CuLogLevel::Error > CuLogLevel::Warning);
298 assert!(CuLogLevel::Warning > CuLogLevel::Info);
299 assert!(CuLogLevel::Info > CuLogLevel::Debug);
300
301 assert!(CuLogLevel::Debug < CuLogLevel::Info);
302 assert!(CuLogLevel::Info < CuLogLevel::Warning);
303 assert!(CuLogLevel::Warning < CuLogLevel::Error);
304 assert!(CuLogLevel::Error < CuLogLevel::Critical);
305 }
306
307 #[test]
308 fn test_log_level_enabled() {
309 assert!(CuLogLevel::Debug.enabled(CuLogLevel::Debug));
311 assert!(CuLogLevel::Info.enabled(CuLogLevel::Debug));
312 assert!(CuLogLevel::Warning.enabled(CuLogLevel::Debug));
313 assert!(CuLogLevel::Error.enabled(CuLogLevel::Debug));
314 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Debug));
315
316 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Info));
318 assert!(CuLogLevel::Info.enabled(CuLogLevel::Info));
319 assert!(CuLogLevel::Warning.enabled(CuLogLevel::Info));
320 assert!(CuLogLevel::Error.enabled(CuLogLevel::Info));
321 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Info));
322
323 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Warning));
325 assert!(!CuLogLevel::Info.enabled(CuLogLevel::Warning));
326 assert!(CuLogLevel::Warning.enabled(CuLogLevel::Warning));
327 assert!(CuLogLevel::Error.enabled(CuLogLevel::Warning));
328 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Warning));
329
330 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Error));
332 assert!(!CuLogLevel::Info.enabled(CuLogLevel::Error));
333 assert!(!CuLogLevel::Warning.enabled(CuLogLevel::Error));
334 assert!(CuLogLevel::Error.enabled(CuLogLevel::Error));
335 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Error));
336
337 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Critical));
339 assert!(!CuLogLevel::Info.enabled(CuLogLevel::Critical));
340 assert!(!CuLogLevel::Warning.enabled(CuLogLevel::Critical));
341 assert!(!CuLogLevel::Error.enabled(CuLogLevel::Critical));
342 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Critical));
343 }
344
345 #[test]
346 fn test_cu_log_entry_with_level() {
347 let entry = CuLogEntry::new(42, CuLogLevel::Warning);
348 assert_eq!(entry.level, CuLogLevel::Warning);
349 assert_eq!(entry.msg_index, 42);
350 }
351}