Skip to content

FIX: Reduce logs panel noise and make execution messages readable (OR-1457)#59

Open
abhizipstack wants to merge 1 commit intomainfrom
fix/logs-panel-noise-and-meaningful-messages
Open

FIX: Reduce logs panel noise and make execution messages readable (OR-1457)#59
abhizipstack wants to merge 1 commit intomainfrom
fix/logs-panel-noise-and-meaningful-messages

Conversation

@abhizipstack
Copy link
Copy Markdown
Contributor

@abhizipstack abhizipstack commented Apr 15, 2026

What

  • Demoted `UsingCachedObject` from `InfoLevel` to `DebugLevel` so the per-query "Using Cached Object of bigquery_connection_object" spam no longer reaches the UI at default verbosity.
  • Fixed `Processing Model : ""` — use `cls.name` (fallback `file_name`) instead of `str(cls)` which was empty for dynamic no-code model classes.
  • Stopped `str(Enum)` from leaking dotted enum names (`ExecStatus.Success`, `Materialization.EPHEMERAL`, etc.) into user-visible messages; use `.value` so the UI shows `SUCCESS` / `OK` / `TABLE`.
  • Fixed the summary counter bug — `parse_and_fire_reports` compared `end_status` against `ExecStatus.Success`/`Error`, but `end_status` is set to `OK`/`Fail`. Counters stayed at zero, so `DONE PASS=0 WARN=0 ERROR=0 SKIP=0 TOTAL=0` showed even after successful runs. Now compares against correct end-status values.
  • Added a log-level selector to the bottom logs tab: `All logs / Info & above / Warnings & errors / Errors only`. Default `Info & above` (hides debug noise out of the box). Choice persists in `localStorage`.
  • Each log row is tinted by severity (grey / default / amber / red) for quick scanning.

Why

Bottom logs panel was dominated by dev-internal debug lines and leaky enum reprs:
```
06:16:09 [info] [ThreadPool]: Processing Model : "".
06:16:09 [info] [ThreadPool]: Executing Model Node: ... Materialization: Materialization.EPHEMERAL ...
06:16:09 [info] [ThreadPool]: 1 of 2 ExecStatus.Success .................................... [ ExecStatus.OK ]
06:16:09 [info] [ThreadPool]: DONE PASS=0 WARN=0 ERROR=0 SKIP=0 TOTAL=0
```
Users couldn't tell what actually happened after an action. After this change, the same run reads:
```
06:16:09 [info] [ThreadPool]: Processing Model : "MModelA".
06:16:09 [info] [ThreadPool]: Executing Model Node: ... Materialization: TABLE ...
06:16:09 [info] [ThreadPool]: 1 of 2 SUCCESS MModelA .......... [ OK ]
06:16:09 [info] [ThreadPool]: DONE PASS=2 WARN=0 ERROR=0 SKIP=0 TOTAL=2
```

How

Backend (`backend/visitran/`)

  • `events/types.py:472` — `UsingCachedObject(InfoLevel) → DebugLevel`.
  • `events/printer.py:89-96` — compare `end_status` to `OK`/`Fail`/`Warn`/`Skipped` values (was comparing to `Success`/`Error`).
  • `visitran.py` — replaced `str(ExecStatus.X)` with `ExecStatus.X.value` everywhere (5 call sites). Same for `node.materialization`.
  • `visitran.py:515` — `ProcessingModel(cls=getattr(cls, "name", "") or file_name)`.

Frontend (`frontend/src/ide/editor/no-code-model/no-code-model.jsx`)

  • Socket handler stores `{ level, message }` (was a raw string) and reads `data?.data?.level`.
  • New `Select` above the logs tab bound to `logsLevel` state, initialized from `localStorage.getItem("visitran.logsLevel") || "info"`.
  • `LOG_LEVEL_RANK` threshold filters the rendered list; `LOG_LEVEL_COLOR` tints rows.

Can this PR break any existing features?

Low risk:

  • Log socket payload already carried `level` — we just started using it. Missing level falls back to `info`.
  • Old string-only entries in `logsInfo` would have crashed the render, but the list starts empty per session and no other writer exists.
  • Enum `.value` change: external consumers that parsed `BaseResult.status == "ExecStatus.Success"` would break, but the only consumer is `parse_and_fire_reports` in the same commit and it's been updated.
  • `UsingCachedObject` demotion to debug means operators who rely on seeing cached-connection hits must select "All logs" (documented via the dropdown label).

Database Migrations

None.

Env Config

None.

Relevant Docs

None.

Related Issues or PRs

Dependencies Versions

No changes.

Notes on Testing

  1. Open a no-code model backed by BigQuery, run it. Logs panel should no longer flood with "Using Cached Object of bigquery_connection_object".
  2. Add a transformation (e.g. Sort) to a Postgres-backed model; the "Processing Model" line should show the actual model name (not empty quotes), and "Executing Model Node" should show `Materialization: TABLE` not `Materialization.TABLE`.
  3. After a successful run, the summary line should read `DONE PASS=N ...` with the correct non-zero count.
  4. Change the log-level selector to "All logs" and reload the page — selection should persist.
  5. Trigger an error; the row should appear in red when the selector is "Errors only".

Screenshots

TBD

Checklist

I have read and understood the Contribution Guidelines.

Layer 1 — silence the worst offender
- Demote UsingCachedObject from InfoLevel to DebugLevel so the
  per-query "Using Cached Object of bigquery_connection_object" spam
  no longer reaches the UI at default verbosity.

Layer 2A — fix data bugs leaking dev internals into the UI
- ProcessingModel: avoid Processing Model : "" by using cls.__name__
  with file_name as a fallback, instead of str(cls) which can be empty
  for dynamically generated no-code model classes.
- ExecStatus / Materialization: stop str(Enum) from leaking the dotted
  enum names (ExecStatus.Success, Materialization.EPHEMERAL) into user
  messages; use .value so the UI shows SUCCESS / OK / TABLE.
- Summary counts: parse_and_fire_reports compared end_status against
  ExecStatus.Success/Error but end_status is set to OK/Fail, so
  pass/error counters stayed zero. Compare against the correct
  end-status values so DONE PASS=N now reflects reality.

Frontend
- Socket handler now captures the level alongside the message; each
  log entry is stored as { level, message } instead of a raw string.
- New log-level selector in the bottom logs tab:
    All logs / Info & above / Warnings & errors / Errors only
  Default is "Info & above" (hides debug noise out of the box).
  Choice persists in localStorage under visitran.logsLevel.
- Log rows are tinted by severity (grey/default/amber/red) for quick
  scanning when All is selected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@abhizipstack abhizipstack requested review from a team as code owners April 15, 2026 06:28
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 15, 2026

Greptile Summary

This PR fixes several log-quality issues: enum .value is used consistently throughout visitran.py (eliminating ExecStatus.Success string leakage), the summary counter comparisons in printer.py are corrected to match the actual end_status values, UsingCachedObject is demoted to debug level, and a log-level filter with localStorage persistence is added to the frontend.

  • P1 — socket cleanup leak (no-code-model.jsx line 1826): The new cleanup function omits newSocket.disconnect(), leaving the socket alive after unmount and risking setState calls on an unmounted component. It also references the cookie sessionId to un-register the listener, while the listener was registered under the socket-server-assigned ID — if these differ, the listener is never removed.

Confidence Score: 4/5

Safe to merge after addressing the socket cleanup leak; backend changes are correct and low-risk.

All four backend changes are straightforward and correct. The frontend filter feature works as intended. The one P1 issue — missing newSocket.disconnect() and the potential session-ID mismatch in the cleanup — is a real resource leak introduced by this PR that should be fixed before shipping.

frontend/src/ide/editor/no-code-model/no-code-model.jsx — socket cleanup at line 1826

Important Files Changed

Filename Overview
backend/visitran/events/printer.py Fixes counter bug: replaces incorrect str(ExecStatus.X) comparisons with ExecStatus.X.value and converts individual if to elif chain — both changes are correct.
backend/visitran/events/types.py Demotes UsingCachedObject from InfoLevel to DebugLevel to suppress per-query cache-hit noise from the default UI view — straightforward one-line change.
backend/visitran/visitran.py Consistently replaces str(ExecStatus.X) with .value across all call sites and fixes ProcessingModel to use cls.__name__ instead of str(cls) — all changes look correct.
frontend/src/ide/editor/no-code-model/no-code-model.jsx Adds log-level filter UI with localStorage persistence and per-level row colouring; introduces an incomplete socket cleanup (missing disconnect() and potential session-ID mismatch) and recreates filter constants on every render.

Sequence Diagram

sequenceDiagram
    participant VT as visitran.py
    participant PR as printer.py
    participant EV as events/types.py
    participant SK as Socket Server
    participant FE as no-code-model.jsx

    VT->>EV: fire_event(ExecutingModelNode(materialization=.value))
    VT->>EV: fire_event(ProcessingModel(cls=cls.__name__ or file_name))
    EV-->>SK: emit logs:{sessionId} {level, message}

    VT->>PR: BASE_RESULT.append(BaseResult(end_status=ExecStatus.OK.value))
    VT->>PR: parse_and_fire_reports()
    PR->>PR: end_status == OK → pass_count++
    PR->>EV: fire_event(EndSummaryReportCounts(pass=N ...))
    EV-->>SK: emit logs:{sessionId} {level:info, message:DONE PASS=N ...}

    Note over EV: UsingCachedObject now DebugLevel

    SK-->>FE: logs:{sessionId} {data: {level, message}}
    FE->>FE: setLogsInfo([...old, { level, message }])
    FE->>FE: filteredLogs = filter by LOG_LEVEL_RANK
    FE->>FE: render rows tinted by LOG_LEVEL_COLOR[level]
Loading

Comments Outside Diff (1)

  1. frontend/src/ide/editor/no-code-model/no-code-model.jsx, line 1826-1829 (link)

    P1 Incomplete socket cleanup — connection leaks on unmount

    The cleanup function calls newSocket.off(...) but never calls newSocket.disconnect(), so the underlying socket connection stays alive after the component unmounts. This will trigger setLogsInfo on an unmounted component whenever a new log message arrives, causing React's "update on unmounted component" warning and a memory leak.

    There's also a session-ID mismatch: the listener was registered on logs:${innerSessionId} (line 1810, from the socket "session_id" event), while the cleanup removes logs:${outerSessionId} (line 153, Cookies.get("sessionid")). If the socket server assigns a different ID than the cookie, the listener is never removed.

    If the server-assigned session ID can differ from the cookie value, innerSessionId should be hoisted to a variable in the useEffect scope so the cleanup can reference the correct channel.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: frontend/src/ide/editor/no-code-model/no-code-model.jsx
    Line: 1826-1829
    
    Comment:
    **Incomplete socket cleanup — connection leaks on unmount**
    
    The cleanup function calls `newSocket.off(...)` but never calls `newSocket.disconnect()`, so the underlying socket connection stays alive after the component unmounts. This will trigger `setLogsInfo` on an unmounted component whenever a new log message arrives, causing React's "update on unmounted component" warning and a memory leak.
    
    There's also a session-ID mismatch: the listener was registered on `logs:${innerSessionId}` (line 1810, from the socket `"session_id"` event), while the cleanup removes `logs:${outerSessionId}` (line 153, `Cookies.get("sessionid")`). If the socket server assigns a different ID than the cookie, the listener is never removed.
    
    
    
    If the server-assigned session ID can differ from the cookie value, `innerSessionId` should be hoisted to a variable in the `useEffect` scope so the cleanup can reference the correct channel.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: frontend/src/ide/editor/no-code-model/no-code-model.jsx
Line: 1826-1829

Comment:
**Incomplete socket cleanup — connection leaks on unmount**

The cleanup function calls `newSocket.off(...)` but never calls `newSocket.disconnect()`, so the underlying socket connection stays alive after the component unmounts. This will trigger `setLogsInfo` on an unmounted component whenever a new log message arrives, causing React's "update on unmounted component" warning and a memory leak.

There's also a session-ID mismatch: the listener was registered on `logs:${innerSessionId}` (line 1810, from the socket `"session_id"` event), while the cleanup removes `logs:${outerSessionId}` (line 153, `Cookies.get("sessionid")`). If the socket server assigns a different ID than the cookie, the listener is never removed.

```suggestion
    return () => {
      newSocket.off(`logs:${sessionId}`);
      newSocket.disconnect();
    };
```

If the server-assigned session ID can differ from the cookie value, `innerSessionId` should be hoisted to a variable in the `useEffect` scope so the cleanup can reference the correct channel.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: frontend/src/ide/editor/no-code-model/no-code-model.jsx
Line: 372-383

Comment:
**Constants and derived state recreated on every render**

`LOG_LEVEL_RANK`, `LOG_LEVEL_COLOR`, and `filteredLogs` are all recreated on every render. As `logsInfo` grows (common in long runs), the filter pass over the full array on every unrelated state change will become noticeable. Move the two lookup objects outside the component and wrap `filteredLogs` in `useMemo`:

```js
// outside the component
const LOG_LEVEL_RANK = { debug: 0, info: 1, warn: 2, warning: 2, error: 3 };
const LOG_LEVEL_COLOR = { debug: "#8c8c8c", info: undefined, warn: "#d48806", warning: "#d48806", error: "#cf1322" };

// inside the component
const filteredLogs = useMemo(
  () => logsInfo.filter(
    (entry) => (LOG_LEVEL_RANK[entry.level] ?? 1) >= (LOG_LEVEL_RANK[logsLevel] ?? 1)
  ),
  [logsInfo, logsLevel]
);
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: reduce logs-panel noise and make me..." | Re-trigger Greptile

Comment on lines +372 to +383
const LOG_LEVEL_RANK = { debug: 0, info: 1, warn: 2, warning: 2, error: 3 };
const LOG_LEVEL_COLOR = {
debug: "#8c8c8c",
info: undefined,
warn: "#d48806",
warning: "#d48806",
error: "#cf1322",
};
const filteredLogs = logsInfo.filter(
(entry) =>
(LOG_LEVEL_RANK[entry.level] ?? 1) >= (LOG_LEVEL_RANK[logsLevel] ?? 1)
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Constants and derived state recreated on every render

LOG_LEVEL_RANK, LOG_LEVEL_COLOR, and filteredLogs are all recreated on every render. As logsInfo grows (common in long runs), the filter pass over the full array on every unrelated state change will become noticeable. Move the two lookup objects outside the component and wrap filteredLogs in useMemo:

// outside the component
const LOG_LEVEL_RANK = { debug: 0, info: 1, warn: 2, warning: 2, error: 3 };
const LOG_LEVEL_COLOR = { debug: "#8c8c8c", info: undefined, warn: "#d48806", warning: "#d48806", error: "#cf1322" };

// inside the component
const filteredLogs = useMemo(
  () => logsInfo.filter(
    (entry) => (LOG_LEVEL_RANK[entry.level] ?? 1) >= (LOG_LEVEL_RANK[logsLevel] ?? 1)
  ),
  [logsInfo, logsLevel]
);
Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/ide/editor/no-code-model/no-code-model.jsx
Line: 372-383

Comment:
**Constants and derived state recreated on every render**

`LOG_LEVEL_RANK`, `LOG_LEVEL_COLOR`, and `filteredLogs` are all recreated on every render. As `logsInfo` grows (common in long runs), the filter pass over the full array on every unrelated state change will become noticeable. Move the two lookup objects outside the component and wrap `filteredLogs` in `useMemo`:

```js
// outside the component
const LOG_LEVEL_RANK = { debug: 0, info: 1, warn: 2, warning: 2, error: 3 };
const LOG_LEVEL_COLOR = { debug: "#8c8c8c", info: undefined, warn: "#d48806", warning: "#d48806", error: "#cf1322" };

// inside the component
const filteredLogs = useMemo(
  () => logsInfo.filter(
    (entry) => (LOG_LEVEL_RANK[entry.level] ?? 1) >= (LOG_LEVEL_RANK[logsLevel] ?? 1)
  ),
  [logsInfo, logsLevel]
);
```

How can I resolve this? If you propose a fix, please make it concise.

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