Skip to content

[cueweb] Add optional Loki backend for frame log viewing#2447

Draft
ramonfigueiredo wants to merge 1 commit into
AcademySoftwareFoundation:masterfrom
ramonfigueiredo:cueweb-loki-log-viewer
Draft

[cueweb] Add optional Loki backend for frame log viewing#2447
ramonfigueiredo wants to merge 1 commit into
AcademySoftwareFoundation:masterfrom
ramonfigueiredo:cueweb-loki-log-viewer

Conversation

@ramonfigueiredo

@ramonfigueiredo ramonfigueiredo commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Related Issues

Summarize your change.

[cueweb] Add optional Loki backend for frame log viewing

When NEXT_PUBLIC_LOKI_URL is set, the frame log viewer queries Loki by frame_id instead of reading the on-disk .rqlog file, mirroring CueGUI's LokiViewPlugin. Falls back to the file-based viewer when unset.

  • lib/loki.ts: Loki HTTP API client (label values for frame attempts, query_range for log lines)
  • frames/[frame-name]: branch on the env flag; new LokiLogView reuses the same Monaco editor, version dropdown, and empty/loading states
  • Document NEXT_PUBLIC_LOKI_URL in README.md and .env.example
  • Add lib/loki.ts unit tests

Summary by CodeRabbit

  • New Features

    • Added optional Loki log backend for frame log viewer; when configured via NEXT_PUBLIC_LOKI_URL environment variable, the frame viewer queries Loki instead of reading local files.
    • New Loki-backed frame log viewer with manual refresh capability; gracefully falls back to file-based viewer when unconfigured.
  • Documentation

    • Updated README and .env.example with Loki configuration guidance.
  • Tests

    • Added comprehensive test suite for Loki integration.

When NEXT_PUBLIC_LOKI_URL is set, the frame log viewer queries Loki by frame_id instead of reading the on-disk .rqlog file, mirroring CueGUI's LokiViewPlugin. Falls back to the file-based viewer when unset.

- lib/loki.ts: Loki HTTP API client (label values for frame attempts, query_range for log lines)
- frames/[frame-name]: branch on the env flag; new LokiLogView reuses the same Monaco editor, version dropdown, and empty/loading states
- Document NEXT_PUBLIC_LOKI_URL in README.md and .env.example
- Add lib/loki.ts unit tests
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an optional Loki log backend to CueWeb's frame log viewer. A new TypeScript client (lib/loki.ts) queries a Loki server by frame_id and session_start_time. A new React component (loki-log-view.tsx) renders the Loki-sourced logs in a Monaco editor. The existing frame page (page.tsx) conditionally routes to the new component when NEXT_PUBLIC_LOKI_URL is set.

Changes

Loki Log Backend Integration

Layer / File(s) Summary
Loki client library and tests
cueweb/lib/loki.ts, cueweb/app/__tests__/loki.test.ts
Defines LokiLogVersion, response type interfaces, config helpers (getLokiUrl, isLokiEnabled), time converters (toUnixNano, unixSecondsToDateString), internal lokiGet HTTP helper, and exported query functions getFrameLogVersions and getFrameLogLines; Jest suite covers all public behaviors including URL construction, ordering, concatenation, empty results, and error paths.
LokiLogView React component
cueweb/app/frames/[frame-name]/loki-log-view.tsx
New LokiLogView component with LokiLogViewProps / LokiLogStatus types, async effects for loading versions and log lines with cancellation guards, Monaco editor in read-only mode with auto-scroll, version-selection dropdown, Refresh button, and overlay states for loading / empty / missing.
Frame page wiring and docs
cueweb/app/frames/[frame-name]/page.tsx, cueweb/.env.example, cueweb/README.md
Imports isLokiEnabled and LokiLogView, derives a lokiEnabled flag, short-circuits on-disk log-fetching effects, conditionally renders LokiLogView vs. the existing Monaco viewer; adds NEXT_PUBLIC_LOKI_URL documentation and env example entry.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant FramePage as page.tsx
  participant LokiLogView as LokiLogView
  participant LokiClient as lib/loki.ts
  participant LokiServer as Loki HTTP API

  User->>FramePage: navigate to frame
  FramePage->>LokiClient: isLokiEnabled()
  LokiClient-->>FramePage: true
  FramePage->>LokiLogView: render with frameId, startTime

  LokiLogView->>LokiClient: getFrameLogVersions(frameId, startTime)
  LokiClient->>LokiServer: GET /label/session_start_time/values?query={frame_id=...}
  LokiServer-->>LokiClient: string[] of session timestamps
  LokiClient-->>LokiLogView: LokiLogVersion[] sorted newest-first

  LokiLogView->>LokiClient: getFrameLogLines(frameId, sessionStartTime, startTime)
  LokiClient->>LokiServer: GET /query_range?query={session_start_time=...,frame_id=...}
  LokiServer-->>LokiClient: LokiStream[] with log values
  LokiClient-->>LokiLogView: concatenated log text
  LokiLogView-->>User: Monaco editor with log lines
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 Hippity-hoppity, logs from the sky,
No more rqlog files — Loki's nearby!
A frame_id query, a nanosecond start,
Monaco displays it, a work of pure art.
The rabbit approves — fresh logs, hip-hip-hooray! 🪵✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding an optional Loki backend for frame log viewing, which is the primary feature introduced across all modified files.
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 unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
cueweb/app/__tests__/loki.test.ts (1)

102-153: ⚡ Quick win

Add a regression test for cross-stream timestamp ordering.

The current happy-path asserts one stream only, so it won’t catch incorrect ordering when Loki returns multiple streams.

Suggested test case
 describe('getFrameLogLines', () => {
+  it('orders lines chronologically across multiple streams', async () => {
+    const loki = loadLoki('http://loki:3100');
+    mockFetch(() => ({
+      status: 'success',
+      data: {
+        result: [
+          { stream: { src: 'stderr' }, values: [['1700000002000000000', 'second']] },
+          { stream: { src: 'stdout' }, values: [['1700000001000000000', 'first']] },
+        ],
+      },
+    }));
+
+    await expect(
+      loki.getFrameLogLines('frame-123', '1700000000'),
+    ).resolves.toBe('first\nsecond');
+  });
🤖 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 `@cueweb/app/__tests__/loki.test.ts` around lines 102 - 153, The current test
for getFrameLogLines only validates ordering within a single stream. Add a new
test case within the describe block that mocks Loki to return multiple streams
with interleaved timestamps and verify that getFrameLogLines correctly orders
the log lines by timestamp across all streams rather than just concatenating
them in stream order. The test should set up at least two stream objects in the
result array, each with values that have timestamps interspersed with the other
stream's timestamps, and assert that the returned text reflects the correct
chronological ordering.
🤖 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 `@cueweb/lib/loki.ts`:
- Around line 175-198: The `getFrameLogLines` function uses a fixed start time
with direction=forward and a single query with a limit, which causes it to miss
recent logs when they exceed the limit boundary, and directly concatenating
lines from multiple streams scrambles their chronological order. To fix this,
implement pagination by tracking the last timestamp returned from each query and
using it as the start time for the next query to fetch all available logs, and
sort all collected lines chronologically before joining them together to ensure
proper ordering across streams like stdout and stderr.

---

Nitpick comments:
In `@cueweb/app/__tests__/loki.test.ts`:
- Around line 102-153: The current test for getFrameLogLines only validates
ordering within a single stream. Add a new test case within the describe block
that mocks Loki to return multiple streams with interleaved timestamps and
verify that getFrameLogLines correctly orders the log lines by timestamp across
all streams rather than just concatenating them in stream order. The test should
set up at least two stream objects in the result array, each with values that
have timestamps interspersed with the other stream's timestamps, and assert that
the returned text reflects the correct chronological ordering.
🪄 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

Run ID: 9c8a1620-9523-4ff1-9c44-6738901b0569

📥 Commits

Reviewing files that changed from the base of the PR and between ca5f70f and 4368fa7.

📒 Files selected for processing (6)
  • cueweb/.env.example
  • cueweb/README.md
  • cueweb/app/__tests__/loki.test.ts
  • cueweb/app/frames/[frame-name]/loki-log-view.tsx
  • cueweb/app/frames/[frame-name]/page.tsx
  • cueweb/lib/loki.ts

Comment thread cueweb/lib/loki.ts
Comment on lines +175 to +198
const params = new URLSearchParams({
query: selector,
direction: "forward",
limit: String(LOKI_QUERY_LIMIT),
});
// Prefer the explicit start time, otherwise derive it from the selected
// session so Loki doesn't reject the query for too-wide a range.
const start =
toUnixNano(startTime) ??
(sessionStartTime ? toUnixNano(parseFloat(sessionStartTime)) : undefined);
if (start) params.set("start", start);

const json = await lokiGet<LokiQueryResponse>(
`/loki/api/v1/query_range?${params.toString()}`,
);

const streams = json.data?.result ?? [];
const lines: string[] = [];
for (const stream of streams) {
for (const [, line] of stream.values ?? []) {
lines.push(line);
}
}
return lines.join("\n");

Copy link
Copy Markdown
Contributor

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

getFrameLogLines can miss recent logs and return lines out of chronological order.

With fixed start, direction=forward, and a single limited query, refresh can keep returning the same earliest window once logs exceed the limit. Also, concatenating stream blocks directly can scramble global ordering across streams (e.g., stdout/stderr).

Proposed fix
   const params = new URLSearchParams({
     query: selector,
-    direction: "forward",
+    // Prefer latest window so refresh surfaces recent output.
+    direction: "backward",
     limit: String(LOKI_QUERY_LIMIT),
   });
@@
   const streams = json.data?.result ?? [];
-  const lines: string[] = [];
-  for (const stream of streams) {
-    for (const [, line] of stream.values ?? []) {
-      lines.push(line);
-    }
-  }
-  return lines.join("\n");
+  const entries = streams.flatMap((stream) =>
+    (stream.values ?? []).map(([ts, line]) => ({ ts, line })),
+  );
+  entries.sort((a, b) => {
+    const at = BigInt(a.ts);
+    const bt = BigInt(b.ts);
+    return at < bt ? -1 : at > bt ? 1 : 0;
+  });
+  return entries.map((e) => e.line).join("\n");
📝 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
const params = new URLSearchParams({
query: selector,
direction: "forward",
limit: String(LOKI_QUERY_LIMIT),
});
// Prefer the explicit start time, otherwise derive it from the selected
// session so Loki doesn't reject the query for too-wide a range.
const start =
toUnixNano(startTime) ??
(sessionStartTime ? toUnixNano(parseFloat(sessionStartTime)) : undefined);
if (start) params.set("start", start);
const json = await lokiGet<LokiQueryResponse>(
`/loki/api/v1/query_range?${params.toString()}`,
);
const streams = json.data?.result ?? [];
const lines: string[] = [];
for (const stream of streams) {
for (const [, line] of stream.values ?? []) {
lines.push(line);
}
}
return lines.join("\n");
const params = new URLSearchParams({
query: selector,
// Prefer latest window so refresh surfaces recent output.
direction: "backward",
limit: String(LOKI_QUERY_LIMIT),
});
// Prefer the explicit start time, otherwise derive it from the selected
// session so Loki doesn't reject the query for too-wide a range.
const start =
toUnixNano(startTime) ??
(sessionStartTime ? toUnixNano(parseFloat(sessionStartTime)) : undefined);
if (start) params.set("start", start);
const json = await lokiGet<LokiQueryResponse>(
`/loki/api/v1/query_range?${params.toString()}`,
);
const streams = json.data?.result ?? [];
const entries = streams.flatMap((stream) =>
(stream.values ?? []).map(([ts, line]) => ({ ts, line })),
);
entries.sort((a, b) => {
const at = BigInt(a.ts);
const bt = BigInt(b.ts);
return at < bt ? -1 : at > bt ? 1 : 0;
});
return entries.map((e) => e.line).join("\n");
🤖 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 `@cueweb/lib/loki.ts` around lines 175 - 198, The `getFrameLogLines` function
uses a fixed start time with direction=forward and a single query with a limit,
which causes it to miss recent logs when they exceed the limit boundary, and
directly concatenating lines from multiple streams scrambles their chronological
order. To fix this, implement pagination by tracking the last timestamp returned
from each query and using it as the start time for the next query to fetch all
available logs, and sort all collected lines chronologically before joining them
together to ensure proper ordering across streams like stdout and stderr.

@ramonfigueiredo ramonfigueiredo marked this pull request as draft June 18, 2026 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant