Skip to content

Commit 5b1e0ab

Browse files
authored
Replace silent except Exception: pass with structured logging in workspace/bubble paths (closes #66) (#76)
* initial implementation for replacing except-pass * chore: refresh requirements-lock.txt for click 8.4.1 Linux CI pip-compile now resolves click==8.4.1 within the flask bound; the lock still pinned 8.4.0, which failed the lockfile freshness job. * fix: code rabbitai comments * fix: test failure with bubble none and pytest missing * fix: Log NULL KV rows instead of silently skipping them. * fix: resolve merge conflicts; apply review feedback on logging
1 parent 731c1e1 commit 5b1e0ab

11 files changed

Lines changed: 463 additions & 65 deletions

api/logs.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from flask import Blueprint, jsonify
1515

1616
from utils.workspace_path import resolve_workspace_path
17-
from utils.path_helpers import to_epoch_ms
17+
from utils.path_helpers import to_epoch_ms, warn_workspace_json_read
1818

1919
bp = Blueprint("logs", __name__)
2020
_logger = logging.getLogger(__name__)
@@ -48,8 +48,12 @@ def get_logs():
4848
try:
4949
bubble = json.loads(row["value"])
5050
chat_map.setdefault(chat_id, []).append(bubble)
51-
except Exception:
52-
pass
51+
except Exception as e:
52+
_logger.warning(
53+
"Failed to decode bubble row %s: %s",
54+
row["key"],
55+
e,
56+
)
5357

5458
for chat_id, bubbles in chat_map.items():
5559
bubbles = [b for b in bubbles if isinstance(b, dict)]
@@ -90,8 +94,8 @@ def get_logs():
9094
with open(wj_path, "r", encoding="utf-8") as f:
9195
wd = json.load(f)
9296
workspace_folder = wd.get("folder")
93-
except Exception:
94-
pass
97+
except Exception as e:
98+
warn_workspace_json_read(_logger, name, e)
9599

96100
try:
97101
# closing() guarantees .close() on scope exit (issue #17).
@@ -130,10 +134,18 @@ def get_logs():
130134
"type": "composer",
131135
"messageCount": len(c.get("conversation") or []),
132136
})
133-
except Exception:
134-
pass
135-
except Exception:
136-
pass
137+
except Exception as e:
138+
_logger.warning(
139+
"Failed to read logs from workspace %s: %s",
140+
name,
141+
e,
142+
)
143+
except Exception as e:
144+
_logger.warning(
145+
"Failed to iterate workspaces under %s: %s",
146+
workspace_path,
147+
e,
148+
)
137149

138150
logs.sort(key=lambda log: log.get("timestamp") or 0, reverse=True)
139151
return jsonify({"logs": logs})

api/search.py

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from utils.exclusion_rules import build_searchable_text, is_excluded_by_rules
1818
from utils.workspace_path import resolve_workspace_path, get_cli_chats_path
19-
from utils.path_helpers import to_epoch_ms
19+
from utils.path_helpers import to_epoch_ms, warn_workspace_json_read
2020
from utils.text_extract import extract_text_from_bubble
2121
from utils.cli_chat_reader import list_cli_projects, traverse_blobs, messages_to_bubbles
2222
from models import Bubble, Composer, SchemaError
@@ -114,10 +114,14 @@ def search():
114114
fn = parts[-1] if parts else None
115115
if fn:
116116
ws_id_to_name[name] = _url_unquote(fn)
117-
except Exception:
118-
pass
119-
except Exception:
120-
pass
117+
except Exception as e:
118+
warn_workspace_json_read(_logger, name, e)
119+
except Exception as e:
120+
_logger.warning(
121+
"Failed to list workspace entries under %s: %s",
122+
workspace_path,
123+
e,
124+
)
121125

122126
# Build composer → workspace mapping
123127
composer_id_to_ws = {}
@@ -139,8 +143,12 @@ def search():
139143
cid = c.get("composerId") if isinstance(c, dict) else None
140144
if cid:
141145
composer_id_to_ws[cid] = entry["name"]
142-
except Exception:
143-
pass
146+
except Exception as e:
147+
_logger.warning(
148+
"Failed to load composer mapping from workspace %s: %s",
149+
entry["name"],
150+
e,
151+
)
144152

145153
# Load bubble text for searching
146154
bubble_map = {}
@@ -261,8 +269,12 @@ def search():
261269
"matchingText": matching_text,
262270
"type": "composer",
263271
})
264-
except Exception:
265-
pass
272+
except Exception as e:
273+
_logger.warning(
274+
"Failed to process Composer from composerData:%s during search: %s",
275+
composer_id,
276+
e,
277+
)
266278

267279
except Exception:
268280
_logger.exception("Error searching global storage")
@@ -288,8 +300,8 @@ def search():
288300
with open(wj_path, "r", encoding="utf-8") as f:
289301
wd = json.load(f)
290302
workspace_folder = wd.get("folder")
291-
except Exception:
292-
pass
303+
except Exception as e:
304+
warn_workspace_json_read(_logger, name, e)
293305
workspace_name = _workspace_display_name_from_folder(workspace_folder, fallback=name)
294306

295307
# try/finally guarantees .close() on every exit path (issue #17).
@@ -362,13 +374,21 @@ def search():
362374
"type": "chat",
363375
})
364376

365-
except Exception:
366-
pass
377+
except Exception as e:
378+
_logger.warning(
379+
"Failed to search legacy workspace %s: %s",
380+
name,
381+
e,
382+
)
367383
finally:
368384
if conn is not None:
369385
conn.close()
370-
except Exception:
371-
pass
386+
except Exception as e:
387+
_logger.warning(
388+
"Failed to iterate legacy workspaces under %s: %s",
389+
workspace_path,
390+
e,
391+
)
372392

373393
# ---------------------------------------------------------------
374394
# Search Cursor CLI sessions (only for type=all)
@@ -386,7 +406,12 @@ def search():
386406

387407
try:
388408
messages = traverse_blobs(session["db_path"])
389-
except Exception:
409+
except Exception as e:
410+
_logger.warning(
411+
"Failed to traverse CLI session blobs for %s: %s",
412+
session_id,
413+
e,
414+
)
390415
continue
391416

392417
bubbles = messages_to_bubbles(messages, created_ms)

api/workspaces.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515

1616
from utils.workspace_path import resolve_workspace_path, get_cli_chats_path
1717
from utils.cli_chat_reader import list_cli_projects
18-
from utils.path_helpers import get_workspace_folder_paths, get_workspace_display_name
18+
from utils.path_helpers import (
19+
get_workspace_folder_paths,
20+
get_workspace_display_name,
21+
warn_workspace_json_read,
22+
)
1923
from utils.workspace_descriptor import read_json_file
2024
from services.workspace_resolver import (
2125
_infer_workspace_name_from_context,
@@ -117,7 +121,8 @@ def get_workspace(workspace_id):
117121
inferred = _infer_workspace_name_from_context(workspace_path, workspace_id)
118122
if inferred:
119123
workspace_name = inferred
120-
except Exception:
124+
except Exception as e:
125+
warn_workspace_json_read(_logger, workspace_id, e)
121126
inferred = _infer_workspace_name_from_context(workspace_path, workspace_id)
122127
if inferred:
123128
workspace_name = inferred

requirements-lock.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# Lock is generated on Linux (CI / update-lock.yml). Windows-only transitives (e.g.
77
# colorama via click) are omitted — pip still installs them on Windows when needed.
88
blinker==1.9.0 # via flask
9-
click==8.4.0 # via flask
9+
click==8.4.1 # via flask
1010
defusedxml==0.7.1 # via fpdf2
1111
flask==3.1.3 # via -r requirements.txt
1212
fonttools==4.63.0 # via fpdf2

services/workspace_listing.py

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
from __future__ import annotations
22

33
import json
4+
import logging
45
import os
56
import sqlite3
67
from datetime import datetime, timezone
78

9+
_logger = logging.getLogger(__name__)
10+
811
from utils.cli_chat_reader import list_cli_projects
912
from utils.exclusion_rules import build_searchable_text, is_excluded_by_rules
1013
from utils.path_helpers import (
1114
get_workspace_folder_paths,
1215
normalize_file_path,
1316
to_epoch_ms,
17+
warn_workspace_json_read,
1418
)
1519
from utils.workspace_descriptor import read_json_file
1620
from utils.workspace_path import get_cli_chats_path
21+
from models import Composer, SchemaError
1722
from services.workspace_db import (
1823
_build_composer_id_to_workspace_id,
1924
_collect_invalid_workspace_ids,
@@ -72,7 +77,32 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list:
7277
for row in composer_rows:
7378
cid = row["key"].split(":")[1]
7479
try:
75-
cd = json.loads(row["value"])
80+
parsed = json.loads(row["value"])
81+
except (json.JSONDecodeError, TypeError, ValueError) as e:
82+
_logger.warning(
83+
"Failed to decode Composer from composerData:%s: %s",
84+
cid,
85+
e,
86+
)
87+
continue
88+
if not isinstance(parsed, dict):
89+
_logger.warning(
90+
"Failed to parse Composer from composerData:%s: expected object, got %s",
91+
cid,
92+
type(parsed).__name__,
93+
)
94+
continue
95+
try:
96+
composer = Composer.from_dict(parsed, composer_id=cid)
97+
except SchemaError as e:
98+
_logger.warning(
99+
"Failed to parse Composer from composerData:%s: %s",
100+
cid,
101+
e,
102+
)
103+
continue
104+
cd = composer.raw
105+
try:
76106
pid = _determine_project_for_conversation(
77107
cd, cid, project_layouts_map,
78108
project_name_map, workspace_path_map,
@@ -98,10 +128,14 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list:
98128
"lastUpdatedAt": to_epoch_ms(cd.get("lastUpdatedAt")) or to_epoch_ms(cd.get("createdAt")) or 0,
99129
"createdAt": to_epoch_ms(cd.get("createdAt")) or 0,
100130
})
101-
except Exception:
102-
pass
131+
except Exception as e:
132+
_logger.warning(
133+
"Failed to process Composer from composerData:%s: %s",
134+
cid,
135+
e,
136+
)
103137
except Exception:
104-
pass
138+
_logger.exception("Failed to load composer rows from global storage")
105139

106140
# Group workspace entries by normalized folder path
107141
folder_to_entries: dict[str, list] = {}
@@ -114,8 +148,8 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list:
114148
first_folder = folders[0] if folders else None
115149
if first_folder:
116150
norm_folder = normalize_file_path(first_folder)
117-
except Exception:
118-
pass
151+
except Exception as e:
152+
warn_workspace_json_read(_logger, entry["name"], e)
119153
if not norm_folder:
120154
norm_folder = entry["name"] # fallback to workspace ID
121155
entry_folder_map[entry["name"]] = norm_folder
@@ -139,7 +173,12 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list:
139173
for e in group
140174
if os.path.isfile(os.path.join(workspace_path, e["name"], "state.vscdb"))
141175
)
142-
except Exception:
176+
except Exception as e:
177+
_logger.warning(
178+
"Failed to resolve mtime for workspace folder %s: %s",
179+
norm_folder,
180+
e,
181+
)
143182
mtime = 0
144183

145184
workspace_name = _get_workspace_display_name(workspace_path, primary["name"])
@@ -238,7 +277,7 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list:
238277
"source": "cli",
239278
})
240279
except Exception as e:
241-
print(f"Failed to load CLI projects: {e}")
280+
_logger.warning("Failed to load CLI projects: %s", e)
242281

243282
projects.sort(key=lambda p: p["lastModified"], reverse=True)
244283
return projects

0 commit comments

Comments
 (0)