1use bincode::{Decode, Encode};
2use cu29_clock::CuTime;
3use cu29_traits::{CuError, CuResult};
4use cu29_value::Value;
5use serde::{Deserialize, Serialize};
6use smallvec::SmallVec;
7use std::collections::HashMap;
8use std::fmt::Display;
9use strfmt::strfmt;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
13pub enum CuLogLevel {
14 Debug = 0,
16 Info = 1,
18 Warning = 2,
20 Error = 3,
22 Critical = 4,
24}
25
26impl CuLogLevel {
27 #[inline]
33 pub const fn enabled(self, max_level: CuLogLevel) -> bool {
34 self as u8 >= max_level as u8
35 }
36}
37
38#[allow(dead_code)]
39pub const ANONYMOUS: u32 = 0;
40
41pub const MAX_LOG_PARAMS_ON_STACK: usize = 10;
42
43#[derive(Debug, Serialize, Deserialize, PartialEq)]
45pub struct CuLogEntry {
46 pub time: CuTime,
48
49 pub level: CuLogLevel,
51
52 pub msg_index: u32,
54
55 pub paramname_indexes: SmallVec<[u32; MAX_LOG_PARAMS_ON_STACK]>,
57
58 pub params: SmallVec<[Value; MAX_LOG_PARAMS_ON_STACK]>,
60}
61
62impl Encode for CuLogEntry {
63 fn encode<E: bincode::enc::Encoder>(
64 &self,
65 encoder: &mut E,
66 ) -> Result<(), bincode::error::EncodeError> {
67 self.time.encode(encoder)?;
68 (self.level as u8).encode(encoder)?;
69 self.msg_index.encode(encoder)?;
70
71 (self.paramname_indexes.len() as u64).encode(encoder)?;
72 for &index in &self.paramname_indexes {
73 index.encode(encoder)?;
74 }
75
76 (self.params.len() as u64).encode(encoder)?;
77 for param in &self.params {
78 param.encode(encoder)?;
79 }
80
81 Ok(())
82 }
83}
84
85impl<Context> Decode<Context> for CuLogEntry {
86 fn decode<D: bincode::de::Decoder>(
87 decoder: &mut D,
88 ) -> Result<Self, bincode::error::DecodeError> {
89 let time = CuTime::decode(decoder)?;
90 let level_raw = u8::decode(decoder)?;
91 let level = match level_raw {
92 0 => CuLogLevel::Debug,
93 1 => CuLogLevel::Info,
94 2 => CuLogLevel::Warning,
95 3 => CuLogLevel::Error,
96 4 => CuLogLevel::Critical,
97 _ => CuLogLevel::Debug, };
99 let msg_index = u32::decode(decoder)?;
100
101 let paramname_len = u64::decode(decoder)? as usize;
102 let mut paramname_indexes = SmallVec::with_capacity(paramname_len);
103 for _ in 0..paramname_len {
104 paramname_indexes.push(u32::decode(decoder)?);
105 }
106
107 let params_len = u64::decode(decoder)? as usize;
108 let mut params = SmallVec::with_capacity(params_len);
109 for _ in 0..params_len {
110 params.push(Value::decode(decoder)?);
111 }
112
113 Ok(CuLogEntry {
114 time,
115 level,
116 msg_index,
117 paramname_indexes,
118 params,
119 })
120 }
121}
122
123impl Display for CuLogEntry {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 write!(
127 f,
128 "CuLogEntry {{ level: {:?}, msg_index: {}, paramname_indexes: {:?}, params: {:?} }}",
129 self.level, self.msg_index, self.paramname_indexes, self.params
130 )
131 }
132}
133
134impl CuLogEntry {
135 pub fn new(msg_index: u32, level: CuLogLevel) -> Self {
137 CuLogEntry {
138 time: 0.into(), level,
141 msg_index,
142 paramname_indexes: SmallVec::new(),
143 params: SmallVec::new(),
144 }
145 }
146
147 pub fn add_param(&mut self, paramname_index: u32, param: Value) {
150 self.paramname_indexes.push(paramname_index);
151 self.params.push(param);
152 }
153}
154
155#[inline]
157pub fn format_logline(
158 time: CuTime,
159 level: CuLogLevel,
160 format_str: &str,
161 params: &[String],
162 named_params: &HashMap<String, String>,
163) -> CuResult<String> {
164 let mut format_str = format_str.to_string();
165
166 for param in params.iter() {
167 format_str = format_str.replacen("{}", param, 1);
168 }
169
170 if named_params.is_empty() {
171 return Ok(format_str);
172 }
173
174 let logline = strfmt(&format_str, named_params).map_err(|e| {
175 CuError::new_with_cause(
176 format!("Failed to format log line: {format_str:?} with variables [{named_params:?}]")
177 .as_str(),
178 e,
179 )
180 })?;
181 Ok(format!("{time} [{level:?}]: {logline}"))
182}
183
184pub fn rebuild_logline(all_interned_strings: &[String], entry: &CuLogEntry) -> CuResult<String> {
187 let format_string = &all_interned_strings[entry.msg_index as usize];
188 let mut anon_params: Vec<String> = Vec::new();
189 let mut named_params = HashMap::new();
190
191 for (i, param) in entry.params.iter().enumerate() {
192 let param_as_string = format!("{param}");
193 if entry.paramname_indexes[i] == 0 {
194 anon_params.push(param_as_string);
196 } else {
197 let name = all_interned_strings[entry.paramname_indexes[i] as usize].clone();
199 named_params.insert(name, param_as_string);
200 }
201 }
202 format_logline(
203 entry.time,
204 entry.level,
205 format_string,
206 &anon_params,
207 &named_params,
208 )
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_log_level_ordering() {
217 assert!(CuLogLevel::Critical > CuLogLevel::Error);
218 assert!(CuLogLevel::Error > CuLogLevel::Warning);
219 assert!(CuLogLevel::Warning > CuLogLevel::Info);
220 assert!(CuLogLevel::Info > CuLogLevel::Debug);
221
222 assert!(CuLogLevel::Debug < CuLogLevel::Info);
223 assert!(CuLogLevel::Info < CuLogLevel::Warning);
224 assert!(CuLogLevel::Warning < CuLogLevel::Error);
225 assert!(CuLogLevel::Error < CuLogLevel::Critical);
226 }
227
228 #[test]
229 fn test_log_level_enabled() {
230 assert!(CuLogLevel::Debug.enabled(CuLogLevel::Debug));
232 assert!(CuLogLevel::Info.enabled(CuLogLevel::Debug));
233 assert!(CuLogLevel::Warning.enabled(CuLogLevel::Debug));
234 assert!(CuLogLevel::Error.enabled(CuLogLevel::Debug));
235 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Debug));
236
237 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Info));
239 assert!(CuLogLevel::Info.enabled(CuLogLevel::Info));
240 assert!(CuLogLevel::Warning.enabled(CuLogLevel::Info));
241 assert!(CuLogLevel::Error.enabled(CuLogLevel::Info));
242 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Info));
243
244 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Warning));
246 assert!(!CuLogLevel::Info.enabled(CuLogLevel::Warning));
247 assert!(CuLogLevel::Warning.enabled(CuLogLevel::Warning));
248 assert!(CuLogLevel::Error.enabled(CuLogLevel::Warning));
249 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Warning));
250
251 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Error));
253 assert!(!CuLogLevel::Info.enabled(CuLogLevel::Error));
254 assert!(!CuLogLevel::Warning.enabled(CuLogLevel::Error));
255 assert!(CuLogLevel::Error.enabled(CuLogLevel::Error));
256 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Error));
257
258 assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Critical));
260 assert!(!CuLogLevel::Info.enabled(CuLogLevel::Critical));
261 assert!(!CuLogLevel::Warning.enabled(CuLogLevel::Critical));
262 assert!(!CuLogLevel::Error.enabled(CuLogLevel::Critical));
263 assert!(CuLogLevel::Critical.enabled(CuLogLevel::Critical));
264 }
265
266 #[test]
267 fn test_cu_log_entry_with_level() {
268 let entry = CuLogEntry::new(42, CuLogLevel::Warning);
269 assert_eq!(entry.level, CuLogLevel::Warning);
270 assert_eq!(entry.msg_index, 42);
271 }
272}