Skip to content

Comments

kraken: use zenoh to show extension logs#3803

Open
nicoschmdt wants to merge 2 commits intobluerobotics:masterfrom
nicoschmdt:kraken-logs-zenoh
Open

kraken: use zenoh to show extension logs#3803
nicoschmdt wants to merge 2 commits intobluerobotics:masterfrom
nicoschmdt:kraken-logs-zenoh

Conversation

@nicoschmdt
Copy link
Contributor

@nicoschmdt nicoschmdt commented Feb 20, 2026

image

Summary by Sourcery

Switch extension log viewing to use zenoh-based streaming and historical retrieval with a dedicated logs modal.

New Features:

  • Add ExtensionLogsModal component to display extension logs with zenoh-based historical loading and live streaming, including follow and download controls.

Enhancements:

  • Initialize and manage a shared zenoh session in ExtensionManagerView for use by the logs modal.
  • Refactor ExtensionManagerView to delegate log display to the new logs modal and simplify log-related state management.
  • Expose a zenoh queryable endpoint in the backend to serve historical extension logs and associated subscription topic information.
  • Extend ContainerManager with an API to fetch non-streaming historical container logs for extensions.

Note

Medium Risk
Touches both UI and backend messaging paths for logs and introduces a new Zenoh queryable, so failures could break log visibility or add load when fetching large historical logs.

Overview
Switches extension log viewing from the GET /container/{name}/log streaming path to a Zenoh-based flow that queries historical logs and subscribes to live log topics.

Frontend adds a dedicated ExtensionLogsModal (follow + download, buffering/limits, error handling) and refactors ExtensionManagerView to open it and manage a shared Zenoh Session; the old getContainerLogs API call and client-side streaming/parsing code are removed.

Backend registers a new Zenoh queryable (extension/logs/request) that resolves an extension, returns recent container logs plus the topic to subscribe to, and adds ContainerManager.get_container_historical_logs to support non-streaming log retrieval.

Written by Cursor Bugbot for commit 6253510. This will update automatically on new commits. Configure here.

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 20, 2026

Reviewer's Guide

Refactors extension log viewing to use a dedicated Vue modal component backed by Zenoh-based log streaming and historical log retrieval, replacing the old HTTP streaming container-logs endpoint and wiring backend Zenoh queryables for extension logs.

Sequence diagram for extension logs retrieval via Zenoh

sequenceDiagram
    actor User
    participant ExtensionManagerView
    participant ExtensionLogsModal
    participant ZenohSession as Zenoh_ts_Session
    participant BackendZenohAPI as Zenoh_router_backend
    participant ZenohHandlers
    participant ContainerManager
    participant DockerEngine
    participant ExtensionLogPublisher

    User->>ExtensionManagerView: click ShowLogs(extension)
    ExtensionManagerView->>ExtensionManagerView: initializeZenohSession()
    ExtensionManagerView->>ZenohSession: Session.open(Config(url))
    ZenohSession-->>ExtensionManagerView: zenoh_session

    ExtensionManagerView->>ExtensionLogsModal: open with extensionIdentifier, extensionName, zenohSession
    ExtensionLogsModal->>ExtensionLogsModal: openModal()
    alt zenohSession not initialized
        ExtensionLogsModal->>ExtensionLogsModal: modal_error = "Zenoh session not initialized"
    else zenohSession ready
        ExtensionLogsModal->>ExtensionLogsModal: requestHistoricalLogsForExtension(identifier)
        ExtensionLogsModal->>ZenohSession: get("kraken/extension/logs/request?extension_name=identifier")
        ZenohSession->>BackendZenohAPI: route query extension/logs/request
        BackendZenohAPI->>ZenohHandlers: logs_request_handler(extension_name)
        ZenohHandlers->>Extension: _fetch_settings()
        ZenohHandlers->>ZenohHandlers: find matching ExtensionSettings
        ZenohHandlers->>ExtensionLogPublisher: _topic_for(extension)
        ZenohHandlers->>Extension: container_name()
        ZenohHandlers->>ContainerManager: get_container_historical_logs(container_name)
        ContainerManager->>DockerEngine: get_container_log_by_name(container_name)
        DockerEngine-->>ContainerManager: raw_logs list
        ContainerManager-->>ZenohHandlers: raw_logs
        ZenohHandlers->>ExtensionLogPublisher: _extract_level(raw_line) for each line
        ZenohHandlers-->>BackendZenohAPI: {status, messages, total_lines, topic}
        BackendZenohAPI-->>ZenohSession: query reply
        ZenohSession-->>ExtensionLogsModal: Receiver result Sample
        ExtensionLogsModal->>ExtensionLogsModal: parse JSON, set modal_messages
        ExtensionLogsModal->>ExtensionLogsModal: scrollToBottom()

        ExtensionLogsModal->>ZenohSession: declare_subscriber(topic)
        ZenohSession-->>ExtensionLogsModal: modal_subscriber
        loop live logs
            ExtensionLogPublisher->>ZenohSession: publish log Sample on topic
            ZenohSession-->>ExtensionLogsModal: handler(sample)
            ExtensionLogsModal->>ExtensionLogsModal: buffer message and flush to modal_messages
            ExtensionLogsModal->>ExtensionLogsModal: scheduleScroll()
        end
    end

    User->>ExtensionLogsModal: click Download
    ExtensionLogsModal->>ExtensionLogsModal: downloadCurrentLog() create File
    ExtensionLogsModal->>User: browser downloads safeName.log

    User->>ExtensionLogsModal: close modal
    ExtensionLogsModal->>ExtensionLogsModal: cleanup() undeclare subscriber
Loading

Class diagram for Zenoh-based extension logs components

classDiagram
    class ExtensionManagerView {
      <<VueComponent>>
      +bool show_log
      +string selected_log_extension_identifier
      +string selected_log_extension_name
      +Session zenoh_session
      +mounted()
      +destroyed()
      +async initializeZenohSession()
      +showLogs(extension)
    }

    class ExtensionLogsModal {
      <<VueComponent>>
      +bool value
      +string extensionIdentifier
      +string extensionName
      +Session zenohSession
      +LogMessage[] modal_messages
      +Subscriber modal_subscriber
      +string current_modal_topic
      +string modal_error
      +bool requesting_logs
      +number query_timeout
      +bool follow_logs
      +bool scroll_pending
      +LogMessage[] message_buffer
      +number buffer_flush_timer
      +async openModal()
      +closeModal()
      +cleanup()
      +flushMessageBuffer()
      +async setupModalSubscriber(topic)
      +async requestHistoricalLogsForExtension(identifier)
      +bool isMessageEmpty(msg)
      +string extractLogMessage(msg)
      +string formatLogMessage(msg)
      +scheduleScroll()
      +scrollToBottom(force)
      +setErrorAndStop(message)
      +string formatErrorType(errorType)
      +downloadCurrentLog()
    }

    class LogMessage {
      +string message
    }

    class ZenohHandlers {
      +ZenohRouter router
      +ZenohHandlers(router)
      +async logs_request_handler(extension_name) dict
      +register_queryables()
    }

    class ContainerManager {
      +async get_container_log_by_name(container_name) AsyncGenerator~str~
      +async get_container_historical_logs(container_name) List~str~
      +async get_containers_stats() Dict
    }

    class ExtensionLogPublisher {
      +static _topic_for(extension) string
      +static _extract_level(raw_line) tuple
    }

    class ExtensionSettings {
      +string identifier
      +string name
      +string container_name()
    }

    class Extension {
      +static _fetch_settings() List~ExtensionSettings~
    }

    class ZenohRouter {
      +add_routes_to_zenoh(application)
      +add_queryable(key, handler)
    }

    class FastAPIZenohIntegration {
      <<FastAPIApp>>
      +ZenohRouter zenoh_router
      +ZenohHandlers zenoh_handlers
      +lifespan()
    }

    ExtensionManagerView --> ExtensionLogsModal : uses
    ExtensionManagerView --> Session : holds zenoh_session
    ExtensionLogsModal --> LogMessage : manages
    ExtensionLogsModal --> Session : uses zenohSession
    ExtensionLogsModal --> Subscriber : declares modal_subscriber

    FastAPIZenohIntegration --> ZenohRouter : configures
    FastAPIZenohIntegration --> ZenohHandlers : creates
    ZenohHandlers --> ZenohRouter : registers queryables
    ZenohHandlers --> Extension : reads settings
    ZenohHandlers --> ExtensionLogPublisher : uses topic and level helpers
    ZenohHandlers --> ContainerManager : requests Docker logs
    ZenohHandlers --> ExtensionSettings : consumes instances

    ContainerManager --> DockerEngine : interacts

    class Session {
      +static open(config) Session
      +declare_subscriber(topic, options) Subscriber
      +get(key, options) Receiver
      +close()
    }

    class Subscriber {
      +undeclare()
    }

    class Receiver {
      +receive() Reply
    }

    class DockerEngine {
      <<ExternalService>>
    }
Loading

File-Level Changes

Change Details Files
Replace inline extension log dialog with a reusable ExtensionLogsModal component that consumes zenoh logs.
  • Remove the inline v-dialog markup and log rendering from ExtensionManagerView.vue and replace it with an ExtensionLogsModal usage bound to show_log.
  • Track the selected extension identifier and name instead of low-level log output state in ExtensionManagerView.
  • Add ExtensionLogsModal to the component registry and pass the zenoh_session plus extension identity into it.
core/frontend/src/views/ExtensionManagerView.vue
core/frontend/src/components/kraken/modals/ExtensionLogsModal.vue
Initialize and lifecycle-manage a shared zenoh Session in the extension manager frontend.
  • Import Config and Session from @eclipse-zenoh/zenoh-ts instead of axios/AnsiUp/file-saver for logs in ExtensionManagerView.
  • Add zenoh_session to component data, initialize it on mounted using a URL derived from window.location, and close it on component destruction.
  • Remove log-related watchers, ansi/html conversion, and download buffering previously used for HTTP-streamed logs.
core/frontend/src/views/ExtensionManagerView.vue
Remove the REST/axios-based container log streaming API from the Kraken frontend manager.
  • Delete getContainerLogs from KrakenManager API wrapper and its export list.
  • Remove showLogs HTTP streaming implementation and downloadCurrentLog from ExtensionManagerView, delegating to the zenoh-based modal instead.
  • Delete streaming helpers usage (aggregateStreamingResponse, parseStreamingResponse) and AbortController-based cancelation tied to log viewing.
core/frontend/src/components/kraken/KrakenManager.ts
core/frontend/src/views/ExtensionManagerView.vue
Implement a Zenoh-backed ExtensionLogsModal that handles historical log query, live subscription, rendering, and download.
  • Create a new Vue modal component that requests historical logs over zenoh, subscribes to a per-extension topic, and renders log lines with ANSI color support.
  • Implement buffering and throttled UI updates for incoming log messages, with follow_logs scrolling behavior and simple error reporting UI.
  • Support downloading the currently displayed log as a .log file, limiting retained messages for performance, and cleaning up subscribers/timers on close or destroy.
core/frontend/src/components/kraken/modals/ExtensionLogsModal.vue
Expose extension historical logs over zenoh on the backend and wire them into the existing ZenohRouter.
  • Add ContainerManager.get_container_historical_logs to fetch non-following stdout/stderr logs from Docker.
  • Introduce ZenohHandlers with a logs_request_handler that resolves an extension by identifier/name, fetches its container logs, wraps them as messages, and returns metadata including the zenoh topic used for live logs.
  • Register the logs request queryable during FastAPI app lifespan so frontend can query "extension/logs/request" via zenoh.
  • Ensure errors in the handler are logged and surfaced as structured error responses with an error_type field.
core/services/kraken/harbor/container.py
core/services/kraken/api/zenoh_handlers.py
core/services/kraken/api/app.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

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The historical logs path (get_container_historical_logslogs_request_handler) returns the full log list and sends all lines over Zenoh in one response; consider enforcing a maximum number of lines or a time/size window to prevent excessive memory usage and very large payloads for long-running containers.
  • In ExtensionLogsModal.cleanup, undeclare() is called without awaiting while other code paths (setupModalSubscriber) await it; for consistency and to avoid dangling subscriptions, consider centralizing subscriber teardown in a single async helper that is always awaited from async contexts and called synchronously only when absolutely necessary.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The historical logs path (`get_container_historical_logs``logs_request_handler`) returns the full log list and sends all lines over Zenoh in one response; consider enforcing a maximum number of lines or a time/size window to prevent excessive memory usage and very large payloads for long-running containers.
- In `ExtensionLogsModal.cleanup`, `undeclare()` is called without awaiting while other code paths (`setupModalSubscriber`) await it; for consistency and to avoid dangling subscriptions, consider centralizing subscriber teardown in a single async helper that is always awaited from async contexts and called synchronously only when absolutely necessary.

## Individual Comments

### Comment 1
<location> `core/services/kraken/harbor/container.py:127-134` </location>
<code_context>
             logger.info(f"Finished streaming logs for {container_name}")

+    @classmethod
+    async def get_container_historical_logs(cls, container_name: str) -> List[str]:
+        async with DockerCtx() as client:
+            try:
+                container = await cls.get_raw_container_by_name(client, container_name)
+            except ContainerNotFound as error:
+                raise StackedHTTPException(status_code=status.HTTP_404_NOT_FOUND, error=error) from error
+
+            return await container.log(stdout=True, stderr=True, follow=False, stream=False)  # type: ignore
+
     @classmethod
</code_context>

<issue_to_address>
**issue (bug_risk):** The type hint suggests a list of strings, but `container.log(..., stream=False)` may return bytes or a single string, which can break downstream processing.

`get_container_historical_logs` is annotated as `List[str]`, and `logs_request_handler` iterates over `raw_logs` assuming each element is a full line of text. However, `container.log(..., stream=False)` may return a single `str`/`bytes` blob or `bytes`, not a list of lines. That can cause iteration over bytes/characters and type issues.

To keep the contract accurate, normalize the result before returning:
- Decode `bytes` to `str` (e.g., UTF-8).
- If you get a single string, split it into lines (e.g., `splitlines()`) so the method always returns `List[str]`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

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