Skip to content

Commit fdaf509

Browse files
committed
initial implementation of the issue requirements
1 parent b344954 commit fdaf509

8 files changed

Lines changed: 130 additions & 30 deletions

File tree

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__,

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

tests/test_models_wired_at_read_sites.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,11 @@ def test_workspace_tabs_endpoint_calls_bubble_from_dict(self):
130130

131131
def test_bubble_schema_drift_is_logged_not_swallowed_silently(self):
132132
# CodeRabbit: SchemaError used to be lumped in with JSONDecodeError /
133-
# ValueError and skipped silently. Schema drift must now print a
133+
# ValueError and skipped silently. Schema drift must now log a
134134
# `Schema drift in bubble <bid>` line so disappearing bubbles can be
135135
# traced. The well-formed row still loads alongside.
136+
import logging
137+
136138
from app import create_app
137139
# Seed a deliberately-malformed bubble row that will trip
138140
# Bubble.from_dict's "expected non-empty str" gate on the bubble_id by
@@ -147,17 +149,14 @@ def test_bubble_schema_drift_is_logged_not_swallowed_silently(self):
147149
app = create_app()
148150
app.config["TESTING"] = True
149151
app.config["EXCLUSION_RULES"] = []
150-
import io
151-
from contextlib import redirect_stdout
152-
captured = io.StringIO()
153-
with redirect_stdout(captured):
152+
with self.assertLogs("api.search", level="WARNING") as logs:
154153
client = app.test_client()
155154
response = client.get("/api/search?q=sentinel-wired")
156155
self.assertEqual(response.status_code, 200)
157-
out = captured.getvalue()
158-
self.assertIn("Schema drift in bubble", out,
159-
msg=f"expected drift log line, got stdout:\n{out!r}")
160-
self.assertIn("bub-bad", out,
156+
messages = "\n".join(logs.output)
157+
self.assertIn("Schema drift in bubble", messages,
158+
msg=f"expected drift log line, got logs:\n{messages!r}")
159+
self.assertIn("bub-bad", messages,
161160
msg="drift log must include the offending bubble id")
162161

163162
def test_workspace_tabs_endpoint_calls_composer_from_dict(self):

0 commit comments

Comments
 (0)