Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1cfec5a
feat(logging): add LogProvider plugin interface and PluginManager wiring
bburda Mar 3, 2026
e5b17f2
feat(logging): add LogManager header with plugin delegation and FQN n…
bburda Mar 3, 2026
951f1c9
test(logging): add LogManager unit tests (severity, FQN normalization…
bburda Mar 3, 2026
9581fdd
feat(logging): implement LogManager with /rosout ring buffer and plug…
bburda Mar 3, 2026
05e3dbe
fix(logging): address code review feedback
bburda Mar 3, 2026
b0dfc9c
feat(logging): add Capability::LOGS to discovery responses
bburda Mar 3, 2026
2b36a2c
feat(logging): add LogHandlers and wire LogManager into GatewayNode
bburda Mar 3, 2026
2cdb19a
feat(logging): register /logs and /logs/configuration routes in RESTS…
bburda Mar 3, 2026
28db4f8
feat(test-utils): add put_raw() helper to GatewayTestCase
bburda Mar 3, 2026
4a430e7
test(logging): add integration test for /logs and /logs/configuration…
bburda Mar 3, 2026
80400c3
docs(logging): document communication log REST API endpoints
bburda Mar 3, 2026
1d56084
test(logging): replace @issue 208 tags with @verifies REQ_INTEROP_06x…
bburda Mar 3, 2026
6d461f1
test(logging): replace @issue tags with @verifies REQ_INTEROP_06x tra…
bburda Mar 3, 2026
fbf92ad
test(logging): add Capability::LOGS coverage to test_capability_builder
bburda Mar 3, 2026
2b34b4e
fix(logging): address PR #245 review comments
bburda Mar 3, 2026
3fb4c26
fix(fault-manager): isolate GTest unit tests from launch_testing subp…
bburda Mar 3, 2026
19f7812
feat(gateway): make log buffer size configurable via params, reduce d…
bburda Mar 3, 2026
20e69c9
docs(logs): align requirements spec and API docs with SOVD spec
bburda Mar 3, 2026
3ca433b
fix(gateway): PUT /logs/configuration returns 204 No Content per SOVD…
bburda Mar 3, 2026
dc8abd0
fix: address code review findings for logging endpoints
bburda Mar 3, 2026
85fae5d
fix: resolve CI test failures and address remaining review comments
bburda Mar 3, 2026
ef2a2d8
fix: correct poll_endpoint_until condition to return dict not bool
bburda Mar 3, 2026
df0220f
feat(logging): add manages_ingestion() to LogProvider and harden plug…
bburda Mar 4, 2026
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ For more examples, see our [Postman collection](postman/).
| 📡 Subscriptions | **Available** | Stream live data and fault events via SSE |
| 🔄 Software Updates | **Available** | Async prepare/execute lifecycle with pluggable backends |
| 🔒 Authentication | **Available** | JWT-based RBAC (viewer, operator, configurator, admin) |
| 📋 Logs | Planned | Log sources, entries, and configuration |
| 📋 Logs | **Available** | Log sources, entries, and configuration |
| 🔁 Entity Lifecycle | Planned | Start, restart, shutdown control |
| 🔐 Modes & Locking | Planned | Target mode control and resource locking |
| 📝 Scripts | Planned | Diagnostic script upload and execution |
Expand Down
118 changes: 118 additions & 0 deletions docs/api/rest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,124 @@ Query and manage faults.
- **400:** Invalid status parameter
- **503:** Fault manager unavailable

Logs Endpoints
--------------

Query and configure the /rosout ring buffer for an entity. Supported entity types:
**components** and **apps**.

.. note::

By default, log entries are sourced from the ``/rosout`` ROS 2 topic. ros2_medkit retains
the 200 most recent entries per node in an in-memory ring buffer (configurable via
``logs.buffer_size`` in ``gateway_params.yaml``). A ``LogProvider`` plugin can replace the
storage backend or take full ownership of the log pipeline (see plugin development docs).

``GET /api/v1/components/{id}/logs``
Query log entries for all nodes in the component namespace (prefix match).

``GET /api/v1/apps/{id}/logs``
Query log entries for the specific app node (exact match).

**Query parameters:**

.. list-table::
:header-rows: 1
:widths: 20 80

* - Parameter
- Description
* - ``severity``
- Minimum severity filter (``debug`` | ``info`` | ``warning`` | ``error`` | ``fatal``).
The stricter of this parameter and the entity's configured ``severity_filter`` is applied.
Without this parameter, the entity's configured ``severity_filter`` (default: ``debug``)
determines the minimum level. Empty or absent = use entity config only.
* - ``context``
- Substring filter applied to the log entry's logger name (``context.node`` in the response).
Maximum length: 256 characters. Empty or absent = no filter.

**Response 200:**

.. code-block:: json

{
"items": [
{
"id": "log_42",
"timestamp": "2026-01-15T10:30:00.123456789Z",
"severity": "warning",
"message": "Calibration drift detected",
"context": {
"node": "powertrain/engine/temp_sensor",
"function": "read_sensor",
"file": "temp_sensor.cpp",
"line": 99
}
}
]
}

The ``context.function``, ``context.file``, and ``context.line`` fields are omitted when empty/zero.

**Severity values** map directly to the ROS 2 log levels:

.. list-table::
:header-rows: 1
:widths: 15 15 70

* - Value
- ROS 2 level
- Meaning
* - ``debug``
- DEBUG (10)
- Fine-grained diagnostic information
* - ``info``
- INFO (20)
- Normal operational messages
* - ``warning``
- WARN (30)
- Non-fatal anomalies
* - ``error``
- ERROR (40)
- Errors that may require attention
* - ``fatal``
- FATAL (50)
- Critical failures

``GET /api/v1/components/{id}/logs/configuration`` / ``GET /api/v1/apps/{id}/logs/configuration``
Return the current log configuration for the entity.

**Response 200:**

.. code-block:: json

{
"severity_filter": "debug",
"max_entries": 100
}

``PUT /api/v1/components/{id}/logs/configuration`` / ``PUT /api/v1/apps/{id}/logs/configuration``
Update the log configuration for the entity. All body fields are optional.

**Request body:**

.. code-block:: json

{
"severity_filter": "warning",
"max_entries": 500
}

``severity_filter`` — minimum severity to return in query results (``debug`` | ``info`` | ``warning`` |
``error`` | ``fatal``). Entries below this level are excluded from queries. Default: ``debug``.

``max_entries`` — maximum number of entries returned per query. Must be between 1 and 10,000
(inclusive). Default: ``100``.

**Response 204:** No content.

- **400:** Invalid ``severity_filter`` or ``max_entries`` value

Bulk Data Endpoints
-------------------

Expand Down
5 changes: 2 additions & 3 deletions docs/requirements/specs/data.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ Data

.. req:: GET /{entity}/data-categories
:id: REQ_INTEROP_016
:status: open
:status: verified
:tags: Data

The endpoint shall provide metadata about available data categories on the addressed entity.

.. req:: GET /{entity}/data-groups
:id: REQ_INTEROP_017
:status: open
:status: verified
:tags: Data

The endpoint shall provide metadata about defined data or signal groups on the addressed entity.
Expand All @@ -35,4 +35,3 @@ Data
:tags: Data

The endpoint shall write a new value to the addressed data item on the entity, if it is writable.

28 changes: 7 additions & 21 deletions docs/requirements/specs/logs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,22 @@ Logs

.. req:: GET /{entity}/logs
:id: REQ_INTEROP_061
:status: open
:status: verified
:tags: Logs

The endpoint shall provide an overview of log sources and log configuration for the addressed entity.
The endpoint shall return log entries for the addressed entity, optionally filtered by
severity or context identifier. *(ISO 17978-3 §7.21)*

.. req:: GET /{entity}/logs/entries
:id: REQ_INTEROP_062
:status: open
:tags: Logs

The endpoint shall return log entries for the addressed entity, optionally in a paged manner.

.. req:: GET /{entity}/logs/config
.. req:: GET /{entity}/logs/configuration
:id: REQ_INTEROP_063
:status: open
:status: verified
:tags: Logs

The endpoint shall return the current logging configuration of the addressed entity.

.. req:: PUT /{entity}/logs/config
.. req:: PUT /{entity}/logs/configuration
:id: REQ_INTEROP_064
:status: open
:status: verified
:tags: Logs

The endpoint shall update the logging configuration of the addressed entity.

.. req:: DELETE /{entity}/logs/config
:id: REQ_INTEROP_065
:status: open
:tags: Logs

The endpoint shall reset the logging configuration of the addressed entity to default settings.

7 changes: 7 additions & 0 deletions src/ros2_medkit_fault_manager/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -126,24 +126,31 @@ if(BUILD_TESTING)
ros2_medkit_clang_tidy()

# Unit tests
# Each GTest target that instantiates ROS 2 nodes gets a dedicated ROS_DOMAIN_ID
# so it cannot interfere with launch_testing integration tests that run concurrently
# in the same CTest invocation and launch their own fault_manager_node subprocess.
ament_add_gtest(test_fault_manager test/test_fault_manager.cpp)
target_link_libraries(test_fault_manager fault_manager_lib)
medkit_target_dependencies(test_fault_manager rclcpp ros2_medkit_msgs)
set_tests_properties(test_fault_manager PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=62")

# SQLite storage tests
ament_add_gtest(test_sqlite_storage test/test_sqlite_storage.cpp)
target_link_libraries(test_sqlite_storage fault_manager_lib)
medkit_target_dependencies(test_sqlite_storage rclcpp ros2_medkit_msgs)
set_tests_properties(test_sqlite_storage PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=63")

# Snapshot capture tests
ament_add_gtest(test_snapshot_capture test/test_snapshot_capture.cpp)
target_link_libraries(test_snapshot_capture fault_manager_lib)
medkit_target_dependencies(test_snapshot_capture rclcpp ros2_medkit_msgs)
set_tests_properties(test_snapshot_capture PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=64")

# Rosbag capture tests
ament_add_gtest(test_rosbag_capture test/test_rosbag_capture.cpp)
target_link_libraries(test_rosbag_capture fault_manager_lib)
medkit_target_dependencies(test_rosbag_capture rclcpp ros2_medkit_msgs)
set_tests_properties(test_rosbag_capture PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=65")

# Correlation config parser tests
ament_add_gtest(test_correlation_config_parser test/test_correlation_config_parser.cpp)
Expand Down
14 changes: 14 additions & 0 deletions src/ros2_medkit_gateway/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ add_library(gateway_lib STATIC
src/operation_manager.cpp
src/configuration_manager.cpp
src/fault_manager.cpp
src/log_manager.cpp
src/subscription_manager.cpp
# Entity resource model
src/models/entity_types.cpp
Expand Down Expand Up @@ -115,6 +116,7 @@ add_library(gateway_lib STATIC
src/http/handlers/operation_handlers.cpp
src/http/handlers/config_handlers.cpp
src/http/handlers/fault_handlers.cpp
src/http/handlers/log_handlers.cpp
src/http/handlers/bulkdata_handlers.cpp
src/http/handlers/sse_fault_handler.cpp
src/http/handlers/cyclic_subscription_handlers.cpp
Expand Down Expand Up @@ -442,6 +444,16 @@ if(BUILD_TESTING)
ament_add_gtest(test_plugin_manager test/test_plugin_manager.cpp)
target_link_libraries(test_plugin_manager gateway_lib)

# Log manager tests
# Dedicated ROS_DOMAIN_ID to prevent cross-talk with concurrent integration tests
ament_add_gtest(test_log_manager test/test_log_manager.cpp)
target_link_libraries(test_log_manager gateway_lib)
set_tests_properties(test_log_manager PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=66")

# Log handlers tests
ament_add_gtest(test_log_handlers test/test_log_handlers.cpp)
target_link_libraries(test_log_handlers gateway_lib)

# Apply coverage flags to test targets
if(ENABLE_COVERAGE)
set(_test_targets
Expand Down Expand Up @@ -474,6 +486,8 @@ if(BUILD_TESTING)
test_health_handlers
test_plugin_loader
test_plugin_manager
test_log_manager
test_log_handlers
)
foreach(_target ${_test_targets})
target_compile_options(${_target} PRIVATE --coverage -O0 -g)
Expand Down
95 changes: 95 additions & 0 deletions src/ros2_medkit_gateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ All endpoints are prefixed with `/api/v1` for API versioning.
- `POST /api/v1/{entity}/{id}/bulk-data/{category}` - Upload bulk data (components/apps only)
- `DELETE /api/v1/{entity}/{id}/bulk-data/{category}/{item_id}` - Delete bulk data (components/apps only)

### Logging Endpoints

- `GET /api/v1/components/{component_id}/logs` - Query recent log entries for a component (all its nodes, prefix match)
- `GET /api/v1/apps/{app_id}/logs` - Query recent log entries for a specific app node (exact match)
- `GET /api/v1/components/{component_id}/logs/configuration` - Get log configuration for a component
- `GET /api/v1/apps/{app_id}/logs/configuration` - Get log configuration for an app
- `PUT /api/v1/components/{component_id}/logs/configuration` - Update log configuration for a component
- `PUT /api/v1/apps/{app_id}/logs/configuration` - Update log configuration for an app

### API Reference

#### GET /api/v1/areas
Expand Down Expand Up @@ -966,6 +975,92 @@ ros2 bag info fault_MOTOR_OVERHEAT_1735830000/
| Query via REST | Yes (structured JSON) | Download only |
| Default | Enabled | Disabled |

### Logging Endpoints

The gateway collects `/rosout` messages and exposes them via REST. Each node's log entries are stored in a per-node ring buffer (default: 200 entries, configurable via `logs.buffer_size` in `gateway_params.yaml`).

**Query parameters for GET /logs:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `severity` | string | Minimum severity filter: `debug`, `info`, `warning`, `error`, `fatal` |
| `context` | string | Substring filter applied to the logger name |

#### GET /api/v1/components/{component_id}/logs

Returns recent log entries for all nodes under the component's namespace (prefix match). Results are capped at `max_entries` (default: 100, configurable per entity via `PUT /logs/configuration`).

```bash
curl http://localhost:8080/api/v1/components/temp_sensor/logs
curl "http://localhost:8080/api/v1/components/temp_sensor/logs?severity=warning"
```

**Response:**

```json
{
"items": [
{
"id": "log_42",
"timestamp": "2026-03-03T12:00:00.000000000Z",
"severity": "warning",
"message": "Temperature exceeded threshold",
"context": {
"node": "powertrain/engine/temp_sensor",
"function": "publish_temperature",
"file": "temp_sensor_node.cpp",
"line": 87
}
}
]
}
```

#### GET /api/v1/apps/{app_id}/logs

Same as above but for a single app node (exact logger name match).

```bash
curl http://localhost:8080/api/v1/apps/temp_sensor/logs
curl "http://localhost:8080/api/v1/apps/temp_sensor/logs?severity=error&context=engine"
```

#### GET /api/v1/{entity}/{id}/logs/configuration

Returns the current log configuration for the entity.

```bash
curl http://localhost:8080/api/v1/components/temp_sensor/logs/configuration
```

**Response:**

```json
{
"severity_filter": "debug",
"max_entries": 100
}
```

#### PUT /api/v1/{entity}/{id}/logs/configuration

Updates the log configuration. Both fields are optional; omitted fields are unchanged.

```bash
curl -X PUT http://localhost:8080/api/v1/components/temp_sensor/logs/configuration \
-H "Content-Type: application/json" \
-d '{"severity_filter": "warning", "max_entries": 50}'
```

**Request body:**

| Field | Type | Description |
|-------|------|-------------|
| `severity_filter` | string | Minimum severity stored/returned: `debug`, `info`, `warning`, `error`, `fatal` |
| `max_entries` | integer > 0 | Maximum entries returned by GET /logs for this entity |

**Response:** `204 No Content`.

## Quick Start

### Build
Expand Down
9 changes: 9 additions & 0 deletions src/ros2_medkit_gateway/config/gateway_params.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ ros2_medkit_gateway:
# Default: 100
max_subscriptions: 100

# Logging Configuration
logs:
# Ring buffer capacity per node name.
# The gateway retains at most this many /rosout entries per node in memory.
# Oldest entries are dropped when the buffer is full.
# Valid range: 1-100000 (values outside this range are clamped with a warning).
# Default: 200
buffer_size: 200

# Discovery Configuration
# Controls how ROS 2 graph entities are mapped to SOVD entities
discovery:
Expand Down
Loading