Skip to content

Commit 2b44f3b

Browse files
authored
Support logging rosout (#1336)
This PR adds support for ROS2 rosout logging by introducing `enableRosout` and `rosoutQos` configuration options to control rosout publisher behavior in nodes. The changes include TypeScript type definitions, C++ bindings, JavaScript implementation, and comprehensive test coverage. - Adds `enableRosout` boolean flag to control rosout logging startup (default: true) - Adds `rosoutQos` option to configure QoS profile for rosout publisher - Implements child logger support with rosout sublogger management for ROS2 distributions newer than Humble Fix: #1330
1 parent 0f52003 commit 2b44f3b

File tree

9 files changed

+321
-3
lines changed

9 files changed

+321
-3
lines changed

lib/logging.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ class Caller {
8989
class Logging {
9090
constructor(name) {
9191
this._name = name;
92+
this._parentName = null;
93+
this._subName = null;
9294
}
9395

9496
/**
@@ -203,6 +205,50 @@ class Logging {
203205
return this._name;
204206
}
205207

208+
/**
209+
* Create a child logger.
210+
* @param {string} name - name of the child logger.
211+
* @function
212+
* @return {Logging} Return the child logger object.
213+
*/
214+
getChild(name) {
215+
if (typeof name !== 'string' || !name) {
216+
throw new Error('Child logger name must be a non-empty string.');
217+
}
218+
219+
let fullname = name;
220+
if (this._name) {
221+
fullname = this._name + '.' + name;
222+
}
223+
224+
const logger = new Logging(fullname);
225+
if (this._name) {
226+
if (
227+
rclnodejs.addRosoutSublogger &&
228+
rclnodejs.addRosoutSublogger(this._name, name)
229+
) {
230+
logger._parentName = this._name;
231+
logger._subName = name;
232+
}
233+
}
234+
return logger;
235+
}
236+
237+
/**
238+
* Destroy the logger and remove it from the parent logger if it is a child logger.
239+
* @function
240+
* @return {undefined}
241+
*/
242+
destroy() {
243+
if (this._parentName && this._subName) {
244+
if (rclnodejs.removeRosoutSublogger) {
245+
rclnodejs.removeRosoutSublogger(this._parentName, this._subName);
246+
}
247+
this._parentName = null;
248+
this._subName = null;
249+
}
250+
}
251+
206252
/**
207253
* Create a logger by name.
208254
* @param {string} name - name of the logger.

lib/node.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ class Node extends rclnodejs.ShadowNode {
102102
namespace,
103103
context.handle,
104104
args,
105-
useGlobalArguments
105+
useGlobalArguments,
106+
options.rosoutQos
106107
);
107108
Object.defineProperty(this, 'handle', {
108109
configurable: false,
@@ -132,6 +133,11 @@ class Node extends rclnodejs.ShadowNode {
132133
this._setParametersCallbacks = [];
133134
this._logger = new Logging(rclnodejs.getNodeLoggerName(this.handle));
134135
this._spinning = false;
136+
this._enableRosout = options.enableRosout;
137+
138+
if (this._enableRosout) {
139+
rclnodejs.initRosoutPublisherForNode(this.handle);
140+
}
135141

136142
this._parameterEventPublisher = this.createPublisher(
137143
PARAMETER_EVENT_MSG_TYPE,
@@ -1016,6 +1022,11 @@ class Node extends rclnodejs.ShadowNode {
10161022

10171023
this.context.onNodeDestroyed(this);
10181024

1025+
if (this._enableRosout) {
1026+
rclnodejs.finiRosoutPublisherForNode(this.handle);
1027+
this._enableRosout = false;
1028+
}
1029+
10191030
this.handle.release();
10201031
this._clock = null;
10211032
this._timers = [];

lib/node_options.js

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,24 @@ class NodeOptions {
2727
* @param {array} [parameterOverrides=[]]
2828
* @param {boolean} [automaticallyDeclareParametersFromOverrides=false]
2929
* @param {boolean} [startTypeDescriptionService=true]
30+
* @param {boolean} [enableRosout=true]
31+
* @param {QoS} [rosoutQos=QoS.profileDefault]
3032
*/
3133
constructor(
3234
startParameterServices = true,
3335
parameterOverrides = [],
3436
automaticallyDeclareParametersFromOverrides = false,
35-
startTypeDescriptionService = true
37+
startTypeDescriptionService = true,
38+
enableRosout = true,
39+
rosoutQos = null
3640
) {
3741
this._startParameterServices = startParameterServices;
3842
this._parameterOverrides = parameterOverrides;
3943
this._automaticallyDeclareParametersFromOverrides =
4044
automaticallyDeclareParametersFromOverrides;
4145
this._startTypeDescriptionService = startTypeDescriptionService;
46+
this._enableRosout = enableRosout;
47+
this._rosoutQos = rosoutQos;
4248
}
4349

4450
/**
@@ -125,6 +131,39 @@ class NodeOptions {
125131
this._startTypeDescriptionService = willStartTypeDescriptionService;
126132
}
127133

134+
/**
135+
* Get the enableRosout option.
136+
* Default value = true;
137+
* @returns {boolean} - true if the rosout logging is enabled.
138+
*/
139+
get enableRosout() {
140+
return this._enableRosout;
141+
}
142+
143+
/**
144+
* Set enableRosout.
145+
* @param {boolean} enableRosout
146+
*/
147+
set enableRosout(enableRosout) {
148+
this._enableRosout = enableRosout;
149+
}
150+
151+
/**
152+
* Get the rosoutQos option.
153+
* @returns {QoS} - The QoS profile for rosout.
154+
*/
155+
get rosoutQos() {
156+
return this._rosoutQos;
157+
}
158+
159+
/**
160+
* Set rosoutQos.
161+
* @param {QoS} rosoutQos
162+
*/
163+
set rosoutQos(rosoutQos) {
164+
this._rosoutQos = rosoutQos;
165+
}
166+
128167
/**
129168
* Return an instance configured with default options.
130169
* @returns {NodeOptions} - An instance with default values.

src/rcl_logging_bindings.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,51 @@
1515
#include "rcl_logging_bindings.h"
1616

1717
#include <rcl/error_handling.h>
18+
#include <rcl/logging.h>
19+
#include <rcl/logging_rosout.h>
1820
#include <rcl/rcl.h>
1921
#include <rcl_logging_interface/rcl_logging_interface.h>
2022

2123
#include <string>
2224

2325
#include "macros.h"
26+
#include "rcl_handle.h"
2427
#include "rcl_utilities.h"
2528

2629
namespace rclnodejs {
2730

31+
Napi::Value InitRosoutPublisherForNode(const Napi::CallbackInfo& info) {
32+
Napi::Env env = info.Env();
33+
RclHandle* node_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
34+
rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr());
35+
36+
if (rcl_logging_rosout_enabled()) {
37+
rcl_ret_t ret = rcl_logging_rosout_init_publisher_for_node(node);
38+
if (ret != RCL_RET_OK) {
39+
Napi::Error::New(env, rcl_get_error_string().str)
40+
.ThrowAsJavaScriptException();
41+
rcl_reset_error();
42+
}
43+
}
44+
return env.Undefined();
45+
}
46+
47+
Napi::Value FiniRosoutPublisherForNode(const Napi::CallbackInfo& info) {
48+
Napi::Env env = info.Env();
49+
RclHandle* node_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
50+
rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr());
51+
52+
if (rcl_logging_rosout_enabled()) {
53+
rcl_ret_t ret = rcl_logging_rosout_fini_publisher_for_node(node);
54+
if (ret != RCL_RET_OK) {
55+
Napi::Error::New(env, rcl_get_error_string().str)
56+
.ThrowAsJavaScriptException();
57+
rcl_reset_error();
58+
}
59+
}
60+
return env.Undefined();
61+
}
62+
2863
Napi::Value setLoggerLevel(const Napi::CallbackInfo& info) {
2964
Napi::Env env = info.Env();
3065

@@ -104,6 +139,41 @@ Napi::Value GetLoggingDirectory(const Napi::CallbackInfo& info) {
104139
return result;
105140
}
106141

142+
#if ROS_VERSION > 2205
143+
Napi::Value AddRosoutSublogger(const Napi::CallbackInfo& info) {
144+
Napi::Env env = info.Env();
145+
std::string logger_name = info[0].As<Napi::String>().Utf8Value();
146+
std::string sublogger_name = info[1].As<Napi::String>().Utf8Value();
147+
148+
rcl_ret_t ret = rcl_logging_rosout_add_sublogger(logger_name.c_str(),
149+
sublogger_name.c_str());
150+
if (ret == RCL_RET_OK) {
151+
return Napi::Boolean::New(env, true);
152+
} else if (ret == RCL_RET_NOT_FOUND) {
153+
rcl_reset_error();
154+
return Napi::Boolean::New(env, false);
155+
} else {
156+
Napi::Error::New(env, rcl_get_error_string().str)
157+
.ThrowAsJavaScriptException();
158+
rcl_reset_error();
159+
return env.Undefined();
160+
}
161+
}
162+
163+
Napi::Value RemoveRosoutSublogger(const Napi::CallbackInfo& info) {
164+
Napi::Env env = info.Env();
165+
std::string logger_name = info[0].As<Napi::String>().Utf8Value();
166+
std::string sublogger_name = info[1].As<Napi::String>().Utf8Value();
167+
168+
rcl_ret_t ret = rcl_logging_rosout_remove_sublogger(logger_name.c_str(),
169+
sublogger_name.c_str());
170+
if (ret != RCL_RET_OK) {
171+
rcl_reset_error();
172+
}
173+
return env.Undefined();
174+
}
175+
#endif
176+
107177
Napi::Object InitLoggingBindings(Napi::Env env, Napi::Object exports) {
108178
exports.Set("setLoggerLevel", Napi::Function::New(env, setLoggerLevel));
109179
exports.Set("getLoggerEffectiveLevel",
@@ -112,6 +182,16 @@ Napi::Object InitLoggingBindings(Napi::Env env, Napi::Object exports) {
112182
exports.Set("isEnableFor", Napi::Function::New(env, IsEnableFor));
113183
exports.Set("getLoggingDirectory",
114184
Napi::Function::New(env, GetLoggingDirectory));
185+
exports.Set("initRosoutPublisherForNode",
186+
Napi::Function::New(env, InitRosoutPublisherForNode));
187+
exports.Set("finiRosoutPublisherForNode",
188+
Napi::Function::New(env, FiniRosoutPublisherForNode));
189+
#if ROS_VERSION > 2205
190+
exports.Set("addRosoutSublogger",
191+
Napi::Function::New(env, AddRosoutSublogger));
192+
exports.Set("removeRosoutSublogger",
193+
Napi::Function::New(env, RemoveRosoutSublogger));
194+
#endif
115195
return exports;
116196
}
117197

src/rcl_node_bindings.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
#include <rcl_yaml_param_parser/types.h>
2727

2828
#include <rcpputils/scope_exit.hpp>
29-
// NOLINTNEXTLINE
29+
// NOLINTBEGIN
30+
#include <memory>
3031
#include <string>
32+
// NOLINTEND
3133

3234
#include "macros.h"
3335
#include "rcl_handle.h"
@@ -212,6 +214,11 @@ Napi::Value CreateNode(const Napi::CallbackInfo& info) {
212214
options.use_global_arguments = use_global_arguments;
213215
options.arguments = arguments;
214216

217+
if (info.Length() > 5 && !info[5].IsUndefined() && !info[5].IsNull()) {
218+
std::unique_ptr<rmw_qos_profile_t> qos_profile = GetQoSProfile(info[5]);
219+
options.rosout_qos = *qos_profile;
220+
}
221+
215222
THROW_ERROR_IF_NOT_EQUAL(RCL_RET_OK,
216223
rcl_node_init(node, node_name.c_str(),
217224
name_space.c_str(), context, &options),

0 commit comments

Comments
 (0)