use std::fmt::{Debug, Display, Formatter, Write};
use std::sync::Arc;
use chrono::{DateTime, Local};
use parking_lot::{RwLock, RwLockReadGuard};
use ringbuf::HeapRb;
use ringbuf::traits::consumer::Consumer;
use ringbuf::traits::{Observer, RingBuffer};
use tracing::{Event, Level, Metadata, Subscriber};
use tracing::field::{Field, Visit};
use tracing_subscriber::Layer;
use tracing_subscriber::layer::Context;
use crate::NonExhaustive;
#[derive(Clone, Debug)]
pub struct ConsoleLogRecord {
pub date_time: DateTime<Local>,
pub level: Level,
pub message: String,
pub _ne: NonExhaustive,
}
impl Display for ConsoleLogRecord {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {} {}", self.date_time.format("%H:%M:%S"), self.level, self.message)
}
}
impl ConsoleLogRecord {
pub fn new(level: Level, message: String) -> Self {
Self {
date_time: Local::now(),
level,
message,
_ne: NonExhaustive(()),
}
}
pub fn for_event(event: &Event) -> Self {
let mut visitor = LogEntryVisitor::default();
event.record(&mut visitor);
visitor.build(event.metadata())
}
}
struct LogEntryVisitor {
message: Option<String>,
data: String,
}
impl Default for LogEntryVisitor {
fn default() -> Self {
Self {
message: None,
data: "".into(),
}
}
}
impl Visit for LogEntryVisitor {
fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
match field.name() {
"message" => {
self.message = Some(format!("{value:?}"));
},
_ => {
write!(&mut self.data, " {} = {:?}", field.name(), value).unwrap();
}
}
}
}
impl LogEntryVisitor {
fn build(self, metadata: &'static Metadata<'static>) -> ConsoleLogRecord {
let mut message = self.message.unwrap_or("".into());
message += &self.data;
ConsoleLogRecord::new(metadata.level().clone(), message)
}
}
pub struct ConsoleLog {
buffer: RwLock<HeapRb<ConsoleLogRecord>>,
}
impl ConsoleLog {
pub fn new(capacity: usize) -> Self {
Self {
buffer: RwLock::new(HeapRb::new(capacity)),
}
}
#[inline]
pub fn push(&self, entry: ConsoleLogRecord) {
self.buffer.write().push_overwrite(entry);
}
pub fn iter(&self) -> ConsoleLogIter {
let lock = self.buffer.read();
ConsoleLogIter {
index_front: 0,
index_back: lock.occupied_len(),
inner: lock,
}
}
}
impl Default for ConsoleLog {
#[inline]
fn default() -> Self {
Self::new(1000)
}
}
pub struct ConsoleLogIter<'a> {
index_front: usize,
index_back: usize,
inner: RwLockReadGuard<'a, HeapRb<ConsoleLogRecord>>,
}
impl ConsoleLogIter<'_> {
fn get(&self, index: usize) -> Option<ConsoleLogRecord> {
let (s_first, s_second) = self.inner.as_slices();
let s_first_len = s_first.len();
if index < s_first_len {
Some(s_first[index].clone())
} else if (index - s_first_len) < s_second.len() {
Some(s_second[index - s_first_len].clone())
} else {
None
}
}
}
impl Iterator for ConsoleLogIter<'_> {
type Item = ConsoleLogRecord;
fn next(&mut self) -> Option<Self::Item> {
if self.index_front >= self.index_back {
return None
}
let val = self.get(self.index_front);
self.index_front += 1;
val
}
fn size_hint(&self) -> (usize, Option<usize>) {
let size = self.index_back - self.index_front;
(size, Some(size))
}
}
impl DoubleEndedIterator for ConsoleLogIter<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.index_back <= self.index_front {
return None
}
self.index_back -= 1;
self.get(self.index_back)
}
}
impl ExactSizeIterator for ConsoleLogIter<'_> {}
pub struct ConsoleLogLayer {
log: Arc<ConsoleLog>,
}
impl ConsoleLogLayer {
pub fn new(log: &Arc<ConsoleLog>) -> Self {
Self {
log: log.clone(),
}
}
}
impl<S: Subscriber> Layer<S> for ConsoleLogLayer {
fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
self.log.push(ConsoleLogRecord::for_event(event));
}
}