Skip to content

Conversation

@joaomariolago
Copy link
Collaborator

@joaomariolago joaomariolago commented Dec 17, 2025

Summary by Sourcery

Introduce a shared event publishing utility and integrate structured lifecycle and health events across core services, while enriching logging metadata with process and session identifiers.

New Features:

  • Add a common EventPublisher utility for emitting Foxglove-compatible service lifecycle, health, error, and settings events over Zenoh.
  • Introduce settings snapshot events from both Pydantic- and Pykson-based configuration managers on initial load and save operations.
  • Expose helpers to retrieve Zenoh session IDs and compose standardized <zid>/<process> source identifiers for telemetry.
  • Add a process metadata helper to derive a human-readable process name used in logs and events.

Enhancements:

  • Wire start, running, and health event publication into multiple core services once they start up and become ready.
  • Extend log publishing to include standardized source names combining Zenoh session ID and process name.
  • Relax MAVLink server log-path handling so logs are only enabled when a path is explicitly configured.
  • Adjust Commander and bootstrap container configurations to use the new system log directory instead of the previous path.

@sourcery-ai
Copy link

sourcery-ai bot commented Dec 17, 2025

Reviewer's Guide

Introduce a reusable event publishing utility that emits structured Foxglove events over Zenoh, integrate it with logging and settings managers, and instrument core services to emit standardized lifecycle and health events, while slightly relaxing MAVLinkServer log-path requirements and updating some defaults.

Sequence diagram for service lifecycle and health events

sequenceDiagram
    actor ServiceProcess
    participant LogsModule as logs
    participant EventPublisher as events
    participant ZenohSession
    participant ZenohSessionImpl as zenohSession

    ServiceProcess->>logs: init_logger(service_name)
    logs->>events: initialize(service_name)
    events->>ProcessName as process_utils: get_process_name()
    ProcessName-->>events: process_name
    events->>ZenohSession: ZenohSession(service_name)
    ZenohSession-->>events: instance
    events->>ZenohSessionImpl: create zenoh.Session
    ZenohSessionImpl-->>ZenohSession: session

    ServiceProcess->>events: publish_start()
    events->>events: _timestamp_payload()
    events->>ZenohSession: format_source_name(process_name)
    ZenohSession->>ZenohSessionImpl: get_session_id via info()
    ZenohSessionImpl-->>ZenohSession: info
    ZenohSession-->>events: source_name
    events->>ZenohSessionImpl: put(topic, foxgloveLog)

    ServiceProcess->>events: publish_running()
    events->>events: _timestamp_payload()
    events->>ZenohSessionImpl: put(topic, foxgloveLog)
    events->>events: publish_health("ready")
    events->>ZenohSessionImpl: put(topic, foxgloveLog)

    ServiceProcess-->>events: process exit
    events->>events: atexit _handle_process_exit()
    events->>events: publish_stop()
    events->>ZenohSessionImpl: put(topic, foxgloveLog)
Loading

Sequence diagram for settings snapshot events

sequenceDiagram
    participant Service as service
    participant SettingsMgr as PydanticManager
    participant EventPublisher as events
    participant ZenohSession as zenohSession
    participant ZenohSessionImpl as zenohSessionImpl

    service->>SettingsMgr: load()
    SettingsMgr->>SettingsMgr: _load_from_files()
    alt valid_settings_file
        SettingsMgr->>SettingsMgr: _settings = load_from_file()
        SettingsMgr->>SettingsMgr: _emit_initial_settings_event()
    else no_valid_settings
        SettingsMgr->>SettingsMgr: _settings = default()
        SettingsMgr->>SettingsMgr: save()
        SettingsMgr->>SettingsMgr: _emit_initial_settings_event()
    end

    SettingsMgr->>SettingsMgr: _publish_settings_snapshot("initial-load")
    SettingsMgr->>SettingsMgr: _serialize_settings()
    SettingsMgr->>events: publish_settings(settings, metadata)
    events->>events: _timestamp_payload()
    events->>zenohSession: format_source_name(process_name)
    zenohSession->>zenohSessionImpl: get_session_id via info()
    zenohSessionImpl-->>zenohSession: info
    zenohSession-->>events: source_name
    events->>zenohSessionImpl: put(topic, foxgloveLog)

    service->>SettingsMgr: save()
    SettingsMgr->>SettingsMgr: settings.save()
    SettingsMgr->>SettingsMgr: _publish_settings_snapshot("save")
    SettingsMgr->>events: publish_settings(settings, metadata)
    events->>zenohSessionImpl: put(topic, foxgloveLog)
Loading

Class diagram for EventPublisher, ZenohSession, and settings managers

classDiagram
    class EventPublisher {
        - ZenohSession _zenoh_session
        - str _service_name
        - str _process_name
        - str _topic
        - bool _initialized
        - bool _stop_emitted
        - bool _atexit_registered
        - bool _excepthook_installed
        - _previous_excepthook
        - bool _asyncio_handler_installed
        - _previous_asyncio_handler
        + initialize(service_name str) void
        + publish(event_type str, payload dict) void
        + publish_start(additional_payload dict) void
        + publish_settings(settings dict, metadata dict) void
        + publish_running(additional_payload dict) void
        + publish_health(status str, details dict) void
        + publish_error(message str, details dict) void
        + publish_stop(additional_payload dict) void
        - _foxglove_timestamp() dict
        - _serialize_event_message(event_type str, payload dict) str
        - _publish_event(event_type str, payload dict) void
        - _timestamp_payload(base dict) dict
        - _register_exit_handlers() void
        - _handle_process_exit() void
        - _handle_unhandled_exception(exc_type, exc_value, exc_traceback) void
        - _handle_async_exception(loop, context dict) void
    }

    class ZenohSession {
        + zenoh.Session session
        + zenoh.Config config
        + ThreadPoolExecutor _executor
        + str _session_id
        + get_session_id() str
        + format_source_name(process_name str) str
        + zenoh_config(service_name str) void
        + close() void
        + publish(topic str, data) void
        + subscribe(topic str, callback) void
        + _extract_session_id(info) str
        + _parse_session_id_string(data str) str
    }

    class PydanticManager {
        + str project_name
        + Path config_folder
        + Type settings_type
        + PydanticSettings _settings
        + bool _initial_event_emitted
        + save() void
        + load() void
        + _clear_temp_files() void
        + _serialize_settings() dict
        + _publish_settings_snapshot(reason str) void
        + _emit_initial_settings_event() void
    }

    class PyksonManager {
        + str project_name
        + Path config_folder
        + Type settings_type
        + PyksonSettings _settings
        + bool _initial_event_emitted
        + save() void
        + load() void
        + _clear_temp_files() void
        + _serialize_settings() dict
        + _publish_settings_snapshot(reason str) void
        + _emit_initial_settings_event() void
    }

    class ProcessUtils {
        + get_process_name() str
    }

    class zenohSession

    EventPublisher --> ZenohSession : uses
    EventPublisher --> ProcessUtils : calls
    PydanticManager --> EventPublisher : publishes settings
    PyksonManager --> EventPublisher : publishes settings
    ZenohSession --> zenohSession : wraps zenoh.Session
Loading

File-Level Changes

Change Details Files
Add a reusable Foxglove/Zenoh event publishing utility and expose a small compatibility API.
  • Introduce EventPublisher class that wraps a ZenohSession and publishes Foxglove-formatted log events to a per-service event topic.
  • Support standard event types (start, running, settings, health, error, stop) with timestamped payloads and JSON serialization fallback behavior.
  • Register process-exit, sys.excepthook, and asyncio exception handlers to automatically emit stop and error events for unhandled failures.
  • Expose a module-level singleton (events) plus thin wrapper functions (init_event_publisher, publish_*_event) and export them from commonwealth.utils for convenient imports.
core/libs/commonwealth/src/commonwealth/utils/events.py
core/libs/commonwealth/src/commonwealth/utils/__init__.py
Augment Zenoh logging helper to include a stable session identifier in log source names.
  • Cache a Zenoh session ID (zid) on ZenohSession, lazily resolving it from various possible info representations (dict, attribute, stringified, or JSON).
  • Add format_source_name to combine <zid>/<process> when a session id is available, falling back to the process name.
  • Update the log sink to derive a per-process name and route log records through format_source_name so Foxglove logs include a richer source identifier.
  • Add small parsing helpers to robustly extract session IDs from structured or free-form strings using JSON parsing and regex.
core/libs/commonwealth/src/commonwealth/utils/zenoh_helper.py
core/libs/commonwealth/src/commonwealth/utils/logs.py
core/libs/commonwealth/src/commonwealth/utils/process.py
Emit settings snapshot events whenever configuration is loaded or saved for both Pydantic and Pykson settings managers.
  • Track whether an initial settings event was emitted to avoid duplicate initial snapshots.
  • On successful settings load (from file or fallback defaults), emit an initial-load settings event with project metadata.
  • On each save, serialize the settings (pydantic model_dump/dict or Pykson JSON) and publish a settings event with project and reason metadata.
  • Add best-effort error handling around serialization and publishing to avoid impacting settings I/O paths.
core/libs/commonwealth/src/commonwealth/settings/managers/pydantic_manager.py
core/libs/commonwealth/src/commonwealth/settings/managers/pykson_manager.py
Instrument core services to emit standardized lifecycle and health events through the new event publisher.
  • After logger initialization, publish a start event at process bootstrap for each service that imports logging via commonwealth.utils.logs/events.
  • At or just before the main async server begins serving (i.e., when the service is ready), publish a running event and a health "ready" event with basic endpoint/port metadata.
  • Ensure some long-running worker-style services (kraken, disk_usage, recorder_extractor) emit running/ready and rely on the global exit handlers for stop/error events.
  • Centralize access to the events publisher via the commonwealth.utils.events/events import pattern in service main modules.
core/services/ardupilot_manager/main.py
core/services/commander/main.py
core/services/beacon/main.py
core/services/bag_of_holding/main.py
core/services/bridget/main.py
core/services/cable_guy/main.py
core/services/helper/main.py
core/services/nmea_injector/main.py
core/services/pardal/main.py
core/services/ping/main.py
core/services/versionchooser/main.py
core/services/wifi/main.py
core/services/kraken/main.py
core/services/disk_usage/main.py
core/services/recorder_extractor/main.py
Adjust a few service-specific behaviors and defaults to align with new telemetry and deployment expectations.
  • Change commander default system log folder to /usr/blueos/system_logs and stop bind-mounting /var/logs/blueos into the bootstrap container in versionchooser.
  • Update MAVLinkServer command construction so --log-path is only added when a log_path is configured, instead of forcing a default path.
core/services/commander/main.py
core/services/versionchooser/utils/chooser.py
core/services/ardupilot_manager/mavlink_proxy/MAVLinkServer.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@joaomariolago joaomariolago force-pushed the add-standard-services-log-events branch from 17cc2d0 to e15b90d Compare December 19, 2025 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant