Summary
Logging endpoints expose structured log entries from entities and allow configuring logging verbosity per entity. In ROS 2, all node logs are published to the /rosout topic, making them centrally available to the gateway. The gateway subscribes to /rosout, stores entries in a ring buffer, and serves them via these endpoints.
Proposed solution
1. GET /api/v1/{entity-path}/logs
Query structured log entries from an entity.
Applies to entity types: Components, Apps
Path parameters:
| Parameter |
Type |
Required |
Description |
{entity-path} |
URL segment |
Yes |
e.g., apps/temp_sensor |
Query parameters:
| Parameter |
Type |
Required |
Description |
severity |
string |
No |
Minimum severity filter. Only entries at this level or higher are returned. |
context |
string |
No |
Filter by context identifier (e.g., logger name sub-component) |
Severity levels (ordered lowest to highest):
| Level |
Description |
ROS 2 Equivalent |
debug |
Detailed debugging info |
DEBUG (10) |
info |
General information |
INFO (20) |
warning |
Warning conditions |
WARN (30) |
error |
Error conditions |
ERROR (40) |
fatal |
Critical failures |
FATAL (50) |
When severity=warning is specified, only warning, error, and fatal entries are returned.
Response 200 OK:
{
"items": [
{
"id": "log_001",
"timestamp": "2026-02-14T10:30:00Z",
"severity": "warning",
"message": "Sensor calibration drift detected: offset=0.23",
"context": {
"node": "temp_sensor",
"logger": "temp_sensor.calibration"
}
},
{
"id": "log_002",
"timestamp": "2026-02-14T10:30:05Z",
"severity": "error",
"message": "Failed to read sensor: timeout after 5000ms",
"context": {
"node": "temp_sensor",
"logger": "temp_sensor.driver"
}
}
]
}
Each item is a LogEntry:
| Field |
Type |
Required |
Description |
id |
string |
Yes |
Unique log entry identifier (server-generated, monotonically increasing) |
timestamp |
string (ISO 8601) |
Yes |
When the log event occurred |
severity |
string |
Yes |
One of: debug, info, warning, error, fatal |
message |
string |
Yes |
Human-readable log message |
context |
object |
No |
Structured context (key-value pairs). Typically includes node name and logger name. |
Error responses:
| Status |
Error Code |
When |
400 |
invalid-parameter |
Unknown severity value |
404 |
entity-not-found |
Entity doesn't exist |
2. GET /api/v1/{entity-path}/logs/configuration
Read the current logging configuration for an entity.
Response 200 OK:
{
"severity_filter": "info",
"max_entries": 10000
}
| Field |
Type |
Required |
Description |
severity_filter |
string |
Yes |
Current minimum severity level for log collection |
max_entries |
integer |
Yes |
Maximum number of log entries retained in the ring buffer |
3. PUT /api/v1/{entity-path}/logs/configuration
Update the logging configuration for an entity.
Request body:
{
"severity_filter": "warning",
"max_entries": 5000
}
| Field |
Type |
Required |
Description |
severity_filter |
string |
No |
New minimum severity. One of: debug, info, warning, error, fatal |
max_entries |
integer |
No |
New max entries. Must be > 0. |
Both fields are optional - only provided fields are updated.
Response 204 No Content
Error responses:
| Status |
Error Code |
When |
400 |
invalid-parameter |
Unknown severity value, max_entries ≤ 0 |
404 |
entity-not-found |
Entity doesn't exist |
Additional context
ROS 2 log collection
- Subscribe to
/rosout - the standard ROS 2 topic for centralized logging (message type: rcl_interfaces/msg/Log)
- Filter by node name - each
/rosout message includes name (node name). Match this against the entity's bound ROS 2 node (App.bound_fqn or Component.fqn).
- Store in a ring buffer per entity - configurable
max_entries, oldest entries evicted when full.
rcl_interfaces/msg/Log fields → LogEntry mapping:
| ROS 2 Log Field |
LogEntry Field |
stamp |
timestamp |
level (uint8) |
severity (mapped: 10→debug, 20→info, 30→warning, 40→error, 50→fatal) |
msg |
message |
name |
context.node |
function |
context.function (optional) |
file |
context.file (optional) |
line |
context.line (optional) |
Logging configuration mapping
Setting severity_filter for an entity can map to:
- Gateway-side filter - only store entries at or above the configured level (simplest)
- ROS 2 logger level - call
rcl_interfaces/srv/SetLoggerLevel service to change the actual node's log level (changes what the node emits, not just what's stored)
Recommended: implement both - gateway-side filter for what's stored, and optionally set the ROS 2 logger level for efficiency.
Architecture
- Create a
LogManager class that subscribes to /rosout
- Per-entity ring buffers (keyed by entity ID → node name mapping)
- Thread-safe access (multiple HTTP requests may query simultaneously)
LogEntry.id can be a simple monotonically increasing integer counter
Route registration
srv->Get((api_path("/apps") + R"(/([^/]+)/logs$)"), handler);
srv->Get((api_path("/apps") + R"(/([^/]+)/logs/configuration$)"), handler);
srv->Put((api_path("/apps") + R"(/([^/]+)/logs/configuration$)"), handler);
// Same for /components/
Important: Register /logs/configuration routes before the generic /logs$ route.
Tests
- Unit test: query logs returns stored entries
- Unit test: severity filter only returns matching entries
- Unit test: read configuration returns current settings
- Unit test: update configuration changes severity filter
- Unit test: ring buffer evicts old entries when full
- Unit test: invalid severity → 400
- Integration test: launch demo nodes, collect /rosout logs, query via endpoint
Summary
Logging endpoints expose structured log entries from entities and allow configuring logging verbosity per entity. In ROS 2, all node logs are published to the
/rosouttopic, making them centrally available to the gateway. The gateway subscribes to/rosout, stores entries in a ring buffer, and serves them via these endpoints.Proposed solution
1.
GET /api/v1/{entity-path}/logsQuery structured log entries from an entity.
Applies to entity types: Components, Apps
Path parameters:
{entity-path}apps/temp_sensorQuery parameters:
severitystringcontextstringSeverity levels (ordered lowest to highest):
debugDEBUG(10)infoINFO(20)warningWARN(30)errorERROR(40)fatalFATAL(50)When
severity=warningis specified, onlywarning,error, andfatalentries are returned.Response
200 OK:{ "items": [ { "id": "log_001", "timestamp": "2026-02-14T10:30:00Z", "severity": "warning", "message": "Sensor calibration drift detected: offset=0.23", "context": { "node": "temp_sensor", "logger": "temp_sensor.calibration" } }, { "id": "log_002", "timestamp": "2026-02-14T10:30:05Z", "severity": "error", "message": "Failed to read sensor: timeout after 5000ms", "context": { "node": "temp_sensor", "logger": "temp_sensor.driver" } } ] }Each item is a
LogEntry:idstringtimestampstring(ISO 8601)severitystringdebug,info,warning,error,fatalmessagestringcontextobjectnodename andloggername.Error responses:
400invalid-parameter404entity-not-found2.
GET /api/v1/{entity-path}/logs/configurationRead the current logging configuration for an entity.
Response
200 OK:{ "severity_filter": "info", "max_entries": 10000 }severity_filterstringmax_entriesinteger3.
PUT /api/v1/{entity-path}/logs/configurationUpdate the logging configuration for an entity.
Request body:
{ "severity_filter": "warning", "max_entries": 5000 }severity_filterstringdebug,info,warning,error,fatalmax_entriesintegerBoth fields are optional - only provided fields are updated.
Response
204 No ContentError responses:
400invalid-parameter404entity-not-foundAdditional context
ROS 2 log collection
/rosout- the standard ROS 2 topic for centralized logging (message type:rcl_interfaces/msg/Log)/rosoutmessage includesname(node name). Match this against the entity's bound ROS 2 node (App.bound_fqnorComponent.fqn).max_entries, oldest entries evicted when full.rcl_interfaces/msg/Logfields →LogEntrymapping:stamptimestamplevel(uint8)severity(mapped: 10→debug, 20→info, 30→warning, 40→error, 50→fatal)msgmessagenamecontext.nodefunctioncontext.function(optional)filecontext.file(optional)linecontext.line(optional)Logging configuration mapping
Setting
severity_filterfor an entity can map to:rcl_interfaces/srv/SetLoggerLevelservice to change the actual node's log level (changes what the node emits, not just what's stored)Recommended: implement both - gateway-side filter for what's stored, and optionally set the ROS 2 logger level for efficiency.
Architecture
LogManagerclass that subscribes to/rosoutLogEntry.idcan be a simple monotonically increasing integer counterRoute registration
Important: Register
/logs/configurationroutes before the generic/logs$route.Tests