use std::{
panic::PanicHookInfo,
sync::{
Mutex,
atomic::{AtomicUsize, Ordering},
},
};
pub use log::{Level, Metadata};
pub use self::macros::*;
use crate::{hook, text::Text};
mod macros {
#[doc(inline)]
pub use crate::{debug, error, info, warn};
#[macro_export]
macro_rules! error {
(($($arg:tt)+), $location:expr $(,)?) => {
$crate::__log__!(
$crate::context::Level::Error,
$location,
$($arg)+
)
};
($($arg:tt)+) => {
$crate::__log__!(
$crate::context::Level::Error,
$crate::context::Location::from_panic_location(::std::panic::Location::caller()),
$($arg)+
)
}
}
#[macro_export]
macro_rules! warn {
(($($arg:tt)+), $location:expr $(,)?) => {
$crate::__log__!(
$crate::context::Level::Warn,
$location,
$($arg)+
)
};
($($arg:tt)+) => {
$crate::__log__!(
$crate::context::Level::Warn,
$crate::context::Location::from_panic_location(::std::panic::Location::caller()),
$($arg)+
)
}
}
#[macro_export]
macro_rules! info {
(($($arg:tt)+), $location:expr $(,)?) => {
$crate::__log__!(
$crate::context::Level::Info,
$location,
$($arg)+
)
};
($($arg:tt)+) => {
$crate::__log__!(
$crate::context::Level::Info,
$crate::context::Location::from_panic_location(::std::panic::Location::caller()),
$($arg)+
)
}
}
#[macro_export]
macro_rules! debug {
(($($arg:tt)+), $location:expr $(,)?) => {
$crate::__log__!(
$crate::context::Level::Debug,
$location,
$($arg)+
)
};
($($arg:tt)+) => {
$crate::__log__!(
$crate::context::Level::Debug,
$crate::context::Location::from_panic_location(::std::panic::Location::caller()),
$($arg)+
)
}
}
}
static LOGS: Logs = {
static LIST: Mutex<Vec<Record>> = Mutex::new(Vec::new());
static CUR_STATE: AtomicUsize = AtomicUsize::new(1);
Logs {
list: &LIST,
cur_state: &CUR_STATE,
read_state: AtomicUsize::new(0),
}
};
pub fn logs() -> Logs {
LOGS.clone()
}
#[derive(Debug)]
pub struct Logs {
list: &'static Mutex<Vec<Record>>,
cur_state: &'static AtomicUsize,
read_state: AtomicUsize,
}
impl Clone for Logs {
fn clone(&self) -> Self {
Self {
list: self.list,
cur_state: self.cur_state,
read_state: AtomicUsize::new(self.cur_state.load(Ordering::Relaxed) - 1),
}
}
}
impl Logs {
pub fn get<I>(&self, i: I) -> Option<<I::Output as ToOwned>::Owned>
where
I: std::slice::SliceIndex<[Record]>,
I::Output: ToOwned,
{
self.read_state
.store(self.cur_state.load(Ordering::Relaxed), Ordering::Relaxed);
self.list.lock().unwrap().get(i).map(ToOwned::to_owned)
}
pub fn last(&self) -> Option<(usize, Record)> {
self.read_state
.store(self.cur_state.load(Ordering::Relaxed), Ordering::Relaxed);
let list = self.list.lock().unwrap();
list.last().cloned().map(|last| (list.len() - 1, last))
}
pub fn last_with_levels(&self, levels: &[Level]) -> Option<(usize, Record)> {
self.read_state
.store(self.cur_state.load(Ordering::Relaxed), Ordering::Relaxed);
self.list
.lock()
.unwrap()
.iter()
.enumerate()
.rev()
.find_map(|(i, rec)| levels.contains(&rec.level()).then(|| (i, rec.clone())))
}
pub fn has_changed(&self) -> bool {
self.cur_state.load(Ordering::Relaxed) > self.read_state.load(Ordering::Relaxed)
}
#[track_caller]
pub(crate) fn push_cmd_result(&self, result: Result<Option<Text>, Text>) {
let is_ok = result.is_ok();
let (Ok(Some(res)) | Err(res)) = result else {
return;
};
self.cur_state.fetch_add(1, Ordering::Relaxed);
let rec = Record {
metadata: log::MetadataBuilder::new()
.level(if is_ok { Level::Info } else { Level::Error })
.build(),
text: Box::leak(Box::new(res)),
location: Location::from_panic_location(std::panic::Location::caller()),
};
crate::context::queue(|pa| {
hook::trigger(pa, hook::MsgLogged(rec.clone()));
self.list.lock().unwrap().push(rec);
});
}
#[doc(hidden)]
pub fn push_record(&self, rec: Record) {
crate::context::queue(|pa| {
hook::trigger(pa, hook::MsgLogged(rec.clone()));
self.list.lock().unwrap().push(rec);
});
self.cur_state.fetch_add(1, Ordering::Relaxed);
}
pub fn len(&self) -> usize {
self.list.lock().unwrap().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl log::Log for Logs {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() > log::Level::Debug
}
#[track_caller]
fn log(&self, rec: &log::Record) {
let rec = Record {
text: Box::leak(Box::new(Text::from(std::fmt::format(*rec.args())))),
metadata: log::MetadataBuilder::new()
.level(rec.level())
.target(rec.target().to_string().leak())
.build(),
location: Location::from_panic_location(std::panic::Location::caller()),
};
self.cur_state.fetch_add(1, Ordering::Relaxed);
self.list.lock().unwrap().push(rec)
}
fn flush(&self) {}
}
#[derive(Clone, Debug)]
pub struct Record {
text: &'static Text,
metadata: log::Metadata<'static>,
location: Location,
}
impl Record {
#[doc(hidden)]
pub fn new(text: Text, level: Level, location: Location) -> Self {
Self {
text: Box::leak(Box::new(text)),
metadata: log::MetadataBuilder::new().level(level).build(),
location,
}
}
#[inline]
pub fn text(&self) -> &Text {
self.text
}
#[inline]
pub fn metadata(&self) -> log::Metadata<'static> {
self.metadata.clone()
}
#[inline]
pub fn level(&self) -> Level {
self.metadata.level()
}
#[inline]
pub fn target(&self) -> &'static str {
self.metadata.target()
}
#[inline]
pub fn location(&self) -> Location {
self.location
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Location {
filename: &'static str,
line: u32,
col: u32,
}
impl Location {
pub fn new(filename: impl ToString, line: u32, col: u32) -> Self {
Self {
filename: filename.to_string().leak(),
line,
col,
}
}
pub fn from_panic_location(loc: &std::panic::Location) -> Self {
Self {
filename: loc.file().to_string().leak(),
line: loc.line(),
col: loc.column(),
}
}
#[must_use]
pub const fn file(&self) -> &'static str {
self.filename
}
#[must_use]
pub const fn line(&self) -> usize {
self.line as usize
}
#[must_use]
pub const fn column(&self) -> usize {
self.col as usize
}
}
impl std::fmt::Display for Location {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}:{}", self.file(), self.line, self.col)
}
}
#[doc(hidden)]
pub fn log_panic(panic_info: &PanicHookInfo) {
let (Some(msg), Some(location)) = (panic_info.payload_as_str(), panic_info.location()) else {
return;
};
let rec = Record {
text: Box::leak(Box::new(Text::from(msg))),
metadata: Metadata::builder().level(Level::Error).build(),
location: Location::from_panic_location(location),
};
crate::context::queue(|pa| {
hook::trigger(pa, hook::MsgLogged(rec.clone()));
LOGS.list.lock().unwrap().push(rec);
});
}