use core::sync::atomic::Ordering;
use core::{cmp, ptr};
#[cfg(not(feature = "portable-atomic"))]
use core::sync::atomic::AtomicUsize;
#[cfg(feature = "portable-atomic")]
use portable_atomic::AtomicUsize;
pub unsafe trait Delogger: log::Log + crate::TryLog + State<&'static AtomicUsize> {
fn buffer(&self) -> &'static mut [u8];
fn claimed(&self) -> &'static AtomicUsize;
fn flush(&self, logs: &str);
fn render(&self, record: &log::Record) -> &'static [u8];
fn capacity(&self) -> usize {
self.buffer().len()
}
}
pub trait State<T> {
fn attempts(&self) -> T;
fn successes(&self) -> T;
fn flushes(&self) -> T;
fn read(&self) -> T;
fn written(&self) -> T;
}
#[derive(Clone, Copy, Debug)]
pub struct Statistics {
pub attempts: usize,
pub successes: usize,
pub flushes: usize,
pub read: usize,
pub written: usize,
}
pub trait TryLog: log::Log {
fn try_log(&self, _: &log::Record) -> core::result::Result<(), ()>;
}
pub trait TryLogWithStatistics: TryLog + State<usize> {
fn statistics(&self) -> Statistics {
Statistics {
attempts: self.attempts(),
successes: self.successes(),
flushes: self.flushes(),
read: self.read(),
written: self.written(),
}
}
}
#[cfg(not(any(
feature = "max_level_off",
all(not(debug_assertions), feature = "release_max_level_off")
)))]
#[macro_export]
macro_rules! delog {
($logger:ident, $capacity:expr, $render_capacity:expr, $flusher:ty) => {
delog!(
$logger,
$capacity,
$render_capacity,
$flusher,
renderer: $crate::render::DefaultRenderer
);
impl $logger {
#[inline]
pub fn init_default(
level: $crate::log::LevelFilter,
flusher: &'static $flusher,
) -> Result<(), ()> {
$logger::init(level, flusher, $crate::render::default())
}
}
};
($logger:ident, $capacity:expr, $flusher:ty) => {
delog!(
$logger,
$capacity,
$capacity,
$flusher,
renderer: $crate::render::DefaultRenderer
);
impl $logger {
#[inline]
pub fn init_default(
level: $crate::log::LevelFilter,
flusher: &'static $flusher,
) -> Result<(), ()> {
$logger::init(level, flusher, $crate::render::default())
}
}
};
($logger:ident, $capacity:expr, $flusher:ty, renderer: $renderer:ty) => {
$crate::delog!($logger, $capacity, $capacity, $flusher, renderer: $renderer);
};
($logger:ident, $capacity:expr, $render_capacity:expr, $flusher:ty, renderer: $renderer:ty) => {
#[derive(Clone, Copy)]
pub struct $logger {
flusher: &'static $flusher,
renderer: &'static $renderer,
}
unsafe impl Send for $logger {}
unsafe impl Sync for $logger {}
impl $crate::log::Log for $logger {
fn enabled(&self, _: &$crate::log::Metadata) -> bool {
true
}
fn flush(&self) {
let mut buf = [0u8; $capacity];
let logs: &str = unsafe { $crate::dequeue(*self, &mut buf) };
if logs.len() > 0 {
use $crate::Flusher;
self.flusher.flush(logs);
}
}
fn log(&self, record: &$crate::log::Record) {
unsafe { $crate::enqueue(*self, record) }
}
}
impl $crate::TryLog for $logger {
fn try_log(&self, record: &$crate::log::Record) -> core::result::Result<(), ()> {
unsafe { $crate::try_enqueue(*self, record) }
}
}
impl $crate::State<usize> for $logger {
fn attempts(&self) -> usize {
<dyn $crate::Delogger>::attempts(self).load(core::sync::atomic::Ordering::SeqCst)
}
fn successes(&self) -> usize {
<dyn $crate::Delogger>::successes(self).load(core::sync::atomic::Ordering::SeqCst)
}
fn flushes(&self) -> usize {
<dyn $crate::Delogger>::flushes(self).load(core::sync::atomic::Ordering::SeqCst)
}
fn read(&self) -> usize {
<dyn $crate::Delogger>::read(self).load(core::sync::atomic::Ordering::SeqCst)
}
fn written(&self) -> usize {
<dyn $crate::Delogger>::written(self).load(core::sync::atomic::Ordering::SeqCst)
}
}
impl $crate::TryLogWithStatistics for $logger {}
#[allow(missing_docs)]
impl $logger {
#[inline]
pub fn init(
level: $crate::log::LevelFilter,
flusher: &'static $flusher,
renderer: &'static $renderer,
) -> Result<(), ()> {
use core::sync::atomic::{AtomicBool, Ordering};
static INITIALIZED: AtomicBool = AtomicBool::new(false);
if INITIALIZED
.compare_exchange_weak(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
let logger = Self { flusher, renderer };
Self::get().replace(logger);
$crate::logger().replace(Self::get().as_ref().unwrap());
$crate::log::set_logger(Self::get().as_ref().unwrap())
.map(|()| $crate::log::set_max_level(level))
.map_err(|_| ())
} else {
Err(())
}
}
fn get() -> &'static mut Option<$logger> {
static mut LOGGER: Option<$logger> = None;
unsafe { &mut LOGGER }
}
pub fn flush() {
if let Some(logger) = Self::get() {
$crate::log::Log::flush(logger)
}
}
}
impl $crate::State<&'static core::sync::atomic::AtomicUsize> for $logger {
fn attempts(&self) -> &'static core::sync::atomic::AtomicUsize {
use core::sync::atomic::AtomicUsize;
static LOG_ATTEMPT_COUNT: AtomicUsize = AtomicUsize::new(0);
&LOG_ATTEMPT_COUNT
}
fn successes(&self) -> &'static core::sync::atomic::AtomicUsize {
use core::sync::atomic::AtomicUsize;
static LOG_SUCCESS_COUNT: AtomicUsize = AtomicUsize::new(0);
&LOG_SUCCESS_COUNT
}
fn flushes(&self) -> &'static core::sync::atomic::AtomicUsize {
use core::sync::atomic::AtomicUsize;
static LOG_FLUSH_COUNT: AtomicUsize = AtomicUsize::new(0);
&LOG_FLUSH_COUNT
}
fn read(&self) -> &'static core::sync::atomic::AtomicUsize {
use core::sync::atomic::AtomicUsize;
static READ: AtomicUsize = AtomicUsize::new(0);
&READ
}
fn written(&self) -> &'static core::sync::atomic::AtomicUsize {
use core::sync::atomic::AtomicUsize;
static WRITTEN: AtomicUsize = AtomicUsize::new(0);
&WRITTEN
}
}
unsafe impl $crate::Delogger for $logger {
fn buffer(&self) -> &'static mut [u8] {
static mut BUFFER: [u8; $capacity] = [0u8; $capacity];
unsafe { &mut BUFFER }
}
fn flush(&self, logs: &str) {
use $crate::Flusher;
self.flusher.flush(logs)
}
fn claimed(&self) -> &'static core::sync::atomic::AtomicUsize {
use core::sync::atomic::AtomicUsize;
static CLAIMED: AtomicUsize = AtomicUsize::new(0);
&CLAIMED
}
fn render(&self, record: &$crate::Record) -> &'static [u8] {
static mut LOCAL_BUFFER: [u8; $render_capacity] = [0u8; $render_capacity];
let local_buffer = unsafe { &mut LOCAL_BUFFER };
use $crate::Renderer;
self.renderer.render(local_buffer, record)
}
}
};
}
#[cfg(any(
feature = "max_level_off",
all(not(debug_assertions), feature = "release_max_level_off")
))]
#[macro_export]
macro_rules! delog {
($logger:ident, $capacity:expr, $flusher:ty) => {
delog!(
$logger,
$capacity,
$flusher,
renderer: $crate::render::DefaultRenderer
);
impl $logger {
#[inline]
pub fn init_default(
level: $crate::log::LevelFilter,
flusher: &'static $flusher,
) -> Result<(), ()> {
Ok(())
}
}
};
($logger:ident, $capacity:expr, $flusher:ty, renderer: $renderer:ty) => {
#[derive(Clone, Copy)]
pub struct $logger {}
// log::Log implementations are required to be Send + Sync
unsafe impl Send for $logger {}
unsafe impl Sync for $logger {}
impl $crate::log::Log for $logger {
fn enabled(&self, _: &$crate::log::Metadata) -> bool {
true
}
fn flush(&self) {}
fn log(&self, _record: &$crate::log::Record) {}
}
impl $crate::TryLog for $logger {
fn try_log(&self, record: &$crate::log::Record) -> core::result::Result<(), ()> {
Ok(())
}
}
impl $crate::State<usize> for $logger {
fn attempts(&self) -> usize {
0
}
fn successes(&self) -> usize {
0
}
fn flushes(&self) -> usize {
0
}
fn read(&self) -> usize {
0
}
fn written(&self) -> usize {
0
}
}
impl $crate::TryLogWithStatistics for $logger {}
#[allow(missing_docs)]
impl $logger {
#[inline]
pub fn init(
level: $crate::log::LevelFilter,
flusher: &'static $flusher,
renderer: &'static $renderer,
) -> Result<(), ()> {
Ok(())
}
fn get() -> &'static mut Option<$logger> {
static mut LOGGER: Option<$logger> = None;
unsafe { &mut LOGGER }
}
pub fn flush() {}
}
impl $crate::State<&'static core::sync::atomic::AtomicUsize> for $logger {
fn attempts(&self) -> &'static core::sync::atomic::AtomicUsize {
use core::sync::atomic::AtomicUsize;
static LOG_ATTEMPT_COUNT: AtomicUsize = AtomicUsize::new(0);
&LOG_ATTEMPT_COUNT
}
fn successes(&self) -> &'static core::sync::atomic::AtomicUsize {
use core::sync::atomic::AtomicUsize;
static LOG_SUCCESS_COUNT: AtomicUsize = AtomicUsize::new(0);
&LOG_SUCCESS_COUNT
}
fn flushes(&self) -> &'static core::sync::atomic::AtomicUsize {
use core::sync::atomic::AtomicUsize;
static LOG_FLUSH_COUNT: AtomicUsize = AtomicUsize::new(0);
&LOG_FLUSH_COUNT
}
fn read(&self) -> &'static core::sync::atomic::AtomicUsize {
use core::sync::atomic::AtomicUsize;
static READ: AtomicUsize = AtomicUsize::new(0);
&READ
}
fn written(&self) -> &'static core::sync::atomic::AtomicUsize {
use core::sync::atomic::AtomicUsize;
static WRITTEN: AtomicUsize = AtomicUsize::new(0);
&WRITTEN
}
}
unsafe impl $crate::Delogger for $logger {
fn buffer(&self) -> &'static mut [u8] {
unsafe { &mut [] }
}
fn flush(&self, logs: &str) {}
fn claimed(&self) -> &'static core::sync::atomic::AtomicUsize {
use core::sync::atomic::AtomicUsize;
static CLAIMED: AtomicUsize = AtomicUsize::new(0);
&CLAIMED
}
fn render(&self, record: &$crate::Record) -> &'static [u8] {
&[]
}
}
};
}
pub unsafe fn enqueue(delogger: impl Delogger, record: &log::Record) {
crate::logger::try_enqueue(delogger, record).ok();
}
#[allow(unused_unsafe, unused_variables)]
pub unsafe fn try_enqueue(
delogger: impl Delogger,
record: &log::Record,
) -> core::result::Result<(), ()> {
#[cfg(any(
feature = "max_level_off",
all(not(debug_assertions), feature = "release_max_level_off")
))]
{
return Ok(());
}
#[cfg(not(any(
feature = "max_level_off",
all(not(debug_assertions), feature = "release_max_level_off")
)))]
{
if record.level() > crate::log::max_level() {
return Ok(());
}
delogger.attempts().fetch_add(1, Ordering::SeqCst);
if record.target() == "!" {
let input = delogger.render(record);
let input = unsafe { core::str::from_utf8_unchecked(input) };
Delogger::flush(&delogger, input);
delogger.successes().fetch_add(1, Ordering::SeqCst);
return Ok(());
}
let capacity = delogger.capacity();
let log = delogger.render(record);
let size = log.len();
let previously_claimed = loop {
let read = delogger.read().load(Ordering::SeqCst);
let claimed = delogger.claimed().load(Ordering::SeqCst);
if claimed + size > read + capacity {
return Err(());
}
let previous = delogger.claimed().compare_exchange(
claimed,
claimed + size,
Ordering::SeqCst,
Ordering::SeqCst,
);
if previous == Ok(claimed) {
break claimed;
}
};
let written = delogger.written().load(Ordering::SeqCst);
let first: bool = written == previously_claimed;
let destination = previously_claimed % capacity;
let buffer = delogger.buffer();
if destination + size < capacity {
unsafe {
ptr::copy_nonoverlapping(log.as_ptr(), buffer.as_mut_ptr().add(destination), size)
};
} else {
let split = capacity - destination;
unsafe {
ptr::copy_nonoverlapping(log.as_ptr(), buffer.as_mut_ptr().add(destination), split);
ptr::copy_nonoverlapping(
log.as_ptr().add(split),
buffer.as_mut_ptr(),
size - split,
);
}
}
if first {
loop {
let claimed = delogger.claimed().load(Ordering::SeqCst);
delogger.written().store(claimed, Ordering::SeqCst);
if claimed == delogger.claimed().load(Ordering::SeqCst) {
break;
}
}
}
delogger.successes().fetch_add(1, Ordering::SeqCst);
Ok(())
}
}
#[allow(unused_unsafe)]
pub unsafe fn dequeue(delogger: impl Delogger, buf: &mut [u8]) -> &str {
delogger.flushes().fetch_add(1, Ordering::SeqCst);
unsafe { core::str::from_utf8_unchecked(drain_as_bytes(delogger, buf)) }
}
fn drain_as_bytes(delogger: impl Delogger, buf: &mut [u8]) -> &[u8] {
unsafe {
let read = delogger.read().load(Ordering::SeqCst);
let written = delogger.written().load(Ordering::SeqCst);
let p = delogger.buffer().as_ptr();
let capacity = delogger.buffer().len();
if capacity == 0 {
return &[];
}
if written > read {
let available = cmp::min(buf.len(), written.wrapping_sub(read));
let r = read % capacity;
if r + available > capacity {
let mid = capacity - r;
ptr::copy_nonoverlapping(p.add(r), buf.as_mut_ptr(), mid);
ptr::copy_nonoverlapping(p, buf.as_mut_ptr().add(mid), available - mid);
} else {
ptr::copy_nonoverlapping(p.add(r), buf.as_mut_ptr(), available);
}
delogger
.read()
.store(read.wrapping_add(available), Ordering::SeqCst);
buf.get_unchecked(..available)
} else {
&[]
}
}
}