Class LogManager

Class Documentation

class LogManager

Manages log collection via the default ring-buffer backend.

Pure C++ application service. Middleware-side ingestion is performed by an injected LogSource adapter (typically Ros2LogSource) that subscribes to /rosout and converts each message into a neutral LogEntry. The manager body keeps its ring-buffer storage, per-entity config map, monotonic id counter, the plugin observer pattern, and the manages_ingestion short-circuit. None of those depend on ROS types.

Plugin integration (two modes):

  • Observer mode (default): If a LogProvider plugin is registered, get_logs() and get_config() delegate to it. on_log_entry() is called on ALL LogProvider observers for every LogEntry produced by the source. Observers returning true suppress ring-buffer storage.

  • Full ingestion (manages_ingestion() == true): The primary LogProvider owns the entire log pipeline. LogManager never calls source->start(), so no subscription is created. All queries and config operations delegate to the plugin.

FQN normalization:

  • entity.fqn from the entity cache has a leading ‘/’ (e.g. “/powertrain/engine/temp_sensor”)

  • Source-emitted entries DO NOT have a leading ‘/’ (rcl_node_get_logger_name convention)

  • Callers pass raw FQNs from entity.fqn; LogManager strips leading ‘/’ internally.

Public Types

using LogSink = std::function<void(int level, std::string_view message)>

Sink for internal diagnostics (plugin provider exceptions, etc.). Keeps LogManager middleware-neutral while preserving observability when the gateway adapter forwards to /rosout. May be null in tests.

using NodeToEntityFn = std::function<std::string(const std::string&)>

Callback that maps a logger FQN to a manifest entity ID. Returns empty string if the FQN cannot be resolved.

Public Functions

explicit LogManager(std::shared_ptr<LogSource> source, LogProviderRegistry *provider_registry = nullptr, size_t max_buffer_size = kDefaultBufferSize, LogSink log_sink = nullptr)

Construct LogManager.

If the primary LogProvider’s manages_ingestion() returns true, source->start() is never called, so no subscription is created. Otherwise the source is started with on_log_entry() as the entry-point callback.

Parameters:
  • sourceLogSource adapter (typically Ros2LogSource). Manager takes shared ownership.

  • provider_registryLogProviderRegistry port for LogProvider lookup (typically the PluginManager). May be nullptr - equivalent to “no plugins registered”.

  • max_buffer_size – Ring buffer size per node (override for unit testing)

  • log_sink – Callback for internal diagnostics. The gateway passes an adapter that forwards to RCLCPP_WARN/ERROR; unit tests may capture calls or pass nullptr.

~LogManager()
LogManager(const LogManager&) = delete
LogManager &operator=(const LogManager&) = delete
LogManager(LogManager&&) = delete
LogManager &operator=(LogManager&&) = delete
tl::expected<json, std::string> get_logs(const std::vector<std::string> &node_fqns, bool prefix_match, const std::string &min_severity, const std::string &context_filter, const std::string &entity_id)

Query log entries for a set of node FQNs.

If a LogProvider plugin is registered, delegates to it. Otherwise uses the local ring buffer.

Parameters:
  • node_fqns – Node FQNs from entity.fqn (WITH leading ‘/’ - normalized internally)

  • prefix_match – If true, match all buffered nodes whose name starts with the given prefix (used for Component queries). If false, exact match (App queries).

  • min_severity – Additional severity filter from query parameter. Empty = no override.

  • context_filter – Substring filter applied to log entry’s name (logger name). Empty = no filter.

  • entity_id – Entity ID for config lookup. Empty = use defaults.

Returns:

JSON array of LogEntry objects sorted by id ascending, capped at entity config max_entries.

tl::expected<LogConfig, std::string> get_config(const std::string &entity_id) const

Get current log configuration for entity (returns defaults if unconfigured)

std::string update_config(const std::string &entity_id, const std::optional<std::string> &severity_filter, const std::optional<size_t> &max_entries)

Update log configuration for an entity.

Returns:

Empty string on success, error message on validation failure

void add_log_entry(const std::string &entity_id, const std::string &severity, const std::string &message, const nlohmann::json &metadata)

Programmatically add a log entry (e.g. from trigger log_settings)

Creates a LogEntry and pushes it to the internal ring buffer using the same path as on_log_entry(). If a ResourceChangeNotifier is set, emits a “logs” CREATED notification so triggers can observe log changes.

Parameters:
  • entity_id – Entity to associate the log with (used as logger name)

  • severity – SOVD severity string (debug, info, warning, error, fatal)

  • message – Human-readable log message

  • metadata – Additional JSON metadata stored in the message (appended)

void set_notifier(ResourceChangeNotifier *notifier)

Set the ResourceChangeNotifier for emitting log change events. Called by GatewayNode after both LogManager and the notifier are available.

void set_node_to_entity_resolver(NodeToEntityFn resolver)

Set the node-to-entity resolver for trigger notifications. When set, on_log_entry() resolves logger names to manifest entity IDs before notifying the ResourceChangeNotifier.

void inject_entry_for_testing(LogEntry entry)

Inject a log entry directly into the ring buffer (bypasses the source)

Used by unit tests to populate the buffer without a live source feed. In production the buffer is populated exclusively by source-emitted entries.

std::size_t dropped_entries_count() const noexcept

Total number of log entries dropped because the per-node-buffer cap (max_buffer_size_ * 10) was exceeded.

Each “new node beyond the cap” entry counts as one drop. Per-node ring-buffer pops (oldest entries replaced inside an existing buffer) are NOT counted here - this counter tracks only entries fully rejected from being buffered. Used by tests and (eventually) a /server-info or /diagnostics endpoint to surface buffer pressure.

Public Static Functions

static std::string level_to_severity(uint8_t level)

Convert ROS 2 uint8 log level -> SOVD severity string (“debug” for unknown levels)

static uint8_t severity_to_level(const std::string &severity)

Convert SOVD severity string -> ROS 2 uint8 log level (0 for invalid/empty)

static bool is_valid_severity(const std::string &severity)

Check if a severity string is valid (one of: debug, info, warning, error, fatal)

static json entry_to_json(const LogEntry &entry)

Format a LogEntry as SOVD JSON (id, timestamp, severity, message, context)

static std::string normalize_fqn(const std::string &fqn)

Strip leading ‘/’ from a node FQN for ring-buffer key normalization.

Public Static Attributes

static constexpr size_t kDefaultBufferSize = 200

Default maximum number of entries retained per node in the ring buffer.

static constexpr int kLogLevelWarn = 30

Severity used by the LogManager’s log_sink callback. Numeric values match rcl/rcutils log levels (30=WARN, 40=ERROR) so an adapter can forward to RCLCPP_* macros without translation.

static constexpr int kLogLevelError = 40