Skip to content

feat: add websocket for app streams - #398

Open
ppatel9703 wants to merge 2 commits into
devfrom
app-logs
Open

feat: add websocket for app streams#398
ppatel9703 wants to merge 2 commits into
devfrom
app-logs

Conversation

@ppatel9703

@ppatel9703 ppatel9703 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Description

Delivery side of app-owner log visibility: pushes a project's app log live to the browser over the existing insight websocket, reusing infrastructure already built for a different feature (Claude Code transcript streaming) rather than a new endpoint. The file being tailed is written by Semoss#2616; the consumer is the Console panel in semoss-ui#3302.

Changes Made

src/prerna/websocket/AppLogStreamer.java (new)
Tails a project's app.log (via Semoss's AppLogManager.getLogFilePath) and broadcasts new lines to clients watching that project's insight, modeled on the existing ClaudeCodeHistoryStreamer:

  • On watch, sends the last ~50KB of the file immediately (so the console doesn't open blank), then tails going forward — re-stats and re-opens the file every 2s rather than holding one handle open, since CSI/NFS-backed volumes cache attributes per-handle and would otherwise never see another process's appends.
  • Holds back trailing partial lines (a write in progress) and re-reads them whole next cycle, so a line never gets split across polls.
  • Caps each broadcast line at 8192 characters. Some reactor logs entire response payloads through EngineLogger as a single line (observed: 150K+ characters, apparently a file-listing response logged whole) — without the cap, one such line dominates the entire history window and blows up a single WS message. The full line stays on disk untouched; only the broadcast is truncated.

src/prerna/websocket/InsightWebsocket.java
Adds a new "app_logs" watch type ({"action":"watch","type":"app_logs","projectId":"<id>"}), constructing an AppLogStreamer. Critically, this is gated before any streamer is created — the socket by itself only proves "logged in", not "allowed to see this project's logs" — so handleWatch now checks the requester is an owner of projectId (SecurityProjectUtils.getUserProjectPermission + AccessPermissionEnum.isOwner, same rule the reactor in Semoss#2616 uses) and sends an error frame instead of ever constructing a streamer for anyone else. Also fixes streamer-key collisions (type + ":" + roomId, which broke for app_logs since it has no roomId) and adds synchronized (session) around the watch_started/error sends (see concurrency fix below).

src/prerna/websocket/SocketSessionHandler.java
sendReturnData now wraps session.getBasicRemote().sendText(...) in synchronized (session). Tomcat's WS RemoteEndpoint throws IllegalStateException if two threads call sendText() on the same session concurrently — this had no synchronization before, for any streamer type including the pre-existing Claude Code one, so it predates this PR. Streaming app logs made it far more likely to actually trip, since there's now regular background write traffic on sessions that also receive other messages (e.g. a streamer thread pushing a line at the same moment the message-handling thread sends an ack).

How to Test

  1. Open a websocket to /insightSocket?insightId=<id>, send {"action":"watch","type":"app_logs","projectId":"<id-you-own>"} — expect {"action":"watch_started",...} followed by the last ~50KB of that project's app.log, then new lines as they're written.
  2. Same, with a projectId you don't own — expect {"action":"error","message":"Only project owners can view app logs"} and no streamer ever starts.
  3. Trigger a reactor that logs an oversized single line — confirm the broadcast is capped at 8192 chars with a ...[truncated, N more chars] suffix, not the full payload.
  4. Leave a watch open for a while under real traffic and confirm no IllegalStateException/WsRemoteEndpointImplBase$StateMachine.checkState errors in the log (the concurrency fix).

Notes

  • How the three repos fit together: Semoss (#2616) writes and owns the file this streams; this repo delivers it live; semoss-ui's Console panel is the only current consumer of this app_logs watch type.
  • No reactor exists for reading logs anymore — the old GetAppLogsReactor (Semoss) is deleted; this streamer covers both the live tail and the initial-history bootstrap it used to handle.
  • Found and fixed along the way (unrelated to app logs specifically, but surfaced by testing this): InsightWebsocket.handlePixel has a pre-existing NPE (insightId stays null after the if branch creates a new Insight, then gets passed into SocketSessionHandlerFactory.getHandler(null)) — not fixed in this PR, flagged as a separate follow-up since it's core pixel-execution infra a Console/app-logs socket never actually exercises (it only ever sends watch/unwatch, never pixel).

Review Updates

Addressed the following from review:

  • Stripped em-dashes/typographic characters from comments in AppLogStreamer.java and InsightWebsocket.java — this repo's Maven compiler plugin also enforces cp1252 source encoding.
  • InsightWebsocket.isProjectOwner(): added a getPrimaryLoginToken() == null check alongside the existing user == null check, before dereferencing the token — same guard as the analogous check in Semoss's SearchAppLogsReactor.

@ppatel9703
ppatel9703 requested a review from a team as a code owner August 4, 2026 16:48
@snyk-io

snyk-io Bot commented Aug 4, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

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