use cu29_clock::{ClockProvider, RobotClock};
use cu29_intern_strs::read_interned_strings;
use cu29_log::CuLogEntry;
use cu29_traits::{CuResult, WriteStream};
use kanal::{bounded, Sender};
use log::{Log, Record};
use once_cell::sync::OnceCell;
use std::path::PathBuf;
use std::sync::Arc;
use std::thread;
use std::thread::{sleep, JoinHandle};
use std::time::Duration;
static QUEUE: OnceCell<Sender<CuLogEntry>> = OnceCell::new();
pub struct LoggerRuntime {
clock: RobotClock,
handle: Option<JoinHandle<()>>,
extra_text_logger: Option<ExtraTextLogger>,
}
impl ClockProvider for LoggerRuntime {
fn get_clock(&self) -> RobotClock {
self.clock.clone()
}
}
impl LoggerRuntime {
pub fn init(
clock_source: RobotClock,
destination: impl WriteStream<CuLogEntry> + 'static,
extra_text_logger: Option<ExtraTextLogger>,
) -> Self {
if (!cfg!(debug_assertions)) && extra_text_logger.is_some() {
eprintln!("Extra text logger is only available in debug builds. Ignoring the extra text logger.");
};
let mut runtime = LoggerRuntime {
clock: clock_source,
extra_text_logger,
handle: None,
};
let (s, handle) = runtime.initialize_queue(destination);
QUEUE
.set(s)
.expect("Failed to initialize the logger queue.");
runtime.handle = Some(handle);
runtime
}
fn initialize_queue(
&self,
mut destination: impl WriteStream<CuLogEntry> + 'static,
) -> (Sender<CuLogEntry>, JoinHandle<()>) {
let (sender, receiver) = bounded::<CuLogEntry>(100);
#[cfg(debug_assertions)]
let (index, extra_text_logger) = if let Some(extra) = &self.extra_text_logger {
let index = Some(
read_interned_strings(extra.path_to_index.as_path())
.expect("Failed to read the interned strings"),
);
let logger = Some(extra.logger.clone());
(index, logger)
} else {
(None, None)
};
let clock = self.clock.clone();
let handle = thread::spawn(move || {
let receiver = receiver.clone();
loop {
if let Ok(mut cu_log_entry) = receiver.recv() {
cu_log_entry.time = clock.now();
if let Err(err) = destination.log(&cu_log_entry) {
eprintln!("Failed to log data: {}", err);
}
#[cfg(debug_assertions)]
if let Some(index) = &index {
if let Some(ref logger) = extra_text_logger {
let stringified = cu29_log::rebuild_logline(index, &cu_log_entry);
match stringified {
Ok(s) => {
let s = format!("[{}] {}", cu_log_entry.time, s);
logger.log(
&Record::builder()
.level(log::Level::Debug)
.args(format_args!("{}", s))
.build(),
); }
Err(e) => {
eprintln!("Failed to rebuild log line: {}", e);
}
}
}
}
} else {
break;
}
}
});
(sender, handle)
}
pub fn is_alive(&self) -> bool {
QUEUE.get().is_some()
}
pub fn flush(&self) {
if let Some(queue) = QUEUE.get() {
loop {
if queue.is_empty() {
break;
}
println!("Waiting for the queue to empty.");
sleep(Duration::from_millis(1));
}
}
}
pub fn close(&mut self) {
let queue = QUEUE.get();
if queue.is_none() {
eprintln!("Logger closed before it was initialized.");
return;
}
self.flush();
queue.unwrap().close();
if let Some(handle) = self.handle.take() {
handle.join().expect("Failed to join the logger thread.");
self.handle = None;
}
}
}
impl Drop for LoggerRuntime {
fn drop(&mut self) {
self.close();
}
}
pub struct ExtraTextLogger {
path_to_index: PathBuf,
logger: Arc<dyn Log>,
}
impl ExtraTextLogger {
pub fn new(path_to_index: PathBuf, logger: Box<dyn Log>) -> Self {
ExtraTextLogger {
path_to_index,
logger: Arc::new(logger),
}
}
}
#[inline]
pub fn log(entry: CuLogEntry) -> CuResult<()> {
if let Some(queue) = QUEUE.get() {
let err = queue
.send(entry)
.map_err(|e| format!("Failed to send data to the logger, did you hold the reference to the logger long enough? {:?}", e).into());
err
} else {
Err("Logger not initialized.".into())
}
}
#[cfg(test)]
mod tests {
use crate::CuLogEntry;
use bincode::config::standard;
use cu29_log::value::Value;
#[test]
fn test_encode_decode_structured_log() {
let log_entry = CuLogEntry {
time: 0.into(),
msg_index: 1,
paramname_indexes: vec![2, 3],
params: vec![Value::String("test".to_string())],
};
let encoded = bincode::encode_to_vec(&log_entry, standard()).unwrap();
println!("{:?}", encoded);
let decoded_tuple: (CuLogEntry, usize) =
bincode::decode_from_slice(&encoded, standard()).unwrap();
assert_eq!(log_entry, decoded_tuple.0);
}
}