Skip to content

Commit e6bae8c

Browse files
authored
Replace print() error output with structured logging (#68) (#77)
* initial implementation for replacing except-pass * fix: test failure with bubble none and pytest missing * fix: resolve merge conflicts; apply review feedback on logging * initial implementation of the issue requirements * fix: coderabbitai comments * fix: Broaden the decode guard to keep malformed KV rows non-fatal. * fix: replace remaining print and pattern. * fix: typecheck CI failure * fix: update export.py to use logger instead of print.
1 parent 5b1e0ab commit e6bae8c

12 files changed

Lines changed: 217 additions & 58 deletions

api/composers.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,12 @@ def list_composers():
7878
try:
7979
local = WorkspaceLocalComposer.from_dict(c)
8080
except SchemaError as e:
81-
print(f"Schema drift in {db_path}: {e}")
81+
_logger.warning(
82+
"Schema drift in %s: %s (%s)",
83+
db_path,
84+
e,
85+
type(e).__name__,
86+
)
8287
continue
8388
# Use the typed view downstream so the dataclass is
8489
# load-bearing, not just a filter (Brad's review): the
@@ -91,9 +96,20 @@ def list_composers():
9196
c["workspaceFolder"] = workspace_folder
9297
composers.append((local, c))
9398
except SchemaError as e:
94-
print(f"Schema drift in {db_path}: {e}")
99+
_logger.warning(
100+
"Schema drift in %s: %s (%s)",
101+
db_path,
102+
e,
103+
type(e).__name__,
104+
)
95105
except Exception as e:
96-
print(f"Failed reading composers from {db_path}: {e}")
106+
_logger.error(
107+
"Failed reading composers from %s: %s (%s)",
108+
db_path,
109+
e,
110+
type(e).__name__,
111+
exc_info=True,
112+
)
97113

98114
composers.sort(key=lambda pair: to_epoch_ms(pair[0].last_updated_at), reverse=True)
99115
return jsonify([c for _, c in composers])
@@ -152,7 +168,12 @@ def get_composer(composer_id):
152168
# Same drift list_composers() logs and skips at line ~78,
153169
# so a single-composer fetch can't silently return malformed
154170
# JSON the list endpoint hid.
155-
print(f"Schema drift in workspace-local composer {composer_id}: {e}")
171+
_logger.warning(
172+
"Schema drift in workspace-local composer %s: %s (%s)",
173+
composer_id,
174+
e,
175+
type(e).__name__,
176+
)
156177
continue
157178
# Match list_composers() at line 89 and the global
158179
# fallback below: `conversation` is normalised to []
@@ -163,7 +184,12 @@ def get_composer(composer_id):
163184
payload["conversation"] = payload.get("conversation") or []
164185
return jsonify(payload)
165186
except SchemaError as e:
166-
print(f"Schema drift in {db_path}: {e}")
187+
_logger.warning(
188+
"Schema drift in %s: %s (%s)",
189+
db_path,
190+
e,
191+
type(e).__name__,
192+
)
167193
except (OSError, sqlite3.Error, json.JSONDecodeError, ValueError):
168194
pass
169195

@@ -186,7 +212,12 @@ def get_composer(composer_id):
186212
# Don't return malformed JSON to the client — surface the drift
187213
# as a 404 + log, matching the silent-skip behaviour of the
188214
# list endpoints for the same row.
189-
print(f"Schema drift in composer {composer_id}: {e}")
215+
_logger.warning(
216+
"Schema drift in composer %s: %s (%s)",
217+
composer_id,
218+
e,
219+
type(e).__name__,
220+
)
190221
return jsonify({"error": "Composer schema drift"}), 404
191222
payload = dict(composer.raw)
192223
payload["conversation"] = payload.get("conversation") or []

api/config_api.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
src/app/api/get-username/route.ts GET /api/get-username
77
"""
88

9+
import logging
910
import os
1011
import subprocess
1112
import sys
@@ -16,6 +17,7 @@
1617
from utils.workspace_path import set_workspace_path_override
1718

1819
bp = Blueprint("config_api", __name__)
20+
_logger = logging.getLogger(__name__)
1921

2022

2123
@bp.route("/api/detect-environment")
@@ -44,7 +46,12 @@ def detect_environment():
4446
})
4547

4648
except Exception as e:
47-
print(f"Failed to detect environment: {e}")
49+
_logger.warning(
50+
"Failed to detect environment: %s (%s)",
51+
e,
52+
type(e).__name__,
53+
exc_info=True,
54+
)
4855
return jsonify({"os": "unknown", "isWSL": False, "isRemote": False})
4956

5057

@@ -80,7 +87,12 @@ def validate_path():
8087
)
8188

8289
except Exception as e:
83-
print(f"Validation error: {e}")
90+
_logger.error(
91+
"Validation error: %s (%s)",
92+
e,
93+
type(e).__name__,
94+
exc_info=True,
95+
)
8496
return jsonify({"valid": False, "error": "Failed to validate path"}), 500
8597

8698

@@ -135,5 +147,10 @@ def get_username():
135147
return jsonify({"username": username})
136148

137149
except Exception as e:
138-
print(f"Failed to get username: {e}")
150+
_logger.warning(
151+
"Failed to get username: %s (%s)",
152+
e,
153+
type(e).__name__,
154+
exc_info=True,
155+
)
139156
return jsonify({"username": "YOUR_USERNAME"})

api/export_api.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import io
88
import json
9+
import logging
910
import os
1011
import sqlite3
1112
import zipfile
@@ -32,6 +33,7 @@
3233
)
3334

3435
bp = Blueprint("export_api", __name__)
36+
_logger = logging.getLogger(__name__)
3537

3638

3739
def _get_state_dir() -> str:
@@ -181,7 +183,13 @@ def export_chats():
181183
exported.append({"path": rel_path, "content": md, "updatedAt": updated_at_ms})
182184

183185
except Exception as e:
184-
print(f"Error processing composer {composer_id} for export: {e}")
186+
_logger.error(
187+
"Error processing composer %s for export: %s (%s)",
188+
composer_id,
189+
e,
190+
type(e).__name__,
191+
exc_info=True,
192+
)
185193

186194
count = len(exported)
187195
if count == 0:
@@ -208,7 +216,10 @@ def export_chats():
208216
)
209217

210218
except Exception as e:
211-
print(f"Export error: {e}")
212-
import traceback
213-
traceback.print_exc()
219+
_logger.error(
220+
"Export failed: %s (%s)",
221+
e,
222+
type(e).__name__,
223+
exc_info=True,
224+
)
214225
return jsonify({"error": f"Export failed: {str(e)}"}), 500

api/pdf.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
"""
55

66
import io
7+
import logging
78
import re
89

910
from flask import Blueprint, Response, jsonify, request
1011

1112
bp = Blueprint("pdf", __name__)
13+
_logger = logging.getLogger(__name__)
1214

1315

1416
def _safe_text(text: str) -> str:
@@ -168,9 +170,12 @@ def footer(self):
168170
)
169171

170172
except Exception as e:
171-
print(f"Failed to generate PDF: {e}")
172-
import traceback
173-
traceback.print_exc()
173+
_logger.error(
174+
"Failed to generate PDF: %s (%s)",
175+
e,
176+
type(e).__name__,
177+
exc_info=True,
178+
)
174179
return jsonify({"error": f"Failed to generate PDF: {str(e)}"}), 500
175180

176181

api/search.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,12 @@ def search():
164164
# Drift logged so the operator can see why a chat dropped
165165
# out of search results; bad row still skipped so search
166166
# keeps returning results from the well-formed ones.
167-
print(f"Schema drift in bubble {bid}: {e}")
167+
_logger.warning(
168+
"Schema drift in bubble %s: %s (%s)",
169+
bid,
170+
e,
171+
type(e).__name__,
172+
)
168173
except (json.JSONDecodeError, ValueError):
169174
pass
170175

@@ -178,7 +183,12 @@ def search():
178183
try:
179184
composer = Composer.from_dict(json.loads(row["value"]), composer_id=composer_id)
180185
except SchemaError as e:
181-
print(f"Schema drift in composer {composer_id}: {e}")
186+
_logger.warning(
187+
"Schema drift in composer %s: %s (%s)",
188+
composer_id,
189+
e,
190+
type(e).__name__,
191+
)
182192
continue
183193
except (json.JSONDecodeError, TypeError, ValueError):
184194
continue

app.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from the Cursor editor's AI chat feature.
55
"""
66

7+
import logging
78
import os
89
import sys
910
from datetime import datetime
@@ -35,6 +36,11 @@ def _get_base_path():
3536

3637

3738
def create_app(exclusion_rules_path=None):
39+
logging.basicConfig(
40+
level=logging.INFO,
41+
format="%(asctime)s %(levelname)s %(name)s %(funcName)s: %(message)s",
42+
)
43+
3844
base = _get_base_path()
3945
app = Flask(
4046
__name__,

scripts/export.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,18 @@
7474
_logger = logging.getLogger(__name__)
7575

7676

77+
def _configure_cli_logging() -> None:
78+
"""Route log records to stderr so stdout stays for export progress lines."""
79+
root = logging.getLogger()
80+
if root.handlers:
81+
return
82+
logging.basicConfig(
83+
level=logging.INFO,
84+
format="%(levelname)s: %(message)s",
85+
stream=sys.stderr,
86+
)
87+
88+
7789
def _json_dump_safe(value) -> str:
7890
"""Best-effort JSON serialization for exclusion matching."""
7991
try:
@@ -165,6 +177,7 @@ def parse_args():
165177

166178

167179
def main():
180+
_configure_cli_logging()
168181
opts = parse_args()
169182
since = opts["since"]
170183
out_dir = os.path.abspath(opts["out_dir"])
@@ -215,10 +228,9 @@ def main():
215228

216229
with _open_global_db(workspace_path) as (global_db, global_db_path):
217230
if global_db is None:
218-
print(
219-
f"Note: Cursor IDE global storage not found at {global_db_path}"
220-
" — skipping IDE chats.",
221-
file=sys.stderr,
231+
_logger.info(
232+
"Cursor IDE global storage not found at %s — skipping IDE chats.",
233+
global_db_path,
222234
)
223235
else:
224236
project_layouts_map = load_project_layouts_map(global_db)
@@ -347,7 +359,12 @@ def main():
347359
try:
348360
cli_projects = list_cli_projects(get_cli_chats_path())
349361
except Exception as e:
350-
print(f"Warning: Could not enumerate CLI chats ({e}) — skipping.", file=sys.stderr)
362+
_logger.warning(
363+
"Could not enumerate CLI chats: %s (%s) — skipping",
364+
e,
365+
type(e).__name__,
366+
exc_info=True,
367+
)
351368
cli_projects = []
352369

353370
for cp in cli_projects:
@@ -378,7 +395,13 @@ def main():
378395
messages = traverse_blobs(session["db_path"])
379396
bubbles = messages_to_bubbles(messages, created_ms)
380397
except Exception as e:
381-
print(f"Warning: Could not read CLI session {session_id}: {e}", file=sys.stderr)
398+
_logger.warning(
399+
"Could not read CLI session %s: %s (%s)",
400+
session_id,
401+
e,
402+
type(e).__name__,
403+
exc_info=True,
404+
)
382405
continue
383406

384407
if not bubbles:

services/cli_tabs.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
from __future__ import annotations
22

3+
import logging
34
from datetime import datetime
45

56
from flask import current_app, jsonify
67

8+
_logger = logging.getLogger(__name__)
9+
710
from utils.cli_chat_reader import list_cli_projects, messages_to_bubbles, traverse_blobs
811
from utils.exclusion_rules import build_searchable_text, is_excluded_by_rules
912
from utils.workspace_path import get_cli_chats_path
@@ -44,13 +47,25 @@ def _get_cli_workspace_tabs(workspace_id: str):
4447
try:
4548
messages = traverse_blobs(session["db_path"])
4649
except Exception as e:
47-
print(f"CLI: could not read session {session_id}: {e}")
50+
_logger.warning(
51+
"Could not read CLI session %s: %s (%s)",
52+
session_id,
53+
e,
54+
type(e).__name__,
55+
exc_info=True,
56+
)
4857
continue
4958

5059
try:
5160
bubbles = messages_to_bubbles(messages, created_ms)
5261
except Exception as e:
53-
print(f"CLI: could not convert session {session_id} to bubbles: {e}")
62+
_logger.warning(
63+
"Could not convert CLI session %s to bubbles: %s (%s)",
64+
session_id,
65+
e,
66+
type(e).__name__,
67+
exc_info=True,
68+
)
5469
continue
5570
if not bubbles:
5671
continue
@@ -113,5 +128,11 @@ def _get_cli_workspace_tabs(workspace_id: str):
113128
return jsonify({"tabs": tabs})
114129

115130
except Exception as e:
116-
print(f"Failed to get CLI workspace tabs: {e}")
131+
_logger.error(
132+
"Failed to get CLI workspace tabs for %s: %s (%s)",
133+
workspace_id,
134+
e,
135+
type(e).__name__,
136+
exc_info=True,
137+
)
117138
return jsonify({"error": "Failed to get CLI workspace tabs"}), 500

services/workspace_listing.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,12 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list:
134134
cid,
135135
e,
136136
)
137-
except Exception:
138-
_logger.exception("Failed to load composer rows from global storage")
137+
except Exception as e:
138+
_logger.error(
139+
"Failed to load composer rows from global storage: %s",
140+
e,
141+
exc_info=True,
142+
)
139143

140144
# Group workspace entries by normalized folder path
141145
folder_to_entries: dict[str, list] = {}

0 commit comments

Comments
 (0)