use std::collections::VecDeque;
use std::io;
use std::sync::{Arc, Mutex, PoisonError};
use tracing::{Level, Metadata};
use tracing_subscriber::fmt::MakeWriter;
const KEEP: usize = 500;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LogLine {
pub level: Level,
pub text: String,
}
#[derive(Clone, Default)]
pub struct LogBuffer {
lines: Arc<Mutex<Lines>>,
}
#[derive(Default)]
struct Lines {
done: VecDeque<LogLine>,
partial: String,
}
impl LogBuffer {
#[must_use]
pub fn tail(&self, count: usize) -> Vec<LogLine> {
let lines = self.lines.lock().unwrap_or_else(PoisonError::into_inner);
let skip = lines.done.len().saturating_sub(count);
lines.done.iter().skip(skip).cloned().collect()
}
#[must_use]
pub fn writer(&self, level: Level) -> LogWriter {
LogWriter {
buffer: self.clone(),
level,
}
}
}
pub struct LogWriter {
buffer: LogBuffer,
level: Level,
}
impl io::Write for LogWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut lines = self
.buffer
.lines
.lock()
.unwrap_or_else(PoisonError::into_inner);
for ch in String::from_utf8_lossy(buf).chars() {
if ch == '\n' {
let text = std::mem::take(&mut lines.partial);
if lines.done.len() == KEEP {
lines.done.pop_front();
}
lines.done.push_back(LogLine {
level: self.level,
text,
});
} else {
lines.partial.push(ch);
}
}
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl<'a> MakeWriter<'a> for LogBuffer {
type Writer = LogWriter;
fn make_writer(&'a self) -> Self::Writer {
self.writer(Level::INFO)
}
fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer {
self.writer(*meta.level())
}
}