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
)