feat(app): stream live server logs to the management UI via SSE#45
Merged
Conversation
Adds a real-time log tail to the Logs panel: - LogsService.streamLogs(game, signal) – AsyncGenerator that polls FilterLogEvents every 2 s, de-duplicates by eventId, advances startTime on each tick, and exits cleanly when the AbortSignal fires. Queries the whole log group so task stop+start (new stream) is handled automatically. - GET /api/logs/:game/stream – @sse() endpoint wrapping the generator in an RxJS Observable. Teardown aborts the generator when the client disconnects. - ApiTokenGuard – falls back to ?token= query param when the Authorization header is absent, which is necessary because the browser's native EventSource cannot set custom headers. - LogsPanel.tsx – fetches a snapshot on game change then opens an EventSource stream that appends new lines (capped at 1 000). Pause buffers incoming lines; Resume flushes the buffer. Replaces the old manual Refresh button. - LogsService.test.ts – seven new streamLogs cases covering clean termination, multi-poll delivery, de-duplication, log-group naming, error recovery, and missing message fields. - docs/components/management-app.md – updated endpoint table, Live Logs description, auth notes, and LogsService blurb. IAM: logs:* already covers FilterLogEvents (docs/setup.md unchanged). https://claude.ai/code/session_01THkoGDYjJhojhtnUEYi4w9
Contributor
There was a problem hiding this comment.
Pull request overview
Adds live log tailing to the management UI using Server-Sent Events (SSE), backed by a new LogsService.streamLogs poller and an SSE controller endpoint, plus an auth guard update to support EventSource limitations.
Changes:
- Add
LogsService.streamLogs()async generator polling CloudWatchFilterLogEvents, plus an SSE endpointGET /api/logs/:game/stream. - Update
ApiTokenGuardto accept?token=whenAuthorizationis not present (for SSE/EventSource). - Update the web
LogsPanelto load a snapshot then stream new lines viaEventSource, and update docs/tests accordingly.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/docs/components/management-app.md | Documents the new SSE logs endpoint, streaming behavior, and auth fallback. |
| app/packages/web/src/components/LogsPanel.tsx | Replaces manual refresh with snapshot + SSE streaming, plus Pause/Resume buffering. |
| app/packages/server/src/services/LogsService.ts | Adds streamLogs() async generator and interruptible sleep for polling. |
| app/packages/server/src/services/LogsService.test.ts | Adds test coverage for streamLogs() polling, dedupe, termination, and error recovery. |
| app/packages/server/src/guards/api-token.guard.ts | Allows ?token= fallback when Authorization header is absent. |
| app/packages/server/src/controllers/logs.controller.ts | Adds @Sse(':game/stream') endpoint wrapping streamLogs() in an Observable. |
- sleepInterruptible: remove the abort listener on both the resolve and
reject paths so it doesn't accumulate on long-lived streams.
- streamLogs: pass { abortSignal: signal } to client.send() so an
in-flight FilterLogEvents call is cancelled promptly on disconnect.
- LogsPanel: await the snapshot before starting the SSE stream to
prevent a race where setLines(snapshot) overwrites already-streamed
lines. Start the stream even if the snapshot fetch fails.
https://claude.ai/code/session_01THkoGDYjJhojhtnUEYi4w9
RequestLoggerMiddleware logs req.query on every response; deleting the token from req.query in ApiTokenGuard (which runs before the finish handler) prevents it appearing in structured logs. https://claude.ai/code/session_01THkoGDYjJhojhtnUEYi4w9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #43
Adds a real-time log tail to the Logs panel:
LogsService.streamLogs(game, signal) – AsyncGenerator that polls
FilterLogEvents every 2 s, de-duplicates by eventId, advances
startTime on each tick, and exits cleanly when the AbortSignal fires.
Queries the whole log group so task stop+start (new stream) is handled
automatically.
GET /api/logs/:game/stream – @sse() endpoint wrapping the generator
in an RxJS Observable. Teardown aborts the generator when the client
disconnects.
ApiTokenGuard – falls back to ?token= query param when the
Authorization header is absent, which is necessary because the
browser's native EventSource cannot set custom headers.
LogsPanel.tsx – fetches a snapshot on game change then opens an
EventSource stream that appends new lines (capped at 1 000). Pause
buffers incoming lines; Resume flushes the buffer. Replaces the old
manual Refresh button.
LogsService.test.ts – seven new streamLogs cases covering clean
termination, multi-poll delivery, de-duplication, log-group naming,
error recovery, and missing message fields.
docs/components/management-app.md – updated endpoint table, Live Logs
description, auth notes, and LogsService blurb.
IAM: logs:* already covers FilterLogEvents (docs/setup.md unchanged).
https://claude.ai/code/session_01THkoGDYjJhojhtnUEYi4w9