use crate::{Slice, Str, is, lets, whilst};
macro_rules! guard {
($self:ident enter $msg:literal) => {
is![$self.guard != 0, panic!($msg)];
$self.guard = 1;
};
($self:ident leave) => {
$self.guard = 0;
};
}
#[doc = crate::_tags!(log)]
#[doc = crate::_doc_location!("sys/log")]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct LoggerStatic<const CAP: usize, const MSG_LEN: usize> {
buf: [[u8; MSG_LEN]; CAP],
lens: [usize; CAP],
leftover: [usize; CAP], len: usize,
guard: i8, }
impl<const CAP: usize, const MSG_LEN: usize> Default for LoggerStatic<CAP, MSG_LEN> {
fn default() -> Self {
Self::new()
}
}
impl<const CAP: usize, const MSG_LEN: usize> LoggerStatic<CAP, MSG_LEN> {
#[inline(always)]
pub const fn new() -> Self {
Self {
buf: [[0; MSG_LEN]; CAP],
lens: [0; CAP],
leftover: [0; CAP],
len: 0,
guard: 0,
}
}
#[inline(always)]
pub const fn clear(&mut self) {
guard![self enter "LoggerStatic::clear()"];
self.len = 0;
guard![self leave];
}
#[inline(always)]
pub const fn count(&mut self) -> usize {
guard![self enter "LoggerStatic::len()"];
let len = self.len;
guard![self leave];
len
}
#[must_use]
#[inline(always)]
pub const fn is_full(&mut self) -> bool {
guard![self enter "LoggerStatic::is_full()"];
let full = self.len == CAP;
guard![self leave];
full
}
pub const fn log_bytes(&mut self, msg: &[u8]) {
guard![self enter "LoggerStatic::log_bytes()"];
if self.len < CAP {
let written = Slice::copy_utf8_into(&mut self.buf[self.len], 0, msg);
let remaining = msg.len().saturating_sub(written);
self.lens[self.len] = written;
self.leftover[self.len] = remaining;
self.len += 1;
}
guard![self leave];
}
#[must_use]
pub const fn get(&mut self, i: usize) -> Option<(&str, usize)> {
guard![self enter "LoggerStatic::get()"];
is! { i >= self.len, { guard![self leave]; return None; }}
let len = self.lens[i];
let leftover = self.leftover[i];
let msg = Slice::range_to(&self.buf[i], len);
#[cfg(any(feature = "safe_text", not(feature = "unsafe_str")))]
let s = crate::unwrap![ok Str::from_utf8(msg)];
#[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
let s = unsafe { Str::from_utf8_unchecked(msg) };
guard![self leave];
Some((s, leftover))
}
pub fn for_each<F: FnMut(usize, &str, usize)>(&mut self, mut f: F) {
guard![self enter "LoggerStatic::log_bytes()"];
for (i, msg) in self.buf[..self.len].iter().enumerate() {
let len = self.lens[i];
let leftover = self.leftover[i];
#[cfg(any(feature = "safe_text", not(feature = "unsafe_str")))]
let s = crate::unwrap![ok Str::from_utf8(&msg[..len])];
#[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
let s = unsafe { Str::from_utf8_unchecked(&msg[..len]) };
f(i, s, leftover);
}
guard![self leave];
}
#[must_use]
pub const fn any_truncated(&mut self) -> bool {
guard![self enter "LoggerStatic::any_truncated()"];
whilst! { i in 0..self.len; {
is![self.leftover[i] > 0, { guard![self leave]; return true; }];
}}
guard![self leave];
false
}
#[must_use]
pub const fn truncation_stats(&mut self) -> (usize, usize) {
guard![self enter "LoggerStatic::truncation_stats()"];
lets![mut count = 0, mut total = 0];
whilst! { i in 0..self.len; {
let lost = self.leftover[i];
is! { lost > 0, { count += 1; total += lost; }}
}}
guard![self leave];
(count, total)
}
}
#[doc = crate::_tags!(fake log)]
#[doc = crate::_doc_location!("sys/log")]
#[cfg_attr(not(feature = "__docs_internal"), doc(hidden))]
#[cfg_attr(cargo_primary_package, doc(hidden))]
#[cfg(not(feature = "unsafe_sync"))]
#[macro_export]
macro_rules! slog {
($($tt:tt)*) => {};
}
#[doc = crate::_tags!(log)]
#[doc = crate::_doc_location!("sys/log")]
#[macro_export]
#[cfg_attr(cargo_primary_package, doc(hidden))]
#[cfg_attr(nightly_doc, doc(cfg(feature = "unsafe_sync")))]
#[cfg(feature = "unsafe_sync")]
macro_rules! slog {
(
/* public API*/
// Define a static logger.
$(#[$attrs:meta])*
$vis:vis new $($id:ident :)? $CAP:literal + $LEN:literal) => { $crate::paste! {
$crate::slog![@$(#[$attrs])* $vis new
$($id:)? $CAP+$LEN,
[<__LOGGER_ $($id _)?$CAP _$LEN>], [<__logger_ $($id _)?$CAP _$LEN>], ];
}};
(
// Clear all logs.
clear $($id:ident :)? $CAP:literal + $LEN:literal) => {
$crate::slog!(@get $($id:)? $CAP + $LEN).clear();
};
(
// Return the number of messages.
count $($id:ident :)? $CAP:literal + $LEN:literal) => {
$crate::slog!(@get $($id:)? $CAP + $LEN).count();
};
(
// Whether the logger is full.
is_full $($id:ident :)? $CAP:literal + $LEN:literal) => {
$crate::slog!(@get $($id:)? $CAP + $LEN).is_full();
};
(
// Log message with formatted arguments.
$($id:ident :)? $CAP:literal + $LEN:literal $($fmt:tt)+) => {{
let mut buf = [0u8; $LEN];
let mut pos = 0;
$crate::fmtcat!(buf, pos, $($fmt)+);
let slice = $crate::Slice::range_to(&buf, pos);
$crate::slog!(@get $($id:)? $CAP + $LEN).log_bytes(slice);
}};
(
// Retrieve a specific message by index.
get $($id:ident :)? $CAP:literal + $LEN:literal, $index:expr) => {
$crate::slog!(@get $($id :)? $CAP + $LEN).get($index)
};
(
// Run a closure for each log message.
for_each $($id:ident :)? $CAP:literal + $LEN:literal $closure:expr) => {
$crate::slog!(@get $($id:)? $CAP + $LEN).for_each($closure);
};
(
// Whether if any message was truncated.
any_truncated $($id:ident :)? $CAP:literal + $LEN:literal) => {
$crate::slog!(@get $($id:)? $CAP + $LEN).any_truncated();
};
(
// Returns `(count, total_lost_bytes)` for truncated messages.
truncation_stats $($id:ident :)? $CAP:literal + $LEN:literal) => {
$crate::slog!(@get $($id:)? $CAP + $LEN).truncation_stats();
};
(
static_name $($id:ident :)? $CAP:literal + $LEN:literal) => { $crate::paste! {
stringify!{ $crate::slog![@static_ident $($id :)? $CAP+$LEN] }
}};
(
@$(#[$attrs:meta])*
$vis:vis new $($id:ident :)? $CAP:literal + $LEN:literal,
$static:ident, $fn:ident $(,)?) => { $crate::paste! {
$(#[$attrs])*
#[doc = "\n\nA single-thread global static logger `" $($id ":" )? $CAP "+" $LEN "`."]
#[doc = "slog![for_each " $($id ":" )? $CAP "+" $LEN " |i, s, _| println!(\"[{i}] {s}\")];"]
#[allow(non_upper_case_globals, reason = "case-sensitive $id")]
$vis static mut $static: $crate::LoggerStatic<$CAP, $LEN> = $crate::LoggerStatic::new();
#[doc(hidden)] $(#[$attrs])*
#[doc = "Returns a mutable reference to the global static [`" $static "`]."]
#[inline(always)]
$vis const unsafe fn $fn() -> &'static mut $crate::LoggerStatic<$CAP, $LEN> {
#[allow(static_mut_refs, reason = "accessing the single-thread static logger instance")]
unsafe { &mut $static }
}
}};
(@get $($id:ident :)? $CAP:literal + $LEN:literal) => {{
unsafe { $crate::slog![@fn_ident $($id :)? $CAP+$LEN]() }
}};
(@static_ident $($id:ident :)? $CAP:literal + $LEN:literal) => { $crate::paste! {
[<__LOGGER_ $($id _)? $CAP _ $LEN>]
}};
(@fn_ident $($id:ident :)? $CAP:literal + $LEN:literal) => { $crate::paste! {
[<__logger_ $($id _)? $CAP _ $LEN>]
}};
(@$(#[$attr:meta])*
$vis:vis use static: $($id:ident :)? $CAP:literal+$LEN:literal in $path:path) => {
$crate::paste! { $(#[$attr])* $vis use $path::[<__LOGGER_ $($id _)? $CAP _ $LEN>]; }
};
(@$(#[$attr:meta])*
$vis:vis use fn: $($id:ident :)? $CAP:literal+$LEN:literal in $path:path) => {
$crate::paste! { $(#[$attr])* $vis use $path::[<__logger_ $($id _)? $CAP _ $LEN>]; }
};
}
#[doc(inline)]
pub use slog;