-
Notifications
You must be signed in to change notification settings - Fork 19
Description
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
/rosoutmessage includesname(node name). Match this against the entity's bound ROS 2 node (App.bound_fqnorComponent.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/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
- Create a
LogManagerclass 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.idcan 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