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

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions