Skip to content

Commit 2bbf6b6

Browse files
authored
Introduce Logger and LogParams
Signed-off-by: Michael X. Grey <[email protected]>
1 parent 4440546 commit 2bbf6b6

File tree

9 files changed

+787
-311
lines changed

9 files changed

+787
-311
lines changed

rclrs/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ fn main() {
3535
}
3636
};
3737

38+
println!("cargo:rustc-check-cfg=cfg(ros_distro, values(\"humble\", \"iron\", \"jazzy\", \"rolling\"))");
3839
println!("cargo:rustc-cfg=ros_distro=\"{ros_distro}\"");
3940

4041
let mut builder = bindgen::Builder::default()

rclrs/src/logging.rs

Lines changed: 369 additions & 268 deletions
Large diffs are not rendered by default.

rclrs/src/logging/log_params.rs

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
use crate::rcl_bindings::RCUTILS_LOG_SEVERITY;
2+
use std::{borrow::Borrow, ffi::CString, time::Duration};
3+
4+
/// These parameters determine the behavior of an instance of logging.
5+
#[derive(Debug, Clone, Copy)]
6+
pub struct LogParams<'a> {
7+
/// The name of the logger
8+
logger_name: LoggerName<'a>,
9+
/// The severity of the logging instance.
10+
severity: LogSeverity,
11+
/// Specify when a log message should be published (See[`LoggingOccurrence`] above)
12+
occurs: LogOccurrence,
13+
/// Specify the publication interval of the message. A value of ZERO (0) indicates that the
14+
/// message should be published every time, otherwise, the message will only be published once
15+
/// the specified interval has elapsed.
16+
/// This field is typically used to limit the output from high-frequency messages, e.g. instead
17+
/// of publishing a log message every 10 milliseconds, the `publish_interval` can be configured
18+
/// such that the message is published every 10 seconds.
19+
interval: Duration,
20+
/// The log message will only published if the specified expression evaluates to true
21+
only_if: bool,
22+
}
23+
24+
impl<'a> LogParams<'a> {
25+
/// Create a set of default log parameters, given the name of a logger
26+
pub fn new(logger_name: LoggerName<'a>) -> Self {
27+
Self {
28+
logger_name,
29+
severity: Default::default(),
30+
occurs: Default::default(),
31+
interval: Duration::new(0, 0),
32+
only_if: true,
33+
}
34+
}
35+
36+
/// Get the logger name
37+
pub fn get_logger_name(&self) -> &LoggerName {
38+
&self.logger_name
39+
}
40+
41+
/// Get the severity of the log
42+
pub fn get_severity(&self) -> LogSeverity {
43+
self.severity
44+
}
45+
46+
/// Get the occurrence
47+
pub fn get_occurence(&self) -> LogOccurrence {
48+
self.occurs
49+
}
50+
51+
/// Get the interval
52+
pub fn get_interval(&self) -> Duration {
53+
self.interval
54+
}
55+
56+
/// Get the arbitrary filter set by the user
57+
pub fn get_user_filter(&self) -> bool {
58+
self.only_if
59+
}
60+
}
61+
62+
/// Methods for defining the behavior of a logging instance.
63+
///
64+
/// This trait is implemented by Logger, Node, and anything that a `&str` can be
65+
/// [borrowed][1] from, such as string literals (`"my_str"`), [`String`], or
66+
/// [`Cow<str>`][2].
67+
///
68+
/// [1]: Borrow
69+
/// [2]: std::borrow::Cow
70+
pub trait ToLogParams<'a>: Sized {
71+
/// Convert the object into LogParams with default settings
72+
fn to_log_params(self) -> LogParams<'a>;
73+
74+
/// The logging should only happen once
75+
fn once(self) -> LogParams<'a> {
76+
self.occurs(LogOccurrence::Once)
77+
}
78+
79+
/// The first time the logging happens, we should skip it
80+
fn skip_first(self) -> LogParams<'a> {
81+
self.occurs(LogOccurrence::SkipFirst)
82+
}
83+
84+
/// Set the occurrence behavior of the log instance
85+
fn occurs(self, occurs: LogOccurrence) -> LogParams<'a> {
86+
let mut params = self.to_log_params();
87+
params.occurs = occurs;
88+
params
89+
}
90+
91+
/// Set an interval during which this log will not publish. A value of zero
92+
/// will never block the message from being published, and this is the
93+
/// default behavior.
94+
///
95+
/// A negative duration is not valid, but will be treated as a zero duration.
96+
fn interval(self, interval: Duration) -> LogParams<'a> {
97+
let mut params = self.to_log_params();
98+
params.interval = interval;
99+
params
100+
}
101+
102+
/// The log will not be published if a `false` expression is passed into
103+
/// this function.
104+
///
105+
/// Other factors may prevent the log from being published if a `true` is
106+
/// passed in, such as `ToLogParams::interval` or `ToLogParams::once`
107+
/// filtering the log.
108+
fn only_if(self, only_if: bool) -> LogParams<'a> {
109+
let mut params = self.to_log_params();
110+
params.only_if = only_if;
111+
params
112+
}
113+
114+
/// Log as a debug message.
115+
fn debug(self) -> LogParams<'a> {
116+
self.severity(LogSeverity::Debug)
117+
}
118+
119+
/// Log as an informative message. This is the default, so you don't
120+
/// generally need to use this.
121+
fn info(self) -> LogParams<'a> {
122+
self.severity(LogSeverity::Info)
123+
}
124+
125+
/// Log as a warning message.
126+
fn warn(self) -> LogParams<'a> {
127+
self.severity(LogSeverity::Warn)
128+
}
129+
130+
/// Log as an error message.
131+
fn error(self) -> LogParams<'a> {
132+
self.severity(LogSeverity::Error)
133+
}
134+
135+
/// Log as a fatal message.
136+
fn fatal(self) -> LogParams<'a> {
137+
self.severity(LogSeverity::Fatal)
138+
}
139+
140+
/// Set the severity for this instance of logging. The default value will be
141+
/// [`LogSeverity::Info`].
142+
fn severity(self, severity: LogSeverity) -> LogParams<'a> {
143+
let mut params = self.to_log_params();
144+
params.severity = severity;
145+
params
146+
}
147+
}
148+
149+
/// This is used to borrow a logger name which might be validated (e.g. came
150+
/// from a [`Logger`][1] struct) or not (e.g. a user-defined `&str`). If an
151+
/// unvalidated logger name is used with one of the logging macros, we will log
152+
/// an error about it, and the original log message will be logged with the
153+
/// default logger.
154+
///
155+
/// [1]: crate::Logger
156+
#[derive(Debug, Clone, Copy)]
157+
pub enum LoggerName<'a> {
158+
/// The logger name is already available as a valid CString
159+
Validated(&'a CString),
160+
/// The logger name has not been validated yet
161+
Unvalidated(&'a str),
162+
}
163+
164+
/// Logging severity.
165+
#[doc(hidden)]
166+
#[derive(Debug, Clone, Copy)]
167+
pub enum LogSeverity {
168+
/// Use the severity level of the parent logger (or the root logger if the
169+
/// current logger has no parent)
170+
Unset,
171+
/// For messages that are not needed outside of debugging.
172+
Debug,
173+
/// For messages that provide useful information about the state of the
174+
/// application.
175+
Info,
176+
/// For messages that indicate something unusual or unintended might have happened.
177+
Warn,
178+
/// For messages that indicate an error has occurred which may cause the application
179+
/// to misbehave.
180+
Error,
181+
/// For messages that indicate an error has occurred which is so severe that the
182+
/// application should terminate because it cannot recover.
183+
///
184+
/// Using this severity level will not automatically cause the application to
185+
/// terminate, the application developer must decide how to do that on a
186+
/// case-by-case basis.
187+
Fatal,
188+
}
189+
190+
impl LogSeverity {
191+
pub(super) fn as_native(&self) -> RCUTILS_LOG_SEVERITY {
192+
use crate::rcl_bindings::rcl_log_severity_t::*;
193+
match self {
194+
LogSeverity::Unset => RCUTILS_LOG_SEVERITY_UNSET,
195+
LogSeverity::Debug => RCUTILS_LOG_SEVERITY_DEBUG,
196+
LogSeverity::Info => RCUTILS_LOG_SEVERITY_INFO,
197+
LogSeverity::Warn => RCUTILS_LOG_SEVERITY_WARN,
198+
LogSeverity::Error => RCUTILS_LOG_SEVERITY_ERROR,
199+
LogSeverity::Fatal => RCUTILS_LOG_SEVERITY_FATAL,
200+
}
201+
}
202+
}
203+
204+
impl Default for LogSeverity {
205+
fn default() -> Self {
206+
Self::Info
207+
}
208+
}
209+
210+
/// Specify when a log message should be published
211+
#[derive(Debug, Clone, Copy)]
212+
pub enum LogOccurrence {
213+
/// Every message will be published if all other conditions are met
214+
All,
215+
/// The message will only be published on the first occurrence (Note: no other conditions apply)
216+
Once,
217+
/// The log message will not be published on the first occurrence, but will be published on
218+
/// each subsequent occurrence (assuming all other conditions are met)
219+
SkipFirst,
220+
}
221+
222+
impl Default for LogOccurrence {
223+
fn default() -> Self {
224+
Self::All
225+
}
226+
}
227+
228+
// Anything that we can borrow a string from can be used as if it's a logger and
229+
// turned into LogParams
230+
impl<'a, T: Borrow<str>> ToLogParams<'a> for &'a T {
231+
fn to_log_params(self) -> LogParams<'a> {
232+
LogParams::new(LoggerName::Unvalidated(self.borrow()))
233+
}
234+
}
235+
236+
impl<'a> ToLogParams<'a> for &'a str {
237+
fn to_log_params(self) -> LogParams<'a> {
238+
LogParams::new(LoggerName::Unvalidated(self))
239+
}
240+
}
241+
242+
impl<'a> ToLogParams<'a> for LogParams<'a> {
243+
fn to_log_params(self) -> LogParams<'a> {
244+
self
245+
}
246+
}

rclrs/src/logging/logger.rs

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

rclrs/src/logging/logging_configuration.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::sync::{Arc, LazyLock, Mutex, Weak};
1+
use std::sync::{Arc, Mutex, OnceLock, Weak};
22

33
use crate::{
44
rcl_bindings::{
@@ -32,12 +32,12 @@ impl LoggingLifecycle {
3232
pub(crate) unsafe fn configure(
3333
context: &rcl_context_t,
3434
) -> Result<Arc<LoggingLifecycle>, RclrsError> {
35-
static CONFIGURATION: LazyLock<LoggingConfiguration> =
36-
LazyLock::new(|| LoggingConfiguration {
37-
lifecycle: Mutex::new(Weak::new()),
38-
});
35+
static CONFIGURATION: OnceLock<LoggingConfiguration> = OnceLock::new();
36+
let configuration = CONFIGURATION.get_or_init(|| LoggingConfiguration {
37+
lifecycle: Mutex::new(Weak::new()),
38+
});
3939

40-
let mut lifecycle = CONFIGURATION.lifecycle.lock().unwrap();
40+
let mut lifecycle = configuration.lifecycle.lock().unwrap();
4141
if let Some(arc_lifecycle) = lifecycle.upgrade() {
4242
return Ok(arc_lifecycle);
4343
}

0 commit comments

Comments
 (0)