#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
extern crate alloc;
extern crate core;
use bincode::{Decode, Encode};
use cu29_clock::CuTime;
use cu29_value::Value;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
#[cfg(not(feature = "std"))]
mod imp {
pub use core::fmt::Display;
pub use core::fmt::Formatter;
pub use core::fmt::Result as FmtResult;
}
#[cfg(feature = "defmt")]
extern crate defmt;
#[cfg(feature = "std")]
mod imp {
pub use core::fmt::Display;
pub use cu29_traits::CuResult;
pub use std::collections::HashMap;
pub use std::fmt::Formatter;
pub use std::fmt::Result as FmtResult;
pub use strfmt::strfmt;
}
use imp::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum CuLogLevel {
Debug = 0,
Info = 1,
Warning = 2,
Error = 3,
Critical = 4,
}
impl CuLogLevel {
#[inline]
pub const fn enabled(self, max_level: CuLogLevel) -> bool {
self as u8 >= max_level as u8
}
}
#[allow(dead_code)]
pub const ANONYMOUS: u32 = 0;
pub const MAX_LOG_PARAMS_ON_STACK: usize = 10;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CuLogEntry {
pub time: CuTime,
pub level: CuLogLevel,
pub msg_index: u32,
pub paramname_indexes: SmallVec<[u32; MAX_LOG_PARAMS_ON_STACK]>,
pub params: SmallVec<[Value; MAX_LOG_PARAMS_ON_STACK]>,
}
impl Encode for CuLogEntry {
fn encode<E: bincode::enc::Encoder>(
&self,
encoder: &mut E,
) -> Result<(), bincode::error::EncodeError> {
self.time.encode(encoder)?;
(self.level as u8).encode(encoder)?;
self.msg_index.encode(encoder)?;
(self.paramname_indexes.len() as u64).encode(encoder)?;
for &index in &self.paramname_indexes {
index.encode(encoder)?;
}
(self.params.len() as u64).encode(encoder)?;
for param in &self.params {
param.encode(encoder)?;
}
Ok(())
}
}
impl<Context> Decode<Context> for CuLogEntry {
fn decode<D: bincode::de::Decoder>(
decoder: &mut D,
) -> Result<Self, bincode::error::DecodeError> {
let time = CuTime::decode(decoder)?;
let level_raw = u8::decode(decoder)?;
let level = match level_raw {
0 => CuLogLevel::Debug,
1 => CuLogLevel::Info,
2 => CuLogLevel::Warning,
3 => CuLogLevel::Error,
4 => CuLogLevel::Critical,
_ => CuLogLevel::Debug, };
let msg_index = u32::decode(decoder)?;
let paramname_len = u64::decode(decoder)? as usize;
let mut paramname_indexes = SmallVec::with_capacity(paramname_len);
for _ in 0..paramname_len {
paramname_indexes.push(u32::decode(decoder)?);
}
let params_len = u64::decode(decoder)? as usize;
let mut params = SmallVec::with_capacity(params_len);
for _ in 0..params_len {
params.push(Value::decode(decoder)?);
}
Ok(CuLogEntry {
time,
level,
msg_index,
paramname_indexes,
params,
})
}
}
impl Display for CuLogEntry {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(
f,
"CuLogEntry {{ level: {:?}, msg_index: {}, paramname_indexes: {:?}, params: {:?} }}",
self.level, self.msg_index, self.paramname_indexes, self.params
)
}
}
impl CuLogEntry {
pub fn new(msg_index: u32, level: CuLogLevel) -> Self {
CuLogEntry {
time: 0.into(), level,
msg_index,
paramname_indexes: SmallVec::new(),
params: SmallVec::new(),
}
}
pub fn add_param(&mut self, paramname_index: u32, param: Value) {
self.paramname_indexes.push(paramname_index);
self.params.push(param);
}
}
#[inline]
#[cfg(feature = "std")]
pub fn format_logline(
time: CuTime,
level: CuLogLevel,
format_str: &str,
params: &[String],
named_params: &HashMap<String, String>,
) -> CuResult<String> {
if format_str.contains("{}") {
let mut formatted = format_str.to_string();
for param in params.iter() {
if !formatted.contains("{}") {
break;
}
formatted = formatted.replacen("{}", param, 1);
}
if formatted.contains("{}") && !named_params.is_empty() {
let mut named = named_params.iter().collect::<Vec<_>>();
named.sort_by(|a, b| a.0.cmp(b.0));
for (_, value) in named {
if !formatted.contains("{}") {
break;
}
formatted = formatted.replacen("{}", value, 1);
}
}
return Ok(format!("{time} [{level:?}]: {formatted}"));
}
let logline = strfmt(format_str, named_params).map_err(|e| {
cu29_traits::CuError::new_with_cause(
format!("Failed to format log line: {format_str:?} with variables [{named_params:?}]")
.as_str(),
e,
)
})?;
Ok(format!("{time} [{level:?}]: {logline}"))
}
#[cfg(feature = "std")]
pub fn rebuild_logline(all_interned_strings: &[String], entry: &CuLogEntry) -> CuResult<String> {
let format_string = all_interned_strings
.get(entry.msg_index as usize)
.ok_or_else(|| {
cu29_traits::CuError::from(format!(
"Invalid message index {} (interned strings length {})",
entry.msg_index,
all_interned_strings.len()
))
})?;
if entry.paramname_indexes.len() != entry.params.len() {
return Err(cu29_traits::CuError::from(format!(
"Mismatched parameter metadata: {} names for {} params",
entry.paramname_indexes.len(),
entry.params.len()
)));
}
let mut anon_params = Vec::with_capacity(entry.params.len());
let mut named_params = HashMap::with_capacity(entry.params.len());
for (i, param) in entry.params.iter().enumerate() {
let param_as_string = format!("{param}");
if entry.paramname_indexes[i] == 0 {
anon_params.push(param_as_string);
} else {
let name = all_interned_strings
.get(entry.paramname_indexes[i] as usize)
.ok_or_else(|| {
cu29_traits::CuError::from(format!(
"Invalid parameter name index {} (interned strings length {})",
entry.paramname_indexes[i],
all_interned_strings.len()
))
})?
.clone();
named_params.insert(name, param_as_string);
}
}
format_logline(
entry.time,
entry.level,
format_string,
&anon_params,
&named_params,
)
}
#[cfg(all(feature = "defmt", not(feature = "std")))]
#[macro_export]
macro_rules! __cu29_defmt_debug {
($fmt:literal $(, $arg:expr)* $(,)?) => {
::defmt::debug!($fmt $(, $arg)*);
}
}
#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
#[macro_export]
macro_rules! __cu29_defmt_debug {
($($tt:tt)*) => {{}};
}
#[cfg(all(feature = "defmt", not(feature = "std")))]
#[macro_export]
macro_rules! __cu29_defmt_info {
($fmt:literal $(, $arg:expr)* $(,)?) => {
::defmt::info!($fmt $(, $arg)*);
}
}
#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
#[macro_export]
macro_rules! __cu29_defmt_info {
($($tt:tt)*) => {{}};
}
#[cfg(all(feature = "defmt", not(feature = "std")))]
#[macro_export]
macro_rules! __cu29_defmt_warn {
($fmt:literal $(, $arg:expr)* $(,)?) => {
::defmt::warn!($fmt $(, $arg)*);
}
}
#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
#[macro_export]
macro_rules! __cu29_defmt_warn {
($($tt:tt)*) => {{}};
}
#[cfg(all(feature = "defmt", not(feature = "std")))]
#[macro_export]
macro_rules! __cu29_defmt_error {
($fmt:literal $(, $arg:expr)* $(,)?) => {
::defmt::error!($fmt $(, $arg)*);
}
}
#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
#[macro_export]
macro_rules! __cu29_defmt_error {
($($tt:tt)*) => {{}};
}
#[macro_export]
macro_rules! defmt_debug {
($($tt:tt)*) => { $crate::__cu29_defmt_debug!($($tt)*) };
}
#[macro_export]
macro_rules! defmt_info {
($($tt:tt)*) => { $crate::__cu29_defmt_info!($($tt)*) };
}
#[macro_export]
macro_rules! defmt_warn {
($($tt:tt)*) => { $crate::__cu29_defmt_warn!($($tt)*) };
}
#[macro_export]
macro_rules! defmt_error {
($($tt:tt)*) => { $crate::__cu29_defmt_error!($($tt)*) };
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_log_level_ordering() {
assert!(CuLogLevel::Critical > CuLogLevel::Error);
assert!(CuLogLevel::Error > CuLogLevel::Warning);
assert!(CuLogLevel::Warning > CuLogLevel::Info);
assert!(CuLogLevel::Info > CuLogLevel::Debug);
assert!(CuLogLevel::Debug < CuLogLevel::Info);
assert!(CuLogLevel::Info < CuLogLevel::Warning);
assert!(CuLogLevel::Warning < CuLogLevel::Error);
assert!(CuLogLevel::Error < CuLogLevel::Critical);
}
#[test]
fn test_log_level_enabled() {
assert!(CuLogLevel::Debug.enabled(CuLogLevel::Debug));
assert!(CuLogLevel::Info.enabled(CuLogLevel::Debug));
assert!(CuLogLevel::Warning.enabled(CuLogLevel::Debug));
assert!(CuLogLevel::Error.enabled(CuLogLevel::Debug));
assert!(CuLogLevel::Critical.enabled(CuLogLevel::Debug));
assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Info));
assert!(CuLogLevel::Info.enabled(CuLogLevel::Info));
assert!(CuLogLevel::Warning.enabled(CuLogLevel::Info));
assert!(CuLogLevel::Error.enabled(CuLogLevel::Info));
assert!(CuLogLevel::Critical.enabled(CuLogLevel::Info));
assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Warning));
assert!(!CuLogLevel::Info.enabled(CuLogLevel::Warning));
assert!(CuLogLevel::Warning.enabled(CuLogLevel::Warning));
assert!(CuLogLevel::Error.enabled(CuLogLevel::Warning));
assert!(CuLogLevel::Critical.enabled(CuLogLevel::Warning));
assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Error));
assert!(!CuLogLevel::Info.enabled(CuLogLevel::Error));
assert!(!CuLogLevel::Warning.enabled(CuLogLevel::Error));
assert!(CuLogLevel::Error.enabled(CuLogLevel::Error));
assert!(CuLogLevel::Critical.enabled(CuLogLevel::Error));
assert!(!CuLogLevel::Debug.enabled(CuLogLevel::Critical));
assert!(!CuLogLevel::Info.enabled(CuLogLevel::Critical));
assert!(!CuLogLevel::Warning.enabled(CuLogLevel::Critical));
assert!(!CuLogLevel::Error.enabled(CuLogLevel::Critical));
assert!(CuLogLevel::Critical.enabled(CuLogLevel::Critical));
}
#[test]
fn test_cu_log_entry_with_level() {
let entry = CuLogEntry::new(42, CuLogLevel::Warning);
assert_eq!(entry.level, CuLogLevel::Warning);
assert_eq!(entry.msg_index, 42);
}
}