use crate::sync::{Channel, Once, Receiver, Sender};
use crate::sync::atomic::{AtomicU32, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Level {
Trace,
Debug,
Info,
Warn,
Error,
}
impl Level {
fn as_str(self) -> &'static str {
match self {
Level::Trace => "TRACE",
Level::Debug => "DEBUG",
Level::Info => "INFO",
Level::Warn => "WARN",
Level::Error => "ERROR",
}
}
}
#[derive(Clone, Copy)]
pub enum LogArg {
None,
U32(u32),
I32(i32),
F32(f32),
Str(&'static str),
}
impl From<u32> for LogArg {
fn from(v: u32) -> Self {
LogArg::U32(v)
}
}
impl From<i32> for LogArg {
fn from(v: i32) -> Self {
LogArg::I32(v)
}
}
impl From<f32> for LogArg {
fn from(v: f32) -> Self {
LogArg::F32(v)
}
}
impl From<&'static str> for LogArg {
fn from(v: &'static str) -> Self {
LogArg::Str(v)
}
}
#[derive(Clone, Copy)]
pub struct LogFrame {
pub level: Level,
pub task_id: Option<u16>,
pub timestamp_us: u32,
pub msg: &'static str,
pub args: [LogArg; 2],
}
const CAPACITY: usize = 16;
#[cfg(not(loom))]
static CHANNEL: Channel<LogFrame, CAPACITY> = Channel::new();
#[cfg(loom)]
loom::lazy_static! {
static ref CHANNEL: Channel<LogFrame, CAPACITY> = Channel::new();
}
#[cfg(not(loom))]
static SENDER: Once<Sender<'static, LogFrame, CAPACITY>> = Once::new();
#[cfg(loom)]
loom::lazy_static! {
static ref SENDER: Once<Sender<'static, LogFrame, CAPACITY>> = Once::new();
}
#[cfg(not(loom))]
static RECEIVER: Once<Receiver<'static, LogFrame, CAPACITY>> = Once::new();
#[cfg(loom)]
loom::lazy_static! {
static ref RECEIVER: Once<Receiver<'static, LogFrame, CAPACITY>> = Once::new();
}
#[cfg(not(loom))]
static DROPPED: AtomicU32 = AtomicU32::new(0);
#[cfg(loom)]
loom::lazy_static! {
static ref DROPPED: AtomicU32 = AtomicU32::new(0);
}
pub(crate) fn init() {
if let Some((tx, rx)) = CHANNEL.split() {
let _ = SENDER.set(tx);
let _ = RECEIVER.set(rx);
}
}
#[doc(hidden)]
pub fn push(level: Level, msg: &'static str, arg0: LogArg, arg1: LogArg) {
let task_id = crate::preempt::sched::current().map(|id| id as u16);
let timestamp_us = crate::port::board::now_us() as u32;
let frame = LogFrame {
level,
task_id,
timestamp_us,
msg,
args: [arg0, arg1],
};
let sent = match SENDER.get() {
Some(tx) => crate::critical::enter(|| tx.try_send(frame)).is_ok(),
None => false,
};
if !sent {
DROPPED.fetch_add(1, Ordering::Relaxed);
}
}
pub fn dropped_frames() -> usize {
DROPPED.load(Ordering::Relaxed) as usize
}
fn write_frame(frame: &LogFrame) {
crate::console::write_str("[");
crate::console::write_str(frame.level.as_str());
crate::console::write_str("] t=");
write_dec(frame.timestamp_us as usize);
if let Some(id) = frame.task_id {
crate::console::write_str(" task=");
write_dec(id as usize);
}
crate::console::write_str(" ");
write_interpolated(frame.msg, &frame.args);
crate::console::write_str("\n");
}
fn write_interpolated(msg: &str, args: &[LogArg; 2]) {
let mut rest = msg;
let mut arg_idx = 0usize;
while let Some(pos) = rest.find("{}") {
crate::console::write_str(&rest[..pos]);
match args.get(arg_idx) {
Some(LogArg::None) | None => crate::console::write_str("{}"),
Some(arg) => write_arg(arg),
}
arg_idx += 1;
rest = &rest[pos + 2..];
}
crate::console::write_str(rest);
}
fn write_arg(arg: &LogArg) {
match *arg {
LogArg::None => {}
LogArg::U32(v) => crate::console::_print(core::format_args!("{v}")),
LogArg::I32(v) => crate::console::_print(core::format_args!("{v}")),
LogArg::F32(v) => crate::console::_print(core::format_args!("{v}")),
LogArg::Str(s) => crate::console::write_str(s),
}
}
fn write_dec(mut n: usize) {
if n == 0 {
crate::console::write_str("0");
return;
}
let mut digits = [0u8; 20];
let mut i = 0;
while n > 0 {
digits[i] = b'0' + (n % 10) as u8;
n /= 10;
i += 1;
}
let mut buf = [0u8; 20];
for j in 0..i {
buf[j] = digits[i - 1 - j];
}
if let Ok(s) = core::str::from_utf8(&buf[..i]) {
crate::console::write_str(s);
}
}
pub fn drain_one() -> bool {
match RECEIVER.get().and_then(|rx| rx.try_recv()) {
Some(frame) => {
write_frame(&frame);
true
}
None => false,
}
}
pub async fn drain_forever() -> ! {
let rx = loop {
if let Some(rx) = RECEIVER.get() {
break rx;
}
crate::time::Sleep::<1000>::new().await;
};
loop {
let frame = rx.recv().await;
write_frame(&frame);
}
}
#[macro_export]
macro_rules! log {
($level:expr, $msg:expr) => {
$crate::log::push(
$level,
$msg,
$crate::log::LogArg::None,
$crate::log::LogArg::None,
)
};
($level:expr, $msg:expr, $a:expr) => {
$crate::log::push(
$level,
$msg,
$crate::log::LogArg::from($a),
$crate::log::LogArg::None,
)
};
($level:expr, $msg:expr, $a:expr, $b:expr) => {
$crate::log::push(
$level,
$msg,
$crate::log::LogArg::from($a),
$crate::log::LogArg::from($b),
)
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn log_arg_from_conversions() {
assert!(matches!(LogArg::from(5u32), LogArg::U32(5)));
assert!(matches!(LogArg::from(-3i32), LogArg::I32(-3)));
assert!(matches!(LogArg::from("hi"), LogArg::Str("hi")));
match LogArg::from(1.5f32) {
LogArg::F32(v) => assert!((v - 1.5).abs() < f32::EPSILON),
_ => panic!("expected F32"),
}
}
#[test]
fn write_interpolated_boundary_cases_do_not_panic() {
write_interpolated("no placeholders", &[LogArg::None, LogArg::None]);
write_interpolated("one {}", &[LogArg::U32(1), LogArg::None]);
write_interpolated("two {} and {}", &[LogArg::U32(1), LogArg::Str("x")]);
write_interpolated("three {} {} {}", &[LogArg::U32(1), LogArg::U32(2)]);
}
}