better logging in websockets

frdel committed Feb 10, 2026 at 10:12 UTC 85d8c4894e4b140373dcbbc34c948ad53f9a61dc
3 files changed +25 -17
python/helpers/websocket_manager.py
+11 -7
@@ -1,6 +1,6 @@
1 from __future__ import annotations
2
3 -import asyncio
3 +import asyncio, os
4 import time
5 import threading
6 from collections import defaultdict, deque
@@ -15,6 +15,7 @@ from python.helpers.defer import DeferredTask
15 from python.helpers.print_style import PrintStyle
16 from python.helpers import runtime
17 from python.helpers.websocket import ConnectionNotFoundError, WebSocketHandler, WebSocketResult
18 +from python.helpers.state_monitor import _ws_debug_enabled
19
20 BUFFER_MAX_SIZE = 100
21 BUFFER_TTL = timedelta(hours=1)
@@ -79,7 +80,8 @@ class WebSocketManager:
80
81 # Internal: development-only debug logging to avoid noise in production
82 def _debug(self, message: str) -> None:
82 - if runtime.is_development():
83 + value = os.getenv("A0_WS_DEBUG", "").strip().lower()
84 + if value in {"1", "true", "yes", "on"}:
85 PrintStyle.debug(message)
86
87 def _ensure_dispatcher_loop(self) -> None:
@@ -329,7 +331,7 @@ class WebSocketManager:
331 )
332 raise
333
332 - if runtime.is_development():
334 + if _ws_debug_enabled():
335 PrintStyle.info(
336 "Registered WebSocket handler %s namespace=%s for events: %s"
337 % (handler.identifier, namespace, ", ".join(validated_events))
@@ -389,7 +391,8 @@ class WebSocketManager:
391 connection_count = sum(
392 1 for conn_identity in self.connections if conn_identity[0] == namespace
393 )
392 - PrintStyle.info(f"WebSocket connected: namespace={namespace} sid={sid}")
394 + if _ws_debug_enabled():
395 + PrintStyle.info(f"WebSocket connected: namespace={namespace} sid={sid}")
396 await self._run_lifecycle(namespace, lambda h: h.on_connect(sid))
397 await self._flush_buffer(identity)
398 if self._server_restart_enabled:
@@ -405,9 +408,10 @@ class WebSocketManager:
408 },
409 handler_id=self._identifier,
410 )
408 - PrintStyle.info(
409 - f"server_restart broadcast emitted to namespace={namespace} sid={sid}"
410 - )
411 + if _ws_debug_enabled():
412 + PrintStyle.info(
413 + f"server_restart broadcast emitted to namespace={namespace} sid={sid}"
414 + )
415 lifecycle_payload = {
416 "namespace": namespace,
417 "sid": sid,
python/websocket_handlers/state_sync_handler.py
+13 -9
@@ -3,7 +3,7 @@ from __future__ import annotations
3 from python.helpers import runtime
4 from python.helpers.print_style import PrintStyle
5 from python.helpers.websocket import WebSocketHandler, WebSocketResult
6 -from python.helpers.state_monitor import get_state_monitor
6 +from python.helpers.state_monitor import get_state_monitor, _ws_debug_enabled
7 from python.helpers.state_snapshot import (
8 StateRequestValidationError,
9 parse_state_request_payload,
@@ -19,11 +19,13 @@ class StateSyncHandler(WebSocketHandler):
19 monitor = get_state_monitor()
20 monitor.bind_manager(self.manager, handler_id=self.identifier)
21 monitor.register_sid(self.namespace, sid)
22 - PrintStyle.info(f"[StateSyncHandler] connect sid={sid}")
22 + if _ws_debug_enabled():
23 + PrintStyle.debug(f"[StateSyncHandler] connect sid={sid}")
24
25 async def on_disconnect(self, sid: str) -> None:
26 get_state_monitor().unregister_sid(self.namespace, sid)
26 - PrintStyle.info(f"[StateSyncHandler] disconnect sid={sid}")
27 + if _ws_debug_enabled():
28 + PrintStyle.debug(f"[StateSyncHandler] disconnect sid={sid}")
29
30 async def process_event(self, event_type: str, data: dict, sid: str) -> dict | WebSocketResult | None:
31 correlation_id = data.get("correlationId")
@@ -39,11 +41,12 @@ class StateSyncHandler(WebSocketHandler):
41 correlation_id=correlation_id,
42 )
43
42 - PrintStyle.debug(
43 - f"[StateSyncHandler] state_request sid={sid} context={request.context!r} "
44 - f"log_from={request.log_from} notifications_from={request.notifications_from} timezone={request.timezone!r} "
45 - f"correlation_id={correlation_id}"
46 - )
44 + if _ws_debug_enabled():
45 + PrintStyle.debug(
46 + f"[StateSyncHandler] state_request sid={sid} context={request.context!r} "
47 + f"log_from={request.log_from} notifications_from={request.notifications_from} timezone={request.timezone!r} "
48 + f"correlation_id={correlation_id}"
49 + )
50
51 # Baseline sequence must be reset on every state_request (new sync period).
52 # V1 policy: seq_base starts >0 to allow simple gating checks.
@@ -61,7 +64,8 @@ class StateSyncHandler(WebSocketHandler):
64 sid,
65 reason="state_sync_handler.StateSyncHandler.state_request",
66 )
64 - PrintStyle.debug(f"[StateSyncHandler] state_request accepted sid={sid} seq_base={seq_base}")
67 + if _ws_debug_enabled():
68 + PrintStyle.debug(f"[StateSyncHandler] state_request accepted sid={sid} seq_base={seq_base}")
69
70 return self.result_ok(
71 {
run_ui.py
+1 -1
@@ -500,7 +500,7 @@ def run():
500 asgi_app,
501 host=host,
502 port=port,
503 - log_level="error",
503 + log_level="info",
504 access_log=_settings.get("uvicorn_access_logs_enabled", False),
505 ws="wsproto",
506 )