use log::{debug, error, info, trace, warn};
use std::collections::HashMap;
use std::fmt;
use std::sync::{Arc, RwLock};
pub use log::Level as LogLevel;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Component {
Transport,
Protocol,
Device,
Security,
Configuration,
Discovery,
Application,
Queue,
}
impl fmt::Display for Component {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Component::Transport => write!(f, "transport"),
Component::Protocol => write!(f, "protocol"),
Component::Device => write!(f, "device"),
Component::Security => write!(f, "security"),
Component::Configuration => write!(f, "config"),
Component::Discovery => write!(f, "discovery"),
Component::Application => write!(f, "application"),
Component::Queue => write!(f, "queue"),
}
}
}
#[derive(Debug)]
pub struct LoggingConfig {
component_levels: Arc<RwLock<HashMap<Component, LogLevel>>>,
default_level: LogLevel,
protocol_events: bool,
hex_dump: bool,
max_hex_dump_size: usize,
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
component_levels: Arc::new(RwLock::new(HashMap::new())),
default_level: LogLevel::Info,
protocol_events: false,
hex_dump: false,
max_hex_dump_size: 256,
}
}
}
impl LoggingConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn set_default_level(&mut self, level: LogLevel) {
self.default_level = level;
}
pub fn set_component_level(&mut self, component: Component, level: LogLevel) {
if let Ok(mut levels) = self.component_levels.write() {
levels.insert(component, level);
}
}
#[must_use]
pub fn get_component_level(&self, component: Component) -> LogLevel {
if let Ok(levels) = self.component_levels.read() {
levels
.get(&component)
.copied()
.unwrap_or(self.default_level)
} else {
self.default_level
}
}
pub fn set_protocol_events(&mut self, enabled: bool) {
self.protocol_events = enabled;
}
pub fn set_hex_dump(&mut self, enabled: bool) {
self.hex_dump = enabled;
}
pub fn set_max_hex_dump_size(&mut self, size: usize) {
self.max_hex_dump_size = size;
}
#[must_use]
pub fn is_enabled(&self, component: Component, level: LogLevel) -> bool {
let component_level = self.get_component_level(component);
level <= component_level && log::log_enabled!(level)
}
#[must_use]
pub fn protocol_events_enabled(&self) -> bool {
self.protocol_events
}
#[must_use]
pub fn hex_dump_enabled(&self) -> bool {
self.hex_dump
}
}
static LOGGING_CONFIG: std::sync::OnceLock<LoggingConfig> = std::sync::OnceLock::new();
pub fn init_logging(config: LoggingConfig) {
LOGGING_CONFIG.set(config).ok(); }
fn get_config() -> &'static LoggingConfig {
use std::sync::OnceLock;
static DEFAULT_CONFIG: OnceLock<LoggingConfig> = OnceLock::new();
LOGGING_CONFIG
.get()
.unwrap_or_else(|| DEFAULT_CONFIG.get_or_init(LoggingConfig::default))
}
pub fn log_message(component: Component, level: LogLevel, message: &str) {
let config = get_config();
if config.is_enabled(component, level) {
match level {
log::Level::Error => error!(target: &component.to_string(), "{message}"),
log::Level::Warn => warn!(target: &component.to_string(), "{message}"),
log::Level::Info => info!(target: &component.to_string(), "{message}"),
log::Level::Debug => debug!(target: &component.to_string(), "{message}"),
log::Level::Trace => trace!(target: &component.to_string(), "{message}"),
}
}
}
#[macro_export]
macro_rules! log_component {
($component:expr, $level:expr, $($arg:tt)*) => {
$crate::logging::log_message($component, $level, &format!($($arg)*))
};
}
pub fn log_hex_dump(target: &str, description: &str, data: &[u8], max_size: usize) {
let data_to_dump = if data.len() > max_size {
&data[..max_size]
} else {
data
};
let hex_string = data_to_dump
.iter()
.map(|b| format!("{b:02x}"))
.collect::<Vec<_>>()
.join(" ");
let truncated = if data.len() > max_size {
format!(" (truncated from {} bytes)", data.len())
} else {
String::new()
};
debug!(target: target, "{description}{truncated}: {hex_string}");
}
pub struct Timer {
start: std::time::Instant,
component: Component,
operation: String,
}
impl Timer {
#[must_use]
pub fn start(component: Component, operation: &str) -> Self {
Self {
start: std::time::Instant::now(),
component,
operation: operation.to_string(),
}
}
pub fn finish(self) {
let duration = self.start.elapsed();
log_message(
self.component,
LogLevel::Debug,
&format!("Operation '{}' completed in {:?}", self.operation, duration),
);
}
pub fn finish_with_message(self, message: &str) {
let duration = self.start.elapsed();
log_message(
self.component,
LogLevel::Debug,
&format!("{message} (took {duration:?})"),
);
}
}
#[macro_export]
macro_rules! log_transport {
($level:expr, $($arg:tt)*) => {
$crate::logging::log_message($crate::logging::Component::Transport, $level, &format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_protocol {
($level:expr, $($arg:tt)*) => {
$crate::logging::log_message($crate::logging::Component::Protocol, $level, &format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_device {
($level:expr, $($arg:tt)*) => {
$crate::logging::log_message($crate::logging::Component::Device, $level, &format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_security {
($level:expr, $($arg:tt)*) => {
$crate::logging::log_message($crate::logging::Component::Security, $level, &format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_config {
($level:expr, $($arg:tt)*) => {
$crate::logging::log_message($crate::logging::Component::Configuration, $level, &format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_discovery {
($level:expr, $($arg:tt)*) => {
$crate::logging::log_message($crate::logging::Component::Discovery, $level, &format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_application {
($level:expr, $($arg:tt)*) => {
$crate::logging::log_message($crate::logging::Component::Application, $level, &format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_queue {
($level:expr, $($arg:tt)*) => {
$crate::logging::log_message($crate::logging::Component::Queue, $level, &format!($($arg)*))
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_log_level_ordering() {
assert!(LogLevel::Error < LogLevel::Warn);
assert!(LogLevel::Warn < LogLevel::Info);
assert!(LogLevel::Info < LogLevel::Debug);
assert!(LogLevel::Debug < LogLevel::Trace);
}
#[test]
fn test_component_display() {
assert_eq!(Component::Transport.to_string(), "transport");
assert_eq!(Component::Protocol.to_string(), "protocol");
assert_eq!(Component::Device.to_string(), "device");
}
#[test]
fn test_logging_config() {
let mut config = LoggingConfig::new();
assert_eq!(
config.get_component_level(Component::Transport),
LogLevel::Info
);
config.set_component_level(Component::Transport, LogLevel::Debug);
assert_eq!(
config.get_component_level(Component::Transport),
LogLevel::Debug
);
assert_eq!(
config.get_component_level(Component::Protocol),
LogLevel::Info
);
}
#[test]
fn test_timer() {
let timer = Timer::start(Component::Transport, "test_operation");
std::thread::sleep(std::time::Duration::from_millis(1));
timer.finish();
}
}