rclrs/logging/logger.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
use std::{borrow::Borrow, ffi::CString, sync::Arc};
use crate::{
rcl_bindings::{rcutils_logging_set_default_logger_level, rcutils_logging_set_logger_level},
LogParams, LogSeverity, LoggerName, RclrsError, ToLogParams, ToResult, ENTITY_LIFECYCLE_MUTEX,
};
/// Logger can be passed in as the first argument into one of the [logging][1]
/// macros provided by rclrs. When passing it into one of the logging macros,
/// you can optionally apply any of the methods from [`ToLogParams`] to tweak
/// the logging behavior.
///
/// You can obtain a Logger in the following ways:
/// - Calling [`Node::logger`][2] to get the logger of a Node
/// - Using [`Logger::create_child`] to create a child logger for an existing logger
/// - Using [`Logger::new`] to create a new logger with any name that you'd like
/// - Using [`Logger::default()`] to access the global default logger
///
/// Note that if you are passing the Logger of a Node into one of the logging macros,
/// then you can choose to simply pass in `&node` instead of `node.logger()`.
///
/// [1]: crate::log
/// [2]: crate::Node::logger
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Logger {
name: Arc<str>,
c_name: Arc<CString>,
}
impl Logger {
/// Create a new logger with the given name.
pub fn new(name: impl Borrow<str>) -> Result<Logger, RclrsError> {
let name: Arc<str> = name.borrow().into();
let c_name = match CString::new((*name).to_owned()) {
Ok(c_name) => c_name,
Err(err) => {
return Err(RclrsError::StringContainsNul {
s: (*name).to_owned(),
err,
});
}
};
Ok(Self {
name,
c_name: Arc::new(c_name),
})
}
/// Create a new logger which will be a child of this logger.
///
/// If the name of this logger is `parent_name`, then the name for the new
/// child will be '"parent_name.child_name"`.
///
/// If this is the default logger (whose name is an empty string), then the
/// name for the new child will simply be the value in `child_name`.
pub fn create_child(&self, child_name: impl Borrow<str>) -> Result<Logger, RclrsError> {
if self.name.is_empty() {
Self::new(child_name)
} else {
Self::new(format!("{}.{}", &self.name, child_name.borrow()))
}
}
/// Get the name of this logger
pub fn name(&self) -> &str {
&self.name
}
/// Set the severity level of this logger
pub fn set_level(&self, severity: LogSeverity) -> Result<(), RclrsError> {
// SAFETY: The precondition are:
// - we are passing in a valid CString, which is already taken care of during construction of the Logger
// - the severity level is a valid value, which is guaranteed by the rigid enum definition
// - not thread-safe, so we lock the global mutex before calling this
let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
unsafe {
rcutils_logging_set_logger_level(self.c_name.as_ptr(), severity.as_native() as i32).ok()
}
}
/// Set the severity level of the default logger which acts as the root ancestor
/// of all other loggers.
pub fn set_default_level(severity: LogSeverity) {
// SAFETY: The preconditions are:
// - the severity level is a valid value, which is guaranteed by the rigid enum definition
// - not thread-safe, so we lock the global mutex before calling this
let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
unsafe {
rcutils_logging_set_default_logger_level(severity.as_native() as i32);
}
}
}
impl<'a> ToLogParams<'a> for &'a Logger {
fn to_log_params(self) -> LogParams<'a> {
LogParams::new(LoggerName::Validated(&self.c_name))
}
}
impl Default for Logger {
fn default() -> Self {
Self::new("").unwrap()
}
}
impl std::hash::Hash for Logger {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
}
}