use std::sync::atomic::{AtomicU32, Ordering};
const LINE_MAX: usize = 512;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum Level {
Quiet,
Normal,
Debug,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Tag {
Master,
Worker(u32),
}
static LEVEL: AtomicU32 = AtomicU32::new(Level::Quiet as u32);
static TAG: AtomicU32 = AtomicU32::new(0);
pub fn init(level: Level, tag: Tag) {
LEVEL.store(level as u32, Ordering::Relaxed);
set_tag(tag);
}
pub fn set_tag(tag: Tag) {
let packed = match tag {
Tag::Master => 1,
Tag::Worker(w) => w + 2,
};
TAG.store(packed, Ordering::Relaxed);
}
fn tag() -> Option<Tag> {
match TAG.load(Ordering::Relaxed) {
0 => None,
1 => Some(Tag::Master),
n => Some(Tag::Worker(n - 2)),
}
}
#[inline(always)]
pub fn enabled(level: Level) -> bool {
LEVEL.load(Ordering::Relaxed) >= level as u32
}
#[cold]
pub fn emit(level_tag: &str, args: core::fmt::Arguments<'_>) {
let mut buf = [0u8; LINE_MAX];
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default();
let len = format_line(&mut buf, now, tag(), level_tag, args);
unsafe {
libc::write(2, buf.as_ptr() as *const libc::c_void, len);
}
}
struct TruncatingWriter<'a> {
buf: &'a mut [u8],
pos: usize,
}
impl core::fmt::Write for TruncatingWriter<'_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let n = s.len().min(self.buf.len() - self.pos);
self.buf[self.pos..self.pos + n].copy_from_slice(&s.as_bytes()[..n]);
self.pos += n;
Ok(())
}
}
fn format_line(
buf: &mut [u8; LINE_MAX],
now: std::time::Duration,
tag: Option<Tag>,
level_tag: &str,
args: core::fmt::Arguments<'_>,
) -> usize {
use core::fmt::Write;
let mut w = TruncatingWriter { buf: &mut buf[..LINE_MAX - 1], pos: 0 };
let _ = write!(w, "{}.{:03} ", now.as_secs(), now.subsec_millis());
let _ = match tag {
None => Ok(()),
Some(Tag::Master) => w.write_str("M "),
Some(Tag::Worker(n)) => write!(w, "W{n} "),
};
let _ = write!(w, "{level_tag} ");
let _ = w.write_fmt(args);
let pos = w.pos;
buf[pos] = b'\n';
pos + 1
}
#[macro_export]
macro_rules! gnitz_error {
($($arg:tt)*) => {{
$crate::log::emit("ERROR", format_args!($($arg)*));
}};
}
#[macro_export]
macro_rules! gnitz_warn {
($($arg:tt)*) => {{
$crate::log::emit("WARN", format_args!($($arg)*));
}};
}
#[macro_export]
macro_rules! gnitz_note {
($($arg:tt)*) => {{
$crate::log::emit("NOTE", format_args!($($arg)*));
}};
}
#[macro_export]
macro_rules! gnitz_info {
($($arg:tt)*) => {{
if $crate::log::enabled($crate::log::Level::Normal) {
$crate::log::emit("INFO", format_args!($($arg)*));
}
}};
}
#[macro_export]
macro_rules! gnitz_debug {
($($arg:tt)*) => {{
if $crate::log::enabled($crate::log::Level::Debug) {
$crate::log::emit("DEBUG", format_args!($($arg)*));
}
}};
}
#[cfg(test)]
#[path = "tests/log.rs"]
mod tests;