use std::sync::{
atomic::{AtomicU16, Ordering},
Mutex,
};
use std::time::SystemTime;
use serde::ser::{Serialize, SerializeMap, Serializer};
const INITIAL_VEC_CAPACITY: usize = 256;
#[derive(Debug, PartialEq, Eq, Clone)]
pub(crate) struct LogEntry {
time: SystemTime,
level: log::Level,
file: String,
msg: String,
}
impl Default for LogEntry {
fn default() -> Self {
Self {
time: SystemTime::UNIX_EPOCH,
level: log::Level::Debug,
file: String::new(),
msg: String::new(),
}
}
}
impl Serialize for LogEntry {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(4))?;
map.serialize_entry(
"time",
&format!(
"{}",
self.time
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
),
)?;
map.serialize_entry("level", self.level.as_str())?;
map.serialize_entry("file", self.file.as_str())?;
map.serialize_entry("msg", self.msg.as_str())?;
map.end()
}
}
impl From<&log::Record<'_>> for LogEntry {
fn from(r: &log::Record<'_>) -> Self {
Self {
time: SystemTime::now(),
level: r.level(),
file: format!(
"{}:{}:{}",
r.module_path().unwrap_or_default(),
r.file().unwrap_or_default(),
r.line().unwrap_or_default()
),
msg: r.args().to_string(),
}
}
}
#[derive(Default, Debug)]
pub(crate) struct MemoryLogger {
consumer_count: AtomicU16,
logs: Mutex<Vec<LogEntry>>,
}
impl log::Log for MemoryLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.target().starts_with("nmstate::")
|| metadata.target().starts_with("nispor::")
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
let mut logs = self.logs.lock().expect("inner lock poisoned");
logs.push(LogEntry::from(record))
}
}
fn flush(&self) {}
}
impl MemoryLogger {
pub(crate) fn new() -> Self {
Self {
consumer_count: AtomicU16::new(0),
logs: Mutex::new(Vec::with_capacity(INITIAL_VEC_CAPACITY)),
}
}
pub(crate) fn add_consumer(&self) {
self.consumer_count.fetch_add(1, Ordering::SeqCst);
}
pub(crate) fn drain(&self, since: SystemTime) -> String {
let mut logs = self.logs.lock().expect("inner lock poisoned");
let ret = serde_json::to_string(
&logs
.as_slice()
.iter()
.filter(|l| l.time >= since)
.collect::<Vec<&LogEntry>>(),
)
.unwrap_or_default();
if self.consumer_count.fetch_sub(1, Ordering::SeqCst) == 1 {
logs.clear();
}
ret
}
}