Stream live server logs over SSE

This commit is contained in:
npc0-hue
2026-08-03 22:28:54 +08:00
parent 5d4fca14f9
commit 7eac1926dd
48 changed files with 1526 additions and 263 deletions
@@ -0,0 +1,15 @@
## Overview
Realtime browser log display is a one-way stream, so the platform exposes Server-Sent Events instead of WebSocket for this change. SSE gives the browser one long-lived HTTP response, works with standard `EventSource`, carries same-origin HttpOnly session cookies, and only needs Nginx buffering disabled.
## Transport Boundary
Run continues to upload logs through `POST /api/v1/run/logs/batches`. That path remains durable and retryable: Run writes to local spool, sends bounded batches, receives sequence ACKs, and can retry without depending on browser presence.
Run log capture remains plugin-declared. For live terminal output, Run captures the supervised process channels declared by the plugin as `process.stdout` / `process.stderr`; game file tails such as `file.tail` are explicit plugin-declared history/backfill sources rather than a generic default. A managed process started by Run writes stdout/stderr into Run-owned capture files and Run tails those capture files into durable batch ingest, so restarting Run can resume transmission for an already-running supervised process without inspecting game-specific logs such as `SCUM.log`.
The browser subscribes to `GET /api/v1/server-instances/{id}/logs/events`. Platform authorizes the user session against the server instance, replays a bounded recent history per stream, then publishes newly ingested log entries from memory fan-out. `POST /api/v1/log-streams/query` stays as an explicit historical cursor API, not a realtime polling loop.
## Proxy Notes
SSE does not require `Upgrade` or `Connection: upgrade`. Reverse proxies must avoid buffering the stream and should keep the upstream read timeout long enough for idle log periods.
@@ -0,0 +1,16 @@
## Why
The browser terminal and server log detail view were polling `POST /api/v1/log-streams/query` on a short interval, and terminal polling multiplied that request count by every candidate log stream. This wastes HTTP requests and can overload the platform or reverse proxy while still failing to feel truly realtime.
## What Changes
- Add a platform-owned `GET /api/v1/server-instances/{id}/logs/events` Server-Sent Events stream for browser live logs.
- Keep Run-to-Platform log transport as durable signed HTTP batch ingest with local spool, sequence acknowledgement, and cursor query for history/reconnect repair.
- Switch the server detail log view, live log drawer, and management terminal to a single EventSource connection with bounded initial history instead of periodic `/log-streams/query` polling.
- Add Nginx proxy settings for unbuffered SSE forwarding.
## Impact
- Affects `platform/` API, DTO, service log ingest fan-out, tests, and protocol docs.
- Affects `platform_web/` API types/client, log UI components, tests, and Nginx config.
- Does not add WebSocket terminal transport, direct browser-to-Run connections, plugin-held platform keys, host paths, credentials, or game-specific log behavior in platform/run.
@@ -0,0 +1,52 @@
## ADDED Requirements
### Requirement: Browser live logs use a platform push stream
The Platform SHALL provide a server-scoped browser log event stream that sends safe log stream metadata and log entries over Server-Sent Events.
#### Scenario: Operator opens live logs
- **WHEN** an authorized operator opens a server log view or management terminal
- **THEN** platform_web opens `GET /api/v1/server-instances/{id}/logs/events` with `EventSource`
- **AND** the view does not start a periodic `/api/v1/log-streams/query` polling loop
#### Scenario: Initial history is replayed
- **WHEN** the browser opens the log event stream with a bounded `historyLimit`
- **THEN** Platform replays recent stored entries for the server's log streams before sending the ready event
- **AND** each event contains only safe stream metadata and log entry fields
### Requirement: Durable Run log ingest remains independent
Run-to-Platform log transfer SHALL remain durable HTTP batch ingest with local spool and sequence acknowledgement. Browser streaming MUST fan out only from platform-ingested log data.
#### Scenario: Run uploads a batch
- **WHEN** Run uploads a valid contiguous log batch
- **THEN** Platform stores it, updates the stream latest sequence, acknowledges the batch, and publishes the new entries to matching browser subscribers
- **AND** duplicate batch acknowledgements do not publish duplicate browser events
### Requirement: Run live logs follow plugin-declared process channels
Run live terminal output SHALL come from plugin-declared log sources and the supervised process that Run started. Game-specific file logs MUST NOT be used as the default live terminal source unless the plugin declares that file source for explicit history, fallback, or backfill.
#### Scenario: Run starts a supervised process
- **WHEN** Run executes a plugin lifecycle start action in supervised mode
- **THEN** Run captures the process stdout and stderr into Run-owned durable capture files
- **AND** Run tails those capture files into durable log batch ingest using the plugin-declared process stream keys
- **AND** Run hides the managed Windows process window when the OS supports hidden startup
#### Scenario: Run restarts while the game process remains alive
- **WHEN** Run restarts and reloads its persisted process journal for an already-running supervised process
- **THEN** Run resumes tailing the Run-owned stdout/stderr capture files from persisted offsets
- **AND** Run does not inspect game-specific logs such as `SCUM.log` to synthesize terminal output
### Requirement: Browser log streaming uses platform session authorization
The log event stream SHALL be authorized by the current platform user session and server access rules. Plugins and browser code MUST NOT receive Run session tokens, component keys, host paths, raw credentials, or direct Run socket information.
#### Scenario: Unauthorized user subscribes
- **WHEN** a user without access opens a server log event stream
- **THEN** Platform rejects the request using the existing authorization error behavior
- **AND** no log entries or stream metadata are sent
### Requirement: Reverse proxies forward log events without buffering
The deployed platform_web reverse proxy SHALL forward the log event route without response buffering and with a long read timeout so idle log periods do not force browser polling.
#### Scenario: Nginx proxies SSE
- **WHEN** Nginx forwards `/api/v1/server-instances/{id}/logs/events`
- **THEN** buffering is disabled for that location
- **AND** the route does not require WebSocket upgrade headers
@@ -0,0 +1,29 @@
## Prompt Boundaries
- Positive prompt (正向提示词): Realtime server logs and the management terminal must use one platform-owned push stream per open view, with bounded history replay and no periodic `/log-streams/query` polling.
- Directional prompt (方向提示词): Work inside `platform/`, `platform_web/`, and `run/`, preserve durable Run log ingest, plugin-declared log-source ownership, platform session authorization, existing UI controls, and Nginx proxying; verify with focused Go/frontend tests plus structure checks.
- Boundary prompt (任务边界): Do not add browser-to-Run sockets, raw credentials, host paths, plugin-owned auth keys, billing/cloud workflows, or game-specific SCUM paths/commands to platform/run code.
## 1. Platform SSE Contract
- [x] 1.1 Add safe log event DTOs and a server-scoped SSE route.
- [x] 1.2 Publish newly accepted log batch entries to non-blocking server-instance subscribers.
- [x] 1.3 Replay bounded recent history on stream open while keeping cursor query available for explicit history.
## 2. Frontend Realtime Logs
- [x] 2.1 Add EventSource client support for server log events.
- [x] 2.2 Replace live log drawer and management terminal `/log-streams/query` intervals with SSE.
- [x] 2.3 Replace server detail log polling with SSE history replay and live append.
## 3. Proxy And Verification
- [x] 3.1 Disable Nginx buffering for the log events route.
- [x] 3.2 Add focused backend/frontend tests for SSE behavior and no terminal polling.
- [x] 3.3 Run final verification: platform Go tests, platform_web tests/typecheck, `scripts/check-structure.sh`, and strict OpenSpec validation.
## 4. Run Process Log Capture
- [x] 4.1 Capture supervised process stdout/stderr through Run-owned durable capture files instead of game-specific log inspection.
- [x] 4.2 Resume managed process log tailing after Run restart using the persisted process journal and capture offsets.
- [x] 4.3 Keep plugin-declared log source keys optional for runtime readiness; explicit file backfill still uses the requested plugin source.