use crate::{
common::{
error::{err, Result},
log::{
callback::LogCallback,
deinit, init,
proxy::LogProxy,
tee_file::{TeeFile, TeeFileConfiguration},
Log, LogRecord, Loglevel, LoglevelFilter, PID,
},
},
trace,
};
use std::thread;
use term::stderr;
#[derive(Debug)]
pub struct LogThread {
sender: Option<crossbeam_channel::Sender<LogRecord>>,
ipc_sender: Option<ipc_channel::ipc::IpcSender<LogRecord>>,
handler: Option<thread::JoinHandle<Result<()>>>,
}
impl LogThread {
pub fn spawn(
name: impl Into<String>,
proxy_level: LoglevelFilter,
stderr_level: LoglevelFilter,
callback: Option<LogCallback>,
tee_files: Vec<TeeFileConfiguration>,
) -> Result<LogThread> {
let (sender, receiver): (_, crossbeam_channel::Receiver<LogRecord>) =
crossbeam_channel::unbounded();
let (ipc_sender, ipc_receiver) = ipc_channel::ipc::channel()?;
let handler = thread::spawn(move || {
let mut t = if stderr_level > LoglevelFilter::Off {
stderr()
} else {
None
};
let supports_dim = t.is_some() && t.as_ref().unwrap().supports_attr(term::Attr::Dim);
let supports_colors = t.is_some()
&& t.as_ref()
.unwrap()
.supports_attr(term::Attr::ForegroundColor(9));
let trace = t.is_some() && stderr_level >= LoglevelFilter::Trace;
let debug = t.is_some() && stderr_level >= LoglevelFilter::Debug;
let tee_files: Vec<TeeFile> = tee_files
.into_iter()
.map(TeeFile::new)
.collect::<Result<Vec<_>>>()
.or_else(|e| err(e.to_string()))?;
while let Ok(record) = receiver.recv() {
let level = LoglevelFilter::from(record.level());
if let Some(callback) = &callback {
callback.log(&record);
}
tee_files
.iter()
.filter(|tf| tf.enabled(record.level()))
.for_each(|tf| tf.log(&record));
if t.is_some() && level <= stderr_level {
let t = t.as_mut().unwrap();
let color: term::color::Color = record.level().into();
t.reset()?;
if supports_dim {
t.attr(term::Attr::Dim)?;
}
write!(
t,
"{} ",
humantime::format_rfc3339_seconds(record.timestamp()),
)?;
t.reset()?;
if debug {
if supports_colors {
t.fg(8)?;
}
write!(
t,
"{:<6}",
format!("+{}ms", record.timestamp().elapsed().unwrap().as_millis())
)?;
t.reset()?;
}
if supports_colors {
t.fg(color)?;
}
write!(t, "{:>5} ", format!("{}", record.level()))?;
t.reset()?;
if supports_colors && *PID != record.process() {
t.fg(record.process() % 7 + 1)?;
}
if supports_dim {
t.attr(term::Attr::Dim)?;
}
if trace {
write!(
t,
"{:<32} ",
format!(
"{:>5}:{:<2} {} ",
record.process(),
record.thread(),
record.logger(),
)
)?;
} else {
write!(t, "{:<25} ", record.logger())?;
}
t.reset()?;
if supports_colors && record.level() == Loglevel::Trace {
t.fg(color)?;
}
writeln!(t, "{}", record.payload())?;
t.reset()?;
}
}
if trace {
let t = t.as_mut().unwrap();
if supports_colors {
if t.supports_attr(term::Attr::Standout(true)) {
t.attr(term::Attr::Standout(true))?;
}
t.fg(term::color::BRIGHT_BLACK)?;
}
writeln!(t, "$")?;
}
if let Some(mut term) = t {
term.reset()?;
}
Ok(())
});
ipc_channel::router::ROUTER
.route_ipc_receiver_to_crossbeam_sender(ipc_receiver, sender.clone());
init(vec![LogProxy::boxed(name, proxy_level, sender.clone())])?;
trace!("LogThread started");
Ok(LogThread {
sender: Some(sender),
ipc_sender: Some(ipc_sender),
handler: Some(handler),
})
}
pub fn get_sender(&self) -> crossbeam_channel::Sender<LogRecord> {
self.sender.clone().unwrap()
}
pub fn get_ipc_sender(&self) -> ipc_channel::ipc::IpcSender<LogRecord> {
self.ipc_sender.clone().unwrap()
}
}
impl Drop for LogThread {
fn drop(&mut self) {
trace!("Dropping LogThread");
deinit().expect("Failed to deinitialize thread-local logger");
self.sender = None;
self.ipc_sender = None;
self.handler
.take()
.expect("LogThread failed to start")
.join()
.expect("LogThread failed to terminate")
.expect("LogThread failed");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn debug() {
let _lt = LogThread::spawn(
"name",
LoglevelFilter::Debug,
LoglevelFilter::Error,
None,
vec![],
)
.unwrap();
#[cfg(target_os = "macos")]
assert_eq!(&format!("{:?}", _lt).as_str()[..100], "LogThread { sender: Some(Sender { .. }), ipc_sender: Some(IpcSender { os_sender: OsIpcSender { port:");
#[cfg(target_os = "linux")]
assert_eq!(&format!("{:?}", _lt).as_str()[..98], "LogThread { sender: Some(Sender { .. }), ipc_sender: Some(IpcSender { os_sender: OsIpcSender { fd:");
}
}