use super::Sink;
use crate::formatter::Format;
use log::{Level, Record};
use std::sync::mpsc::{self, SyncSender};
use std::thread;
struct OwnedRecord {
level: Level,
target: String,
file: Option<String>,
line: Option<u32>,
module_path: Option<String>,
formatted: String,
}
impl OwnedRecord {
fn from_record(record: &Record, formatter: &dyn Format) -> Self {
Self {
level: record.level(),
target: record.target().to_string(),
file: record.file().map(|s| s.to_string()),
line: record.line(),
module_path: record.module_path().map(|s| s.to_string()),
formatted: formatter.format(record),
}
}
}
struct Preformatted<'a>(&'a str);
impl<'a> Format for Preformatted<'a> {
fn format(&self, _record: &Record) -> String {
self.0.to_string()
}
}
enum AsyncMessage {
Log(OwnedRecord),
Flush(mpsc::Sender<()>),
Shutdown,
}
pub struct AsyncSink {
sender: SyncSender<AsyncMessage>,
}
impl AsyncSink {
pub fn new(inners: Vec<Box<dyn Sink>>, capacity: usize) -> Self {
let (sender, receiver) = mpsc::sync_channel::<AsyncMessage>(capacity);
thread::Builder::new()
.name("prologger-async-worker".to_string())
.spawn(move || {
for msg in receiver {
match msg {
AsyncMessage::Log(owned) => {
let mut builder = log::Record::builder();
builder
.level(owned.level)
.target(&owned.target)
.file(owned.file.as_deref())
.line(owned.line)
.module_path(owned.module_path.as_deref());
let record = builder.args(format_args!("")).build();
let formatter = Preformatted(&owned.formatted);
for inner in &inners {
inner.write(&record, &formatter);
}
}
AsyncMessage::Flush(reply) => {
for inner in &inners {
inner.flush();
}
let _ = reply.send(());
}
AsyncMessage::Shutdown => {
for inner in &inners {
inner.flush();
}
break;
}
}
}
})
.expect("Failed to spawn prologger async worker thread");
Self { sender }
}
}
impl Sink for AsyncSink {
fn write(&self, record: &Record, formatter: &dyn Format) {
let owned = OwnedRecord::from_record(record, formatter);
let _ = self.sender.try_send(AsyncMessage::Log(owned));
}
fn flush(&self) {
let (tx, rx) = mpsc::channel();
if self.sender.send(AsyncMessage::Flush(tx)).is_ok() {
let _ = rx.recv();
}
}
}
impl Drop for AsyncSink {
fn drop(&mut self) {
let _ = self.sender.send(AsyncMessage::Shutdown);
}
}