cog_task/resource/
logger.rs

1use crate::action::Action;
2use crate::comm::QWriter;
3use crate::server::{AsyncSignal, Config, Info};
4use chrono::{DateTime, Local};
5use eyre::{eyre, Context, Error, Result};
6use itertools::Itertools;
7use ron::ser::PrettyConfig;
8use serde::{Deserialize, Serialize, Serializer};
9use serde_cbor::{from_slice, Value};
10use std::collections::HashMap;
11use std::fmt::Debug;
12use std::fs::{create_dir_all, File};
13use std::io::Write;
14use std::path::PathBuf;
15use std::time::Duration;
16use std::{fs, thread};
17
18pub const TAG_INFO: u64 = 0x01;
19pub const TAG_CONFIG: u64 = 0x02;
20pub const TAG_ACTION: u64 = 0x03;
21
22pub type LogGroup = (Vec<(String, String, Value)>, bool);
23
24#[derive(Debug, Default)]
25pub struct Logger {
26    out_dir: PathBuf,
27    content: HashMap<String, LogGroup>,
28    needs_flush: bool,
29    log_format: LogFormat,
30}
31
32#[derive(Debug, Clone)]
33pub enum LoggerSignal {
34    Append(String, (String, Value)),
35    Extend(String, Vec<(String, Value)>),
36    Write(String, Value),
37    Flush,
38}
39
40#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
41#[serde(rename_all = "lowercase")]
42pub enum LogFormat {
43    Inherit,
44    JSON,
45    YAML,
46    RON,
47}
48
49impl Default for LogFormat {
50    #[inline(always)]
51    fn default() -> Self {
52        LogFormat::Inherit
53    }
54}
55
56impl LogFormat {
57    pub fn or(&self, other: &Self) -> Self {
58        if let Self::Inherit = self {
59            *other
60        } else {
61            *self
62        }
63    }
64}
65
66impl LoggerSignal {
67    #[inline(always)]
68    fn requires_flush(&self) -> bool {
69        matches!(
70            self,
71            LoggerSignal::Append(_, _) | LoggerSignal::Extend(_, _)
72        )
73    }
74}
75
76impl From<LoggerSignal> for AsyncSignal {
77    fn from(signal: LoggerSignal) -> Self {
78        AsyncSignal::Logger(Local::now(), signal)
79    }
80}
81
82impl Logger {
83    pub fn new(info: &Info, config: &Config) -> Result<Self> {
84        let block = normalized_name(info.block());
85        let date = Local::now().format("%F").to_string();
86        let time = Local::now().format("%T").to_string().replace(':', "-");
87        let out_dir = info.output().join(format!("{date}/{block}/{time}"));
88
89        if out_dir.exists() {
90            return Err(eyre!("Output directory already exists: {out_dir:?}"));
91        }
92        create_dir_all(&out_dir)
93            .wrap_err_with(|| format!("Failed to create output directory: {out_dir:?}"))?;
94
95        Ok(Self {
96            out_dir,
97            content: HashMap::new(),
98            needs_flush: false,
99            log_format: config.log_format(),
100        })
101    }
102
103    fn append(&mut self, time: DateTime<Local>, group: String, entry: (String, Value)) {
104        let time = time.to_string();
105        let (name, value) = entry;
106        let (vec, flush) = self.content.entry(group).or_default();
107        vec.push((time, name, value));
108        *flush = true;
109        self.needs_flush = true;
110    }
111
112    fn extend(&mut self, time: DateTime<Local>, group: String, entries: Vec<(String, Value)>) {
113        let time = time.to_string();
114        let (vec, flush) = self.content.entry(group).or_default();
115        vec.extend(
116            entries
117                .into_iter()
118                .map(|(name, value)| (time.clone(), name, value)),
119        );
120        *flush = true;
121        self.needs_flush = true;
122    }
123
124    fn write(&mut self, name: String, content: Value) -> Result<()> {
125        let name = format!("{}.log", normalized_name(&name));
126        let path = self.out_dir.join(name);
127
128        if let Value::Text(content) = content {
129            fs::write(&path, &content)
130                .wrap_err_with(|| format!("Failed to write to log file ({path:?})."))?;
131        } else {
132            let file = File::create(&path)
133                .wrap_err_with(|| format!("Failed to create log file ({path:?})."))?;
134            write_as(file, &content, self.log_format)
135                .wrap_err_with(|| format!("Failed to write to log file ({path:?})."))?;
136        }
137
138        #[cfg(debug_assertions)]
139        println!("{:?} -> Wrote to file: {path:?}", Local::now());
140        Ok(())
141    }
142
143    fn flush(&mut self) -> Result<()> {
144        for (group, (vec, flush)) in self.content.iter_mut().filter(|(_, (_, flush))| *flush) {
145            let name = format!("{}.log", normalized_name(group));
146            let path = self.out_dir.join(name);
147            let mut file = File::create(&path)
148                .wrap_err_with(|| format!("Failed to create log file ({path:?})."))?;
149
150            write_vec(&mut file, self.log_format, vec)?;
151            *flush = false;
152
153            #[cfg(debug_assertions)]
154            println!("{:?} -> Wrote to file: {path:?}", Local::now());
155        }
156        self.needs_flush = false;
157        Ok(())
158    }
159
160    pub fn update(
161        &mut self,
162        time: DateTime<Local>,
163        signal: LoggerSignal,
164        async_writer: &QWriter<AsyncSignal>,
165    ) -> Result<()> {
166        if signal.requires_flush() && !self.needs_flush {
167            let mut async_writer = async_writer.clone();
168            thread::spawn(move || {
169                thread::sleep(Duration::from_secs(5));
170                async_writer.push(LoggerSignal::Flush);
171            });
172        }
173
174        match signal {
175            LoggerSignal::Append(group, entry) => {
176                self.append(time, group, entry);
177            }
178            LoggerSignal::Extend(group, entries) => {
179                self.extend(time, group, entries);
180            }
181            LoggerSignal::Write(name, content) => {
182                self.write(name, content)?;
183            }
184            LoggerSignal::Flush => {
185                self.flush()?;
186                self.needs_flush = false;
187            }
188        }
189
190        Ok(())
191    }
192
193    pub fn finish(&mut self) -> Result<()> {
194        self.flush()
195            .wrap_err("Failed to graciously close logger.")?;
196
197        self.content.clear();
198        Ok(())
199    }
200}
201
202pub fn normalized_name(name: &str) -> String {
203    name.to_lowercase()
204        .split_whitespace()
205        .join("_")
206        .replace('-', "_")
207}
208
209fn write_vec(file: &mut File, fmt: LogFormat, vec: &Vec<(String, String, Value)>) -> Result<()> {
210    let mut vec_t: Vec<(&str, &str, Serializable)> = vec![];
211    for (a, b, v) in vec {
212        vec_t.push((a, b, Serializable::try_from(v)?));
213    }
214
215    write_as(file, &vec_t, fmt)
216}
217
218fn write_as<W, T>(mut file: W, content: &T, fmt: LogFormat) -> Result<()>
219where
220    W: Write,
221    T: ?Sized + Serialize,
222{
223    match fmt {
224        LogFormat::Inherit => Err(eyre!("Cannot log with log_format=`Inherit`.")),
225        LogFormat::JSON => {
226            serde_json::to_writer_pretty(file, &content).wrap_err("Failed to log JSON to file")
227        }
228        LogFormat::YAML => {
229            serde_yaml::to_writer(file, &content).wrap_err("Failed to log YAML to file")
230        }
231        LogFormat::RON => file
232            .write_all(
233                ron::ser::to_string_pretty(&content, PrettyConfig::default())
234                    .wrap_err("Failed to serialize log as RON")?
235                    .as_bytes(),
236            )
237            .wrap_err("Failed to log RON to file"),
238    }
239}
240
241enum Serializable<'a> {
242    Info(Info),
243    Config(Config),
244    Action(Box<dyn Action>),
245    Value(&'a Value),
246}
247
248impl<'a> Serialize for Serializable<'a> {
249    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
250    where
251        S: Serializer,
252    {
253        match self {
254            Serializable::Info(info) => info.serialize(serializer),
255            Serializable::Config(config) => config.serialize(serializer),
256            Serializable::Action(action) => action.serialize(serializer),
257            Serializable::Value(value) => value.serialize(serializer),
258        }
259    }
260}
261
262impl<'a> TryFrom<&'a Value> for Serializable<'a> {
263    type Error = Error;
264
265    fn try_from(v: &'a Value) -> Result<Self> {
266        Ok(match v {
267            Value::Tag(TAG_INFO, v) => Serializable::Info(match v.as_ref() {
268                Value::Bytes(v) => from_slice::<Info>(v).unwrap(),
269                _ => return Err(eyre!("Failed to deserialize Info struct")),
270            }),
271            Value::Tag(TAG_CONFIG, v) => Serializable::Config(match v.as_ref() {
272                Value::Bytes(v) => from_slice::<Config>(v).unwrap(),
273                _ => return Err(eyre!("Failed to deserialize Config struct",)),
274            }),
275            Value::Tag(TAG_ACTION, v) => Serializable::Action(match v.as_ref() {
276                Value::Bytes(v) => from_slice::<Box<dyn Action>>(v).unwrap(),
277                _ => return Err(eyre!("Failed to deserialize Action struct",)),
278            }),
279            v => Serializable::Value(v),
280        })
281    }
282}