Skip to content

Implement Logging (application log query) endpoints #208

@bburda

Description

@bburda

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

  1. Subscribe to /rosout - the standard ROS 2 topic for centralized logging (message type: rcl_interfaces/msg/Log)
  2. 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).
  3. 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:

  1. Gateway-side filter - only store entries at or above the configured level (simplest)
  2. 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

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions