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 std::path::{Path, PathBuf};
10use strfmt::strfmt;
11
12const INDEX_DIR_NAME: &str = "cu29_log_index";
14
15#[allow(dead_code)]
16pub const ANONYMOUS: u32 = 0;
17
18pub const MAX_LOG_PARAMS_ON_STACK: usize = 10;
19
20#[derive(Debug, Serialize, Deserialize, PartialEq)]
22pub struct CuLogEntry {
23 pub time: CuTime,
25
26 pub msg_index: u32,
28
29 pub paramname_indexes: SmallVec<[u32; MAX_LOG_PARAMS_ON_STACK]>,
31
32 pub params: SmallVec<[Value; MAX_LOG_PARAMS_ON_STACK]>,
34}
35
36impl Encode for CuLogEntry {
37 fn encode<E: bincode::enc::Encoder>(
38 &self,
39 encoder: &mut E,
40 ) -> Result<(), bincode::error::EncodeError> {
41 self.time.encode(encoder)?;
42 self.msg_index.encode(encoder)?;
43
44 (self.paramname_indexes.len() as u64).encode(encoder)?;
45 for &index in &self.paramname_indexes {
46 index.encode(encoder)?;
47 }
48
49 (self.params.len() as u64).encode(encoder)?;
50 for param in &self.params {
51 param.encode(encoder)?;
52 }
53
54 Ok(())
55 }
56}
57
58impl<Context> Decode<Context> for CuLogEntry {
59 fn decode<D: bincode::de::Decoder>(
60 decoder: &mut D,
61 ) -> Result<Self, bincode::error::DecodeError> {
62 let time = CuTime::decode(decoder)?;
63 let msg_index = u32::decode(decoder)?;
64
65 let paramname_len = u64::decode(decoder)? as usize;
66 let mut paramname_indexes = SmallVec::with_capacity(paramname_len);
67 for _ in 0..paramname_len {
68 paramname_indexes.push(u32::decode(decoder)?);
69 }
70
71 let params_len = u64::decode(decoder)? as usize;
72 let mut params = SmallVec::with_capacity(params_len);
73 for _ in 0..params_len {
74 params.push(Value::decode(decoder)?);
75 }
76
77 Ok(CuLogEntry {
78 time,
79 msg_index,
80 paramname_indexes,
81 params,
82 })
83 }
84}
85
86impl Display for CuLogEntry {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 write!(
90 f,
91 "CuLogEntry {{ msg_index: {}, paramname_indexes: {:?}, params: {:?} }}",
92 self.msg_index, self.paramname_indexes, self.params
93 )
94 }
95}
96
97impl CuLogEntry {
98 pub fn new(msg_index: u32) -> Self {
100 CuLogEntry {
101 time: 0.into(), msg_index,
104 paramname_indexes: SmallVec::new(),
105 params: SmallVec::new(),
106 }
107 }
108
109 pub fn add_param(&mut self, paramname_index: u32, param: Value) {
112 self.paramname_indexes.push(paramname_index);
113 self.params.push(param);
114 }
115}
116
117#[inline]
119pub fn format_logline(
120 time: CuTime,
121 format_str: &str,
122 params: &[String],
123 named_params: &HashMap<String, String>,
124) -> CuResult<String> {
125 let mut format_str = format_str.to_string();
126
127 for param in params.iter() {
128 format_str = format_str.replacen("{}", param, 1);
129 }
130
131 if named_params.is_empty() {
132 return Ok(format_str);
133 }
134
135 let logline = strfmt(&format_str, named_params).map_err(|e| {
136 CuError::new_with_cause(
137 format!("Failed to format log line: {format_str:?} with variables [{named_params:?}]")
138 .as_str(),
139 e,
140 )
141 })?;
142 Ok(format!("{time}: {logline}"))
143}
144
145pub fn rebuild_logline(all_interned_strings: &[String], entry: &CuLogEntry) -> CuResult<String> {
148 let format_string = &all_interned_strings[entry.msg_index as usize];
149 let mut anon_params: Vec<String> = Vec::new();
150 let mut named_params = HashMap::new();
151
152 for (i, param) in entry.params.iter().enumerate() {
153 let param_as_string = format!("{param}");
154 if entry.paramname_indexes[i] == 0 {
155 anon_params.push(param_as_string);
157 } else {
158 let name = all_interned_strings[entry.paramname_indexes[i] as usize].clone();
160 named_params.insert(name, param_as_string);
161 }
162 }
163 format_logline(entry.time, format_string, &anon_params, &named_params)
164}
165
166fn parent_n_times(path: &Path, n: usize) -> Option<PathBuf> {
167 let mut result = Some(path.to_path_buf());
168 for _ in 0..n {
169 result = result?.parent().map(PathBuf::from);
170 }
171 result
172}
173
174pub fn default_log_index_dir() -> PathBuf {
176 let outdir = std::env::var("LOG_INDEX_DIR").expect("no LOG_INDEX_DIR system variable set, be sure build.rs sets it, see cu29_log/build.rs for example.");
177 let outdir_path = Path::new(&outdir);
178
179 parent_n_times(outdir_path, 3).unwrap().join(INDEX_DIR_NAME)
180}