Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions .github/workflows/linux-arm64-build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,18 @@ jobs:
# Fix ownership of the workspace to allow write access
sudo chown -R $(whoami):$(whoami) $GITHUB_WORKSPACE

# Wrap the test step in nick-fields/retry@v4 to absorb transient flakes
# (mocha races, native rebuilds, Electron postinstall, network blips).
# Keep max_attempts low so real regressions still surface quickly.
- name: Build and test rclnodejs
run: |
uname -a
source /opt/ros/${{ matrix.ros_distribution }}/setup.bash
npm i
npm test
uses: nick-fields/retry@v4
with:
shell: bash
max_attempts: 2
retry_wait_seconds: 10
timeout_minutes: 60
command: |
uname -a
source /opt/ros/${{ matrix.ros_distribution }}/setup.bash
npm i
npm test
15 changes: 12 additions & 3 deletions .github/workflows/linux-x64-asan-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,16 @@ jobs:
source /opt/ros/jazzy/setup.bash
npm i

# Wrap the asan test step in nick-fields/retry@v4 to absorb transient
# flakes. ASan can be sensitive to timing-related test races; keep
# max_attempts low so real regressions still surface quickly.
- name: Build and test with AddressSanitizer
run: |
source /opt/ros/jazzy/setup.bash
npm run test:asan
uses: nick-fields/retry@v4
with:
shell: bash
max_attempts: 2
retry_wait_seconds: 10
timeout_minutes: 60
command: |
source /opt/ros/jazzy/setup.bash
npm run test:asan
41 changes: 29 additions & 12 deletions .github/workflows/linux-x64-build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,22 +123,39 @@ jobs:

- uses: actions/checkout@v6

# The mocha suite and the Electron postinstall (extract-zip 2.0.1) are
# known to be intermittently flaky on this runner. Wrap both test
# invocations in nick-fields/retry@v4 so a single transient failure does
# not fail the whole matrix leg. Keep max_attempts conservative so real
# regressions still surface quickly.
- name: Build and test rclnodejs
run: |
uname -a
source /opt/ros/${{ matrix.ros_distribution }}/setup.bash
npm i
npm run lint
npm test
npm run clean
uses: nick-fields/retry@v4
with:
shell: bash
max_attempts: 2
retry_wait_seconds: 10
timeout_minutes: 60
command: |
uname -a
source /opt/ros/${{ matrix.ros_distribution }}/setup.bash
npm i
npm run lint
npm test
npm run clean

- name: Test with IDL ROS messages (rolling / lyrical)
if: ${{ matrix.ros_distribution == 'rolling' || matrix.ros_distribution == 'lyrical' }}
run: |
source /opt/ros/${{ matrix.ros_distribution }}/setup.bash
npm i
npm run test-idl
npm run clean
uses: nick-fields/retry@v4
with:
shell: bash
max_attempts: 2
retry_wait_seconds: 10
timeout_minutes: 60
command: |
source /opt/ros/${{ matrix.ros_distribution }}/setup.bash
npm i
npm run test-idl
npm run clean

- name: Coveralls
if: ${{ matrix.ros_distribution == 'rolling' && matrix['node-version'] == '24.X' }}
Expand Down
20 changes: 14 additions & 6 deletions .github/workflows/windows-build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,19 @@ jobs:
call "C:\pixi_ws\ros2-windows\setup.bat"
npm i

# Wrap the test step in nick-fields/retry@v4 to absorb transient flakes
# (mocha races, native rebuilds, network blips). Keep max_attempts low so
# real regressions still surface quickly.
- name: Test rclnodejs
if: ${{ matrix.run_tests }}
shell: cmd
run: |
set PATH=C:\pixi_ws\.pixi\envs\default\Library\bin;C:\pixi_ws\.pixi\envs\default\Scripts;C:\pixi_ws\.pixi\envs\default\bin;%PATH%
set RMW_IMPLEMENTATION=rmw_fastrtps_cpp
call "C:\pixi_ws\ros2-windows\setup.bat"
npm test
uses: nick-fields/retry@v4
with:
shell: cmd
max_attempts: 2
retry_wait_seconds: 10
timeout_minutes: 60
command: |
set PATH=C:\pixi_ws\.pixi\envs\default\Library\bin;C:\pixi_ws\.pixi\envs\default\Scripts;C:\pixi_ws\.pixi\envs\default\bin;%PATH%
set RMW_IMPLEMENTATION=rmw_fastrtps_cpp
call "C:\pixi_ws\ros2-windows\setup.bat"
npm test
122 changes: 122 additions & 0 deletions lib/logging_service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright (c) 2026, The Robot Web Tools Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

const Logging = require('./logging.js');

const LOGGING_SEVERITY_UNSET = 0;

/**
* Implements the ROS 2 logging service interfaces for a node.
*
* The interfaces implemented are:
* rcl_interfaces/srv/GetLoggerLevels
* rcl_interfaces/srv/SetLoggerLevels
*
* @class
*/
class LoggingService {
/**
* Create a new instance.
* @param {Node} node - The node these services support.
*/
constructor(node) {
this._node = node;
this._isRunning = false;
}

/**
* Get the node this service supports.
* @return {Node} - The supported node.
*/
get node() {
return this._node;
}

/**
* Check if logging services are configured and accepting requests.
* @return {boolean} - True if services are active; false otherwise.
*/
isStarted() {
return this._isRunning;
}

/**
* Configure logging services and begin processing client requests.
* @return {undefined}
*/
start() {
if (this._isRunning) return;

this._isRunning = true;
const nodeName = this.node.name();

this.node.createService(
'rcl_interfaces/srv/GetLoggerLevels',
nodeName + '/get_logger_levels',
(request, response) => this._handleGetLoggerLevels(request, response)
);

this.node.createService(
'rcl_interfaces/srv/SetLoggerLevels',
nodeName + '/set_logger_levels',
(request, response) => this._handleSetLoggerLevels(request, response)
);
}

_handleGetLoggerLevels(request, response) {
const msg = response.template;

for (const name of request.names) {
try {
msg.levels.push({
name,
level: Logging.getLogger(name).loggerEffectiveLevel,
});
} catch {
msg.levels.push({
name,
level: LOGGING_SEVERITY_UNSET,
});
}
}

response.send(msg);
}

_handleSetLoggerLevels(request, response) {
const msg = response.template;

for (const loggerLevel of request.levels) {
const result = {
successful: false,
reason: '',
};

try {
Logging.getLogger(loggerLevel.name).setLoggerLevel(loggerLevel.level);
result.successful = true;
} catch (error) {
result.reason = error.message;
}

msg.results.push(result);
}

response.send(msg);
}
}

module.exports = LoggingService;
9 changes: 9 additions & 0 deletions lib/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const DistroUtils = require('./distro.js');
const GuardCondition = require('./guard_condition.js');
const loader = require('./interface_loader.js');
const Logging = require('./logging.js');
const LoggingService = require('./logging_service.js');
const NodeOptions = require('./node_options.js');
const {
ParameterType,
Expand Down Expand Up @@ -121,6 +122,8 @@ class Node extends rclnodejs.ShadowNode {
defaults.startTypeDescriptionService,
enableRosout: options.enableRosout ?? defaults.enableRosout,
rosoutQos: options.rosoutQos ?? defaults.rosoutQos,
enableLoggerService:
options.enableLoggerService ?? defaults.enableLoggerService,
};
}

Expand Down Expand Up @@ -159,6 +162,7 @@ class Node extends rclnodejs.ShadowNode {
this._parameterDescriptors = new Map();
this._parameters = new Map();
this._parameterService = null;
this._loggerService = null;
this._typeDescriptionService = null;
this._parameterEventPublisher = null;
this._preSetParametersCallbacks = [];
Expand Down Expand Up @@ -219,6 +223,11 @@ class Node extends rclnodejs.ShadowNode {
this._parameterService.start();
}

if (options.enableLoggerService) {
this._loggerService = new LoggingService(this);
this._loggerService.start();
}

if (
DistroUtils.getDistroId() >= DistroUtils.getDistroId('jazzy') &&
options.startTypeDescriptionService
Expand Down
22 changes: 21 additions & 1 deletion lib/node_options.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@ class NodeOptions {
* @param {boolean} [startTypeDescriptionService=true]
* @param {boolean} [enableRosout=true]
* @param {QoS} [rosoutQos=QoS.profileDefault]
* @param {boolean} [enableLoggerService=false]
*/
constructor(
startParameterServices = true,
parameterOverrides = [],
automaticallyDeclareParametersFromOverrides = false,
startTypeDescriptionService = true,
enableRosout = true,
rosoutQos = null
rosoutQos = null,
enableLoggerService = false
) {
this._startParameterServices = startParameterServices;
this._parameterOverrides = parameterOverrides;
Expand All @@ -45,6 +47,7 @@ class NodeOptions {
this._startTypeDescriptionService = startTypeDescriptionService;
this._enableRosout = enableRosout;
this._rosoutQos = rosoutQos;
this._enableLoggerService = enableLoggerService;
}

/**
Expand Down Expand Up @@ -164,6 +167,23 @@ class NodeOptions {
this._rosoutQos = rosoutQos;
}

/**
* Get the enableLoggerService option.
* Default value = false;
* @returns {boolean} - true if logger services are enabled.
*/
get enableLoggerService() {
return this._enableLoggerService;
}

/**
* Set enableLoggerService.
* @param {boolean} enableLoggerService
*/
set enableLoggerService(enableLoggerService) {
this._enableLoggerService = enableLoggerService;
}

/**
* Return an instance configured with default options.
* @returns {NodeOptions} - An instance with default values.
Expand Down
Loading
Loading