Skip to content

Optional logging of OSC messages#629

Merged
leszko merged 3 commits intomainfrom
osc-logging
Mar 9, 2026
Merged

Optional logging of OSC messages#629
leszko merged 3 commits intomainfrom
osc-logging

Conversation

@thomshutt
Copy link
Copy Markdown
Contributor

@thomshutt thomshutt commented Mar 9, 2026

It now logs unrecognised messages by default (since this is a user error) and has a switch to enable logging of all messages for debugging an integration

image

Summary by CodeRabbit

  • New Features
    • Added an OSC message logging toggle in Settings to enable/disable logging of all incoming OSC messages.
    • Toggle shows "All" when enabled and "Errors only" when disabled.
    • Toggle is disabled during OSC operations or when the server is not listening.
    • OSC status now reports the current logging mode so the UI stays in sync.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 9, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bf732507-7527-4f56-9c1f-a131638c273c

📥 Commits

Reviewing files that changed from the base of the PR and between 0312732 and 9df5d2c.

📒 Files selected for processing (2)
  • frontend/src/components/settings/OscTab.tsx
  • frontend/src/lib/api.ts

📝 Walkthrough

Walkthrough

Adds a toggleable "Log Messages" setting: frontend UI toggle, new API call to update OSC settings, backend settings endpoint and model, and OSC server flag/property that controls logging and appears in status responses.

Changes

Cohort / File(s) Summary
Frontend UI
frontend/src/components/settings/OscTab.tsx
Adds a "Log Messages" Switch, new isTogglingLog state, uses OscStatusResponse for status, implements handleToggleLogging to call updateOscSettings with optimistic state update and error handling; toggle disabled while toggling/loading or when not listening.
Frontend API
frontend/src/lib/api.ts
Adds OscSettingsRequest and OscStatusResponse types and updateOscSettings(settings: OscSettingsRequest): Promise<OscStatusResponse> which PUTs /api/v1/osc/settings, returns parsed status, and surfaces response text on non-OK errors.
Backend API
src/scope/server/app.py
Adds OscSettingsRequest(BaseModel) and new PUT /api/v1/osc/settings endpoint to set log_all_messages; osc_status() now includes log_all_messages in both running and not-running responses.
Backend OSC Server
src/scope/server/osc_server.py
Adds log_all_messages property (getter/setter) backed by _log_all_messages; status() includes log_all_messages; when enabled, logs valid OSC messages and standardizes unknown-path log text.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI
    participant API
    participant Server
    participant OSC

    User->>UI: toggle "Log Messages"
    UI->>API: updateOscSettings({log_all_messages: true/false})
    API->>Server: PUT /api/v1/osc/settings
    Server->>OSC: set log_all_messages property
    OSC-->>Server: return updated status (includes log_all_messages)
    Server-->>API: 200 OK with status
    API-->>UI: resolve with OscStatusResponse
    UI->>UI: update optimistic status / handle errors
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I flipped a switch with a twitch and a hop,
Logs now chatter whether I stop or I hop,
From frontend to server the message took flight,
All or just errors — I watch through the night. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Optional logging of OSC messages' is clear and specific, directly summarizing the main change that adds optional logging capability for OSC messages across frontend and backend components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch osc-logging

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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 (2)
src/scope/server/osc_server.py (1)

125-130: Consider sampling unknown-message logs.

A misconfigured OSC sender can hit this branch at message rate, and each event is now written at INFO to both console and rotating file logs. A simple per-address rate limit or summary counter would make the new default much safer operationally.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scope/server/osc_server.py` around lines 125 - 130, The current
logger.info call for unknown OSC messages (the branch where path_info is None
using logger.info with address and value) can flood logs; implement a
sampling/rate-limit or summary counter keyed by address to reduce spam: add a
small in-memory map (e.g., unknown_msg_counters or last_logged_timestamps)
updated in the message handler that either logs only every Nth event per address
or only once per time window, and increment an aggregated counter for suppressed
events so you can periodically emit a summary log (e.g., "OSC UNKNOWN x
occurrences for %s, last value=%r"). Update the branch that checks path_info is
None to consult this map before calling logger.info and to increment the
suppression counter instead of logging every event.
frontend/src/lib/api.ts (1)

946-965: Return the server-confirmed OSC status from this helper.

PUT /api/v1/osc/settings already returns srv.status(), but this helper drops that payload and forces OscTab.tsx to re-create the state update locally. Returning a typed response would keep the frontend aligned with the backend contract and remove one partial state copy.

♻️ Suggested refactor
 export interface OscSettingsRequest {
   log_all_messages: boolean;
 }
 
+export interface OscStatusResponse {
+  enabled: boolean;
+  listening: boolean;
+  port: number | null;
+  host: string | null;
+  log_all_messages: boolean;
+}
+
 export const updateOscSettings = async (
   settings: OscSettingsRequest
-): Promise<void> => {
+): Promise<OscStatusResponse> => {
   const response = await fetch("/api/v1/osc/settings", {
     method: "PUT",
     headers: { "Content-Type": "application/json" },
     body: JSON.stringify(settings),
   });
@@
   if (!response.ok) {
     const errorText = await response.text();
     throw new Error(
       `Update OSC settings failed: ${response.status} ${response.statusText}: ${errorText}`
     );
   }
+
+  return response.json();
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/lib/api.ts` around lines 946 - 965, The updateOscSettings helper
currently ignores the server response; modify updateOscSettings (which accepts
OscSettingsRequest) to parse and return the typed server payload (the
srv.status() object returned by PUT /api/v1/osc/settings) instead of void so
callers like OscTab.tsx can use the server-confirmed status; keep the same error
handling (throw on !response.ok) but after validating response.ok call await
response.json(), validate/shape it to the expected status type, and return that
parsed object to callers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/settings/OscTab.tsx`:
- Around line 104-105: The UI label in OscTab.tsx currently shows "Unknown only"
when status?.log_all_messages is false but the backend still logs
malformed/invalid OSC — update the off-state label in the JSX (near the span
using status?.log_all_messages) to accurately reflect behavior (e.g., "Unknown
and malformed" or "Unknown & invalid") so users understand malformed/invalid
messages are also logged when log_all_messages is off.

---

Nitpick comments:
In `@frontend/src/lib/api.ts`:
- Around line 946-965: The updateOscSettings helper currently ignores the server
response; modify updateOscSettings (which accepts OscSettingsRequest) to parse
and return the typed server payload (the srv.status() object returned by PUT
/api/v1/osc/settings) instead of void so callers like OscTab.tsx can use the
server-confirmed status; keep the same error handling (throw on !response.ok)
but after validating response.ok call await response.json(), validate/shape it
to the expected status type, and return that parsed object to callers.

In `@src/scope/server/osc_server.py`:
- Around line 125-130: The current logger.info call for unknown OSC messages
(the branch where path_info is None using logger.info with address and value)
can flood logs; implement a sampling/rate-limit or summary counter keyed by
address to reduce spam: add a small in-memory map (e.g., unknown_msg_counters or
last_logged_timestamps) updated in the message handler that either logs only
every Nth event per address or only once per time window, and increment an
aggregated counter for suppressed events so you can periodically emit a summary
log (e.g., "OSC UNKNOWN x occurrences for %s, last value=%r"). Update the branch
that checks path_info is None to consult this map before calling logger.info and
to increment the suppression counter instead of logging every event.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 164939d7-a8af-4341-af4f-98faaf8fe840

📥 Commits

Reviewing files that changed from the base of the PR and between abed05e and fd3aba7.

📒 Files selected for processing (4)
  • frontend/src/components/settings/OscTab.tsx
  • frontend/src/lib/api.ts
  • src/scope/server/app.py
  • src/scope/server/osc_server.py

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 9, 2026

🚀 fal.ai Preview Deployment

App ID daydream/scope-pr-629--preview
WebSocket wss://fal.run/daydream/scope-pr-629--preview/ws
Commit 9df5d2c

Testing

Connect to this preview deployment by running this on your branch:

uv run build && SCOPE_CLOUD_APP_ID="daydream/scope-pr-629--preview/ws" uv run daydream-scope

🧪 E2E tests will run automatically against this deployment.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 9, 2026

✅ E2E Tests passed

Status passed
fal App daydream/scope-pr-629--preview
Run View logs

Test Artifacts

Check the workflow run for screenshots.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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)
src/scope/server/app.py (1)

675-703: Give the new OSC payload a response model.

Both endpoints now define a shared frontend/backend contract, but they still return raw dicts. A small OscStatusResponse model here would make FastAPI validate log_all_messages/host/port and keep the OpenAPI schema in sync with src/scope/server/osc_server.py::status().

♻️ Possible shape
+class OscStatusResponse(BaseModel):
+    enabled: bool
+    listening: bool
+    port: int | None
+    host: str | None
+    log_all_messages: bool
+
-@app.get("/api/v1/osc/status")
+@app.get("/api/v1/osc/status", response_model=OscStatusResponse)
 async def osc_status():
     ...

-@app.put("/api/v1/osc/settings")
+@app.put("/api/v1/osc/settings", response_model=OscStatusResponse)
 async def update_osc_settings(request: OscSettingsRequest):
     ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scope/server/app.py` around lines 675 - 703, Add a FastAPI response model
for the OSC status and use it on both endpoints: define a Pydantic model named
OscStatusResponse matching the keys returned by get_osc_server().status()
(enabled: bool, listening: bool, port: Optional[int], host: Optional[str],
log_all_messages: bool), then annotate the osc_status() and
update_osc_settings() route handlers with response_model=OscStatusResponse;
continue to use get_osc_server() and srv.status() as the source of values but
let FastAPI validate/serialize via the new OscStatusResponse model.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/scope/server/osc_server.py`:
- Around line 125-130: When path_info is None, avoid logging every frame by
introducing a small in-memory rate-limit/dedupe cache keyed by address (e.g.,
unknown_path_cache[address] = {count, last_logged_ts}); on first hit log as now
via logger.info, on subsequent hits within a suppression window (e.g., 30–60s
using time.monotonic()) increment count but skip writing the log, and when the
window expires log again including the number of suppressed repeats (and reset
count/last_logged_ts). Update the branch around path_info, address, value, and
logger.info to consult this cache, use thread-safe access if this handler can be
called from multiple threads (e.g., a simple lock), and include the
suppressed-count summary in the periodic log entry so repeated unknown-path
messages are collapsed rather than persisted each frame.

---

Nitpick comments:
In `@src/scope/server/app.py`:
- Around line 675-703: Add a FastAPI response model for the OSC status and use
it on both endpoints: define a Pydantic model named OscStatusResponse matching
the keys returned by get_osc_server().status() (enabled: bool, listening: bool,
port: Optional[int], host: Optional[str], log_all_messages: bool), then annotate
the osc_status() and update_osc_settings() route handlers with
response_model=OscStatusResponse; continue to use get_osc_server() and
srv.status() as the source of values but let FastAPI validate/serialize via the
new OscStatusResponse model.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 222d3d9e-275a-41ed-bded-ce04d9e475d8

📥 Commits

Reviewing files that changed from the base of the PR and between fd3aba7 and 0312732.

📒 Files selected for processing (4)
  • frontend/src/components/settings/OscTab.tsx
  • frontend/src/lib/api.ts
  • src/scope/server/app.py
  • src/scope/server/osc_server.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/settings/OscTab.tsx

Comment on lines 125 to 130
if path_info is None:
logger.info(
"OSC INVALID %s = %r reason=unknown path",
"OSC UNKNOWN %s = %r",
address,
value,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Rate-limit unknown-path logging.

A broken sender can hit this branch at frame rate, and every INFO line is persisted by the rotating file handler in src/scope/server/app.py. That will churn the 5×5MB logs fast and bury the useful first failure. Please dedupe or rate-limit these entries per address, then emit a periodic summary of suppressed repeats.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scope/server/osc_server.py` around lines 125 - 130, When path_info is
None, avoid logging every frame by introducing a small in-memory
rate-limit/dedupe cache keyed by address (e.g., unknown_path_cache[address] =
{count, last_logged_ts}); on first hit log as now via logger.info, on subsequent
hits within a suppression window (e.g., 30–60s using time.monotonic()) increment
count but skip writing the log, and when the window expires log again including
the number of suppressed repeats (and reset count/last_logged_ts). Update the
branch around path_info, address, value, and logger.info to consult this cache,
use thread-safe access if this handler can be called from multiple threads
(e.g., a simple lock), and include the suppressed-count summary in the periodic
log entry so repeated unknown-path messages are collapsed rather than persisted
each frame.

leszko and others added 2 commits March 9, 2026 14:53
Instead of optimistically updating local state (which was not reverted on
error), use the status response returned by PUT /api/v1/osc/settings to
set the UI state. Also deduplicate the OscStatus interface into a shared
OscStatusResponse type in api.ts.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
Copy link
Copy Markdown
Collaborator

@leszko leszko left a comment

Choose a reason for hiding this comment

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

Pushed some nit improvements. Other than that LGTM. Merging.

@leszko leszko merged commit d347ccb into main Mar 9, 2026
7 of 8 checks passed
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.

2 participants