use std::convert::{TryFrom, TryInto};
use std::ffi::CString;
use std::os::raw::{c_char, c_int};
use anyhow::{Context, Result};
use once_cell::sync::Lazy;
use parking_lot::ReentrantMutex;
use crate::error::{RclRustError, ToRclRustResult};
use crate::impl_from_trait_for_enum;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LogSeverity {
Unset,
Debug,
Info,
Warn,
Error,
Fatal,
}
impl_from_trait_for_enum! {
LogSeverity,
rcl_sys::RCUTILS_LOG_SEVERITY,
Unset := RCUTILS_LOG_SEVERITY_UNSET,
Debug := RCUTILS_LOG_SEVERITY_DEBUG,
Info := RCUTILS_LOG_SEVERITY_INFO,
Warn := RCUTILS_LOG_SEVERITY_WARN,
Error := RCUTILS_LOG_SEVERITY_ERROR,
Fatal := RCUTILS_LOG_SEVERITY_FATAL,
}
impl TryFrom<c_int> for LogSeverity {
type Error = anyhow::Error;
fn try_from(from: c_int) -> Result<Self> {
use rcl_sys::RCUTILS_LOG_SEVERITY;
if from == RCUTILS_LOG_SEVERITY::RCUTILS_LOG_SEVERITY_UNSET as u32 as c_int {
Ok(Self::Unset)
} else if from == RCUTILS_LOG_SEVERITY::RCUTILS_LOG_SEVERITY_DEBUG as u32 as c_int {
Ok(Self::Debug)
} else if from == RCUTILS_LOG_SEVERITY::RCUTILS_LOG_SEVERITY_INFO as u32 as c_int {
Ok(Self::Info)
} else if from == RCUTILS_LOG_SEVERITY::RCUTILS_LOG_SEVERITY_WARN as u32 as c_int {
Ok(Self::Warn)
} else if from == RCUTILS_LOG_SEVERITY::RCUTILS_LOG_SEVERITY_ERROR as u32 as c_int {
Ok(Self::Error)
} else if from == RCUTILS_LOG_SEVERITY::RCUTILS_LOG_SEVERITY_FATAL as u32 as c_int {
Ok(Self::Fatal)
} else {
Err(RclRustError::RuntimeError("cast error: LogSeverity").into())
}
}
}
#[no_mangle]
pub(crate) unsafe extern "C" fn logging_output_handler(
location: *const rcl_sys::rcutils_log_location_t,
severity: c_int,
name: *const c_char,
timestamp: rcl_sys::rcutils_time_point_value_t,
format: *const c_char,
args: *mut rcl_sys::va_list,
) {
let _guard = LOGGER_MUTEX.lock();
rcl_sys::rcl_logging_multiple_output_handler(location, severity, name, timestamp, format, args)
}
pub(crate) static LOGGER_MUTEX: Lazy<ReentrantMutex<()>> = Lazy::new(|| ReentrantMutex::new(()));
#[derive(Debug)]
pub struct Logger {
name: CString,
}
impl Logger {
pub fn new(name: &str) -> Self {
Self {
name: CString::new(name).unwrap(),
}
}
pub fn empty_name() -> Self {
Self {
name: CString::default(),
}
}
pub fn log_common(&self, severity: LogSeverity, msg: &str, file: &str, line: u32) {
let _guard = LOGGER_MUTEX.lock();
unsafe {
if !rcl_sys::g_rcutils_logging_initialized {
rcl_sys::rcutils_logging_initialize()
.to_result()
.expect("rcutils_logging_initialize() should succeed");
}
}
if !self.is_enable_for(severity) {
return;
}
let function_name = CString::new("()").unwrap();
let file = CString::new(file).unwrap();
let msg = CString::new(msg).unwrap();
let logging_location = rcl_sys::rcutils_log_location_t {
function_name: function_name.as_ptr(),
file_name: file.as_ptr(),
line_number: line.try_into().unwrap(),
};
unsafe {
rcl_sys::rcutils_log(
&logging_location,
severity.into(),
self.get_name_ptr(),
msg.as_ptr(),
);
}
}
pub fn get_name(&self) -> &str {
self.name.to_str().unwrap()
}
fn get_name_ptr(&self) -> *const c_char {
self.name.as_ptr()
}
pub fn get_child(&self, suffix: &str) -> Self {
if self.name.to_str().unwrap().is_empty() {
Self::empty_name()
} else {
Self::new(&format!(
"{}.{}",
self.name.clone().into_string().unwrap(),
suffix
))
}
}
pub fn set_level(&self, level: LogSeverity) -> Result<()> {
let _guard = LOGGER_MUTEX.lock();
unsafe {
rcl_sys::rcutils_logging_set_logger_level(self.get_name_ptr(), level.into())
.to_result()
.with_context(|| "rcl_sys::rcutils_logging_set_logger_level in Logger::set_level")
}
}
pub fn get_level(&self) -> Result<LogSeverity> {
LogSeverity::try_from(unsafe {
let _guard = LOGGER_MUTEX.lock();
rcl_sys::rcutils_logging_get_logger_level(self.get_name_ptr())
})
.with_context(|| format!("{:?}", self.name))
}
pub fn get_effective_level(&self) -> Result<LogSeverity> {
LogSeverity::try_from(unsafe {
let _guard = LOGGER_MUTEX.lock();
rcl_sys::rcutils_logging_get_logger_effective_level(self.get_name_ptr())
})
.with_context(|| format!("{:?}", self.name))
}
pub fn is_enable_for(&self, severity: LogSeverity) -> bool {
let _guard = LOGGER_MUTEX.lock();
unsafe {
rcl_sys::rcutils_logging_logger_is_enabled_for(self.get_name_ptr(), severity.into())
}
}
}
#[macro_export]
macro_rules! rclrust_debug {
($logger:expr, $($arg:tt)+) => {
$logger.log_common($crate::log::LogSeverity::Debug, &format!($($arg)+), file!(), line!());
};
}
#[macro_export]
macro_rules! rclrust_info {
($logger:expr, $($arg:tt)+) => {
$logger.log_common($crate::log::LogSeverity::Info, &format!($($arg)+), file!(), line!());
};
}
#[macro_export]
macro_rules! rclrust_warn {
($logger:expr, $($arg:tt)+) => {
$logger.log_common($crate::log::LogSeverity::Warn, &format!($($arg)+), file!(), line!());
};
}
#[macro_export]
macro_rules! rclrust_error {
($logger:expr, $($arg:tt)+) => {
$logger.log_common($crate::log::LogSeverity::Error, &format!($($arg)+), file!(), line!());
};
}
#[macro_export]
macro_rules! rclrust_fatal {
($logger:expr, $($arg:tt)+) => {
$logger.log_common($crate::log::LogSeverity::Fatal, &format!($($arg)+), file!(), line!());
};
}
#[cfg(test)]
mod test {
use super::*;
use std::sync::Mutex;
static TEST_MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
#[test]
fn named_logger_init() -> Result<()> {
let logger = Logger::new("test");
assert_eq!(logger.get_name(), "test");
Ok(())
}
#[test]
fn get_child_logger() -> Result<()> {
let logger = Logger::new("test");
let child_logger = logger.get_child("child");
assert_eq!(child_logger.get_name(), "test.child");
let grandchild_logger = child_logger.get_child("child2");
assert_eq!(grandchild_logger.get_name(), "test.child.child2");
Ok(())
}
#[test]
fn empty_logger_level() -> Result<()> {
let _guard = TEST_MUTEX.lock().unwrap();
let logger = Logger::empty_name();
logger.set_level(LogSeverity::Debug)?;
assert_eq!(
logger.get_level()?,
LogSeverity::Debug,
"get_level() should return LogSeverity::Debug"
);
assert_eq!(
logger.get_effective_level()?,
LogSeverity::Debug,
"get_effective_level() should return LogSeverity::Debug"
);
logger.set_level(LogSeverity::Info)?;
Ok(())
}
#[test]
fn named_logger_level() -> Result<()> {
let _guard = TEST_MUTEX.lock().unwrap();
let root_logger = Logger::empty_name();
let logger = Logger::new("test");
assert_eq!(logger.get_level()?, LogSeverity::Unset);
assert_eq!(logger.get_effective_level()?, root_logger.get_level()?);
logger.set_level(LogSeverity::Info)?;
assert_eq!(logger.get_level()?, LogSeverity::Info);
assert_eq!(logger.get_effective_level()?, LogSeverity::Info);
logger.set_level(LogSeverity::Unset)?;
Ok(())
}
#[test]
fn empty_logger_is_enable_for() -> Result<()> {
let _guard = TEST_MUTEX.lock().unwrap();
let logger = Logger::empty_name();
logger.set_level(LogSeverity::Unset)?;
assert!(logger.is_enable_for(LogSeverity::Fatal));
assert!(logger.is_enable_for(LogSeverity::Error));
assert!(logger.is_enable_for(LogSeverity::Warn));
assert!(logger.is_enable_for(LogSeverity::Info));
assert!(logger.is_enable_for(LogSeverity::Debug));
logger.set_level(LogSeverity::Fatal)?;
assert!(logger.is_enable_for(LogSeverity::Fatal));
assert!(!logger.is_enable_for(LogSeverity::Error));
assert!(!logger.is_enable_for(LogSeverity::Warn));
assert!(!logger.is_enable_for(LogSeverity::Info));
assert!(!logger.is_enable_for(LogSeverity::Debug));
logger.set_level(LogSeverity::Error)?;
assert!(logger.is_enable_for(LogSeverity::Fatal));
assert!(logger.is_enable_for(LogSeverity::Error));
assert!(!logger.is_enable_for(LogSeverity::Warn));
assert!(!logger.is_enable_for(LogSeverity::Info));
assert!(!logger.is_enable_for(LogSeverity::Debug));
logger.set_level(LogSeverity::Warn)?;
assert!(logger.is_enable_for(LogSeverity::Fatal));
assert!(logger.is_enable_for(LogSeverity::Error));
assert!(logger.is_enable_for(LogSeverity::Warn));
assert!(!logger.is_enable_for(LogSeverity::Info));
assert!(!logger.is_enable_for(LogSeverity::Debug));
logger.set_level(LogSeverity::Info)?;
assert!(logger.is_enable_for(LogSeverity::Fatal));
assert!(logger.is_enable_for(LogSeverity::Error));
assert!(logger.is_enable_for(LogSeverity::Warn));
assert!(logger.is_enable_for(LogSeverity::Info));
assert!(!logger.is_enable_for(LogSeverity::Debug));
logger.set_level(LogSeverity::Debug)?;
assert!(logger.is_enable_for(LogSeverity::Fatal));
assert!(logger.is_enable_for(LogSeverity::Error));
assert!(logger.is_enable_for(LogSeverity::Warn));
assert!(logger.is_enable_for(LogSeverity::Info));
assert!(logger.is_enable_for(LogSeverity::Debug));
logger.set_level(LogSeverity::Info)?;
Ok(())
}
}