Skip to content

feat: add log filtering and streaming endpoints - #41

Merged
atomeam merged 5 commits into
mainfrom
feature/log-filtering
May 18, 2026
Merged

feat: add log filtering and streaming endpoints#41
atomeam merged 5 commits into
mainfrom
feature/log-filtering

Conversation

@atomeam

@atomeam atomeam commented May 18, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add log filtering: ?step=observer&status=running&limit=10
  • Add SSE streaming: GET /api/logs/stream
  • Add /api/log-files for config

Endpoints

  • GET /api/logs — Read logs with filters
  • GET /api/logs?status=running — Filter by status
  • GET /api/logs?step=evaluator — Filter by step
  • GET /api/logs?limit=5 — Limit results
  • GET /api/logs/stream — SSE live stream
  • GET /api/log-files — Config list

@atomeam can click here to continue refining the PR

Summary by CodeRabbit

  • New Features

    • Load environment from local .env; key-status and fail-fast validation
    • Endpoints: key tests (Gemini, Notion), list log files, enhanced logs retrieval, real-time log stream
    • Repo existence/git check and endpoint to run configured local scripts
  • Bug Fixes

    • Gemini access now respects configured keys and returns a safe mock when absent
    • Notion client initialized only when a key is present
  • Chores

    • .gitignore updated; dependency (dotenv) bumped

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2183c714-e4d9-4b9a-b157-9ffdc13f30ae

📥 Commits

Reviewing files that changed from the base of the PR and between d8ae320 and 7d522bb.

⛔ Files ignored due to path filters (1)
  • unified-app/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • unified-app/.gitignore
  • unified-app/package.json
  • unified-app/server.js

📝 Walkthrough

Walkthrough

Loads .env, computes and validates runtime keys, adds key-test endpoints, repo existence and PowerShell script-run endpoints with JSONL structured logging, adds log-file listing, replaces log reading with JSONL parsing and filtering, adds SSE log stream, and gates callGemini on KEYS.GEMINI.

Changes

Server operational APIs

Layer / File(s) Summary
Startup keys, repo checks, script runner
unified-app/server.js
Loads .env, computes KEYS/KEY_STATUS, enforces FAIL_FAST, exposes GET /api/keys, GET /api/keys/gemini/test, GET /api/keys/notion/test, GET /api/repos/check, conditionally initializes Notion, and adds POST /api/run-script/:name to spawn PowerShell scripts and write structured JSONL logs.
Log-file listing, JSONL reader, SSE stream
unified-app/server.js
GET /api/log-files returns CONFIG.logs as { name, exists }; GET /api/logs parses JSONL, reverses order, filters by step/status, supports limit, and returns { entries, count, total, file, filters } or an error; GET /api/logs/stream is an SSE endpoint that can emit initial entries and periodic ping.
callGemini gating and URL update
unified-app/server.js
callGemini now requires KEYS.GEMINI, builds the Gemini request URL from it, and returns a mock when the key is missing.
Package and .gitignore tweaks
unified-app/package.json, unified-app/.gitignore
Bumps dotenv version and reorders dependencies in package.json; adds ignores for node_modules/, .env, .env.local, and *.log in .gitignore.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant API_Server
  participant PowerShell
  participant ServerLogs
  Client->>API_Server: POST /api/run-script/:name
  API_Server->>PowerShell: spawn pwsh/powershell (script path, cwd=APPS_DIR)
  PowerShell-->>API_Server: stdout / stderr chunks
  API_Server->>ServerLogs: write JSONL entries (running, output, success/error)
  PowerShell-->>API_Server: exit code / error
  API_Server-->>Client: JSON { exit, code, error? }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • atomeam/atomarcade-bridge#41: Yes—both PRs add/replace the GET /api/logs JSONL parsing with step/status/limit filtering and introduce the GET /api/logs/stream SSE endpoint (and GET /api/log-files), all implemented in unified-app/server.js.
  • atomeam/atomarcade-bridge#39: Both PRs touch unified-app/server.js log-related routing/handlers (this PR refactors local JSONL log access and adds SSE streaming; #39 added Notion-backed log access).

Poem

🐰 I hopped through keys and scripts this dawn,
I checked the repos, then watched logs spawn,
JSON lines whispered each running task,
Pings kept my heartbeat steady and fast,
PowerShell hummed — the rabbit clapped at last!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title mentions only log filtering and streaming endpoints, but the changeset also adds key validation, repo status checks, script execution, and refactors multiple other endpoints. Revise title to reflect the broader scope, such as 'feat: add operational endpoints for keys, repos, scripts, and log filtering' or split into multiple PRs by concern.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/log-filtering

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
unified-app/server.js (1)

75-75: ⚡ Quick win

Avoid duplicate GET /api/logs route declarations.

There is another /api/logs handler at Line 152-165. This earlier handler already sends a response, so the later one is shadowed and can drift from the actual API contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@unified-app/server.js` at line 75, There are two declarations of the same
Express route app.get('/api/logs') which causes the later handler to be
shadowed; remove the duplicate and consolidate any needed behavior into the
single handler (keep the original implementation that sends the response and
merge any additional logic from the later handler into it). Locate both
app.get('/api/logs') handlers in server.js, reconcile their implementations so
only one route remains, and ensure the final handler uses the existing req/res
flow and preserves any middleware or error handling that both versions relied
on.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@unified-app/server.js`:
- Around line 122-124: The current SSE handler only sets an interval named
interval that writes ping events via res.write and never watches or polls the
log file for new appended data, so live logs never stream after init; modify the
/api/logs/stream handler to attach a file watcher or a polling loop (using
fs.watch or periodic fs.stat + read with a tracked offset) that reads only
appended bytes from the target log file and calls res.write for each new log
line/event, ensure you reuse the existing res.write pattern (instead of only
sending pings), handle client disconnect by clearing interval/watcher/stream,
and keep variable names like interval and res intact so the change is localized
to the SSE handler logic.
- Around line 70-71: The /api/log-files route currently returns CONFIG.logs with
exists hardcoded to true; update the handler (the app.get('/api/log-files', ...)
route) to compute exists by checking the filesystem for each log path from
CONFIG.logs (use fs.existsSync or asynchronous fs.promises.stat with try/catch)
and return { name: l.name, exists: <result> } for each entry; ensure you
reference the log file path property on CONFIG.logs objects and handle missing
files without throwing (treat as exists: false).
- Around line 86-90: The handler currently reads limit from req.query and
directly does parseInt(limit) then entries.slice, which yields surprising
results for invalid/negative values; update the code around req.query/limit to
validate limit before slicing: if limit is undefined, leave entries unchanged;
otherwise parse with parseInt(limit, 10), ensure the parsed value is a
non-negative integer (use Number.isInteger and >= 0 or isNaN check) and if
invalid respond with a 400 client error (bad request) describing the problem;
only call entries.slice(0, n) when the validated n is a safe non-negative
integer.
- Line 106: The SSE response is using the wrong MIME type; update the header set
on the response (the res.setHeader call that currently sets 'Content-Type' to
'text/eventstream') to use the standard 'text/event-stream' value so EventSource
clients accept the stream; locate the res.setHeader invocation in server.js that
sets 'Content-Type' and change the value to 'text/event-stream'.

---

Nitpick comments:
In `@unified-app/server.js`:
- Line 75: There are two declarations of the same Express route
app.get('/api/logs') which causes the later handler to be shadowed; remove the
duplicate and consolidate any needed behavior into the single handler (keep the
original implementation that sends the response and merge any additional logic
from the later handler into it). Locate both app.get('/api/logs') handlers in
server.js, reconcile their implementations so only one route remains, and ensure
the final handler uses the existing req/res flow and preserves any middleware or
error handling that both versions relied on.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d1942f37-1bee-4cbd-a333-7803b24edf4a

📥 Commits

Reviewing files that changed from the base of the PR and between c856b7b and 9436bde.

📒 Files selected for processing (1)
  • unified-app/server.js

Comment thread unified-app/server.js
Comment on lines +70 to 71
app.get('/api/log-files', (req, res) => {
res.json(CONFIG.logs.map(l => ({ name: l.name, exists: true })));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Compute exists from the filesystem instead of hardcoding true.

/api/log-files currently reports all log files as present, which makes the existence flag inaccurate.

Proposed fix
 app.get('/api/log-files', (req, res) => {
-  res.json(CONFIG.logs.map(l => ({ name: l.name, exists: true })));
+  res.json(
+    CONFIG.logs.map(l => ({
+      name: l.name,
+      exists: existsSync(join(__dirname, l.path))
+    }))
+  );
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@unified-app/server.js` around lines 70 - 71, The /api/log-files route
currently returns CONFIG.logs with exists hardcoded to true; update the handler
(the app.get('/api/log-files', ...) route) to compute exists by checking the
filesystem for each log path from CONFIG.logs (use fs.existsSync or asynchronous
fs.promises.stat with try/catch) and return { name: l.name, exists: <result> }
for each entry; ensure you reference the log file path property on CONFIG.logs
objects and handle missing files without throwing (treat as exists: false).

Comment thread unified-app/server.js
Comment on lines +86 to +90
const { step, status, limit } = req.query;
let entries = allEntries;
if (step) entries = entries.filter(e => e.step === step);
if (status) entries = entries.filter(e => e.status === status);
if (limit) entries = entries.slice(0, parseInt(limit));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate limit before slicing entries.

Invalid or negative values (for example, "abc" or -1) currently produce surprising results instead of a clear client error.

Proposed fix
   const { step, status, limit } = req.query;
   let entries = allEntries;
   if (step) entries = entries.filter(e => e.step === step);
   if (status) entries = entries.filter(e => e.status === status);
-  if (limit) entries = entries.slice(0, parseInt(limit));
+  if (limit !== undefined) {
+    const parsedLimit = Number(limit);
+    if (!Number.isInteger(parsedLimit) || parsedLimit < 1) {
+      return res.status(400).json({ error: 'limit must be a positive integer' });
+    }
+    entries = entries.slice(0, parsedLimit);
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@unified-app/server.js` around lines 86 - 90, The handler currently reads
limit from req.query and directly does parseInt(limit) then entries.slice, which
yields surprising results for invalid/negative values; update the code around
req.query/limit to validate limit before slicing: if limit is undefined, leave
entries unchanged; otherwise parse with parseInt(limit, 10), ensure the parsed
value is a non-negative integer (use Number.isInteger and >= 0 or isNaN check)
and if invalid respond with a 400 client error (bad request) describing the
problem; only call entries.slice(0, n) when the validated n is a safe
non-negative integer.

Comment thread unified-app/server.js

// Stream logs (Server-Sent Events)
app.get('/api/logs/stream', (req, res) => {
res.setHeader('Content-Type', 'text/eventstream');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What is the required HTTP Content-Type header for Server-Sent Events (EventSource) responses?

💡 Result:

The required HTTP Content-Type header for Server-Sent Events (SSE) responses is text/event-stream [1][2][3]. According to the HTML Living Standard and the W3C specification for the EventSource API, servers must use this specific MIME type to correctly identify the response as an event stream [1][3][4]. If a server responds with a different Content-Type, or fails to provide one, the browser's EventSource interface will fail the connection [3][4]. Additionally, the event stream must be encoded as UTF-8 [1][3][4]. While the specification allows for an optional charset parameter (e.g., text/event-stream; charset=utf-8), it is generally unnecessary as UTF-8 is the required default [3].

Citations:


🏁 Script executed:

# First, find the server.js file and verify the content at line 106
find . -name "server.js" -type f | head -20

Repository: atomeam/atomarcade-bridge

Length of output: 93


🏁 Script executed:

# Search for instances of Content-Type headers related to event streams
rg "text/event" -A 1 -B 1

Repository: atomeam/atomarcade-bridge

Length of output: 278


🏁 Script executed:

# Specifically search for the incorrect and correct SSE header variants
rg "text/eventstream|text/event-stream" -n

Repository: atomeam/atomarcade-bridge

Length of output: 148


🏁 Script executed:

# Get more context around both occurrences
sed -n '104,110p' unified-app/server.js
sed -n '156,162p' unified-app/server.js

Repository: atomeam/atomarcade-bridge

Length of output: 423


🏁 Script executed:

# Check if there are any other instances we might have missed
rg "eventstream" -n

Repository: atomeam/atomarcade-bridge

Length of output: 148


🏁 Script executed:

# Also check the exact line numbers and their context for line 158
sed -n '156,162p' unified-app/server.js

Repository: atomeam/atomarcade-bridge

Length of output: 304


🏁 Script executed:

# Count total occurrences and show them with line numbers
wc -l unified-app/server.js
rg "text/eventstream" -n --no-heading

Repository: atomeam/atomarcade-bridge

Length of output: 174


🏁 Script executed:

# Show the exact line 106 to verify the review comment's accuracy
sed -n '106p' unified-app/server.js

Repository: atomeam/atomarcade-bridge

Length of output: 83


🏁 Script executed:

# Let me get a broader view around where the incorrect headers appear
sed -n '150,165p' unified-app/server.js

Repository: atomeam/atomarcade-bridge

Length of output: 563


🏁 Script executed:

# Double-check if there are any other SSE-related endpoints with this issue
rg "get\(|post\(" -A 5 | rg -B 5 "eventstream"

Repository: atomeam/atomarcade-bridge

Length of output: 422


Fix SSE Content-Type header to the standard text/event-stream at line 158.

The header text/eventstream is incorrect and violates the EventSource specification. Per W3C standards and the HTML Living Standard, the correct MIME type for Server-Sent Events responses is text/event-stream. Using the wrong header causes EventSource clients to reject the stream and fail the connection.

Proposed fix
-  res.setHeader('Content-Type', 'text/eventstream');
+  res.setHeader('Content-Type', 'text/event-stream');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
res.setHeader('Content-Type', 'text/eventstream');
res.setHeader('Content-Type', 'text/event-stream');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@unified-app/server.js` at line 106, The SSE response is using the wrong MIME
type; update the header set on the response (the res.setHeader call that
currently sets 'Content-Type' to 'text/eventstream') to use the standard
'text/event-stream' value so EventSource clients accept the stream; locate the
res.setHeader invocation in server.js that sets 'Content-Type' and change the
value to 'text/event-stream'.

Comment thread unified-app/server.js
Comment on lines +122 to +124
const interval = setInterval(() => {
res.write(`data: ${JSON.stringify({ type: 'ping', ts: new Date().toISOString() })}\n\n`);
}, 15000);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

/api/logs/stream is heartbeat-only after init and does not stream new logs.

After the initial payload, the interval only sends ping; no file watch/poll logic emits appended log entries, so this does not satisfy live log streaming behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@unified-app/server.js` around lines 122 - 124, The current SSE handler only
sets an interval named interval that writes ping events via res.write and never
watches or polls the log file for new appended data, so live logs never stream
after init; modify the /api/logs/stream handler to attach a file watcher or a
polling loop (using fs.watch or periodic fs.stat + read with a tracked offset)
that reads only appended bytes from the target log file and calls res.write for
each new log line/event, ensure you reuse the existing res.write pattern
(instead of only sending pings), handle client disconnect by clearing
interval/watcher/stream, and keep variable names like interval and res intact so
the change is localized to the SSE handler logic.

@atomeam
atomeam merged commit c1d0e75 into main May 18, 2026
1 check passed
@atomeam
atomeam deleted the feature/log-filtering branch May 18, 2026 09:37
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