less debug prints in state monitor

frdel committed Feb 5, 2026 at 19:23 UTC 0160fd06ce2e60b3715875155c194e4f10b5c8ef
1 file changed +49 -48
python/helpers/state_monitor.py
+49 -48
@@ -1,6 +1,7 @@
1 from __future__ import annotations
2
3 import asyncio
4 +import os
5 import threading
6 import time
7 from dataclasses import dataclass, field
@@ -22,6 +23,17 @@ if TYPE_CHECKING: # pragma: no cover - hints only
23 ConnectionIdentity = tuple[str, str] # (namespace, sid)
24
25
26 +def _ws_debug_enabled() -> bool:
27 + value = os.getenv("A0_WS_DEBUG", "").strip().lower()
28 + return value in {"1", "true", "yes", "on"}
29 +
30 +
31 +def _debug_log(message: str) -> None:
32 + if not _ws_debug_enabled():
33 + return
34 + PrintStyle.debug(message)
35 +
36 +
37 @dataclass
38 class ConnectionProjection:
39 namespace: str
@@ -61,10 +73,9 @@ class StateMonitor:
73 # Use the manager's dispatcher loop for all scheduling so mark_dirty can be
74 # invoked safely from non-async contexts and other threads.
75 self._dispatcher_loop = getattr(manager, "_dispatcher_loop", None)
64 - if runtime.is_development():
65 - PrintStyle.debug(
66 - f"[StateMonitor] bind_manager handler_id={handler_id or self._emit_handler_id}"
67 - )
76 + _debug_log(
77 + f"[StateMonitor] bind_manager handler_id={handler_id or self._emit_handler_id}"
78 + )
79
80 def register_sid(self, namespace: str, sid: str) -> None:
81 identity: ConnectionIdentity = (namespace, sid)
@@ -72,8 +83,7 @@ class StateMonitor:
83 self._projections.setdefault(
84 identity, ConnectionProjection(namespace=namespace, sid=sid)
85 )
75 - if runtime.is_development():
76 - PrintStyle.debug(f"[StateMonitor] register_sid namespace={namespace} sid={sid}")
86 + _debug_log(f"[StateMonitor] register_sid namespace={namespace} sid={sid}")
87
88 def unregister_sid(self, namespace: str, sid: str) -> None:
89 identity: ConnectionIdentity = (namespace, sid)
@@ -85,14 +95,11 @@ class StateMonitor:
95 if task is not None:
96 task.cancel()
97 self._projections.pop(identity, None)
88 - if runtime.is_development():
89 - PrintStyle.debug(
90 - f"[StateMonitor] unregister_sid namespace={namespace} sid={sid}"
91 - )
98 + _debug_log(f"[StateMonitor] unregister_sid namespace={namespace} sid={sid}")
99
100 def mark_dirty_all(self, *, reason: str | None = None) -> None:
101 wave_id = None
95 - if runtime.is_development():
102 + if _ws_debug_enabled():
103 with self._lock:
104 self._dirty_wave_seq += 1
105 wave_id = f"all_{self._dirty_wave_seq}"
@@ -106,7 +113,7 @@ class StateMonitor:
113 return
114 target = context_id.strip()
115 wave_id = None
109 - if runtime.is_development():
116 + if _ws_debug_enabled():
117 with self._lock:
118 self._dirty_wave_seq += 1
119 wave_id = f"ctx_{self._dirty_wave_seq}"
@@ -135,12 +142,11 @@ class StateMonitor:
142 projection.request = request
143 projection.seq_base = seq_base
144 projection.seq = seq_base
138 - if runtime.is_development():
139 - PrintStyle.debug(
140 - f"[StateMonitor] update_projection namespace={namespace} sid={sid} context={request.context!r} "
141 - f"log_from={request.log_from} notifications_from={request.notifications_from} "
142 - f"timezone={request.timezone!r} seq_base={seq_base}"
143 - )
145 + _debug_log(
146 + f"[StateMonitor] update_projection namespace={namespace} sid={sid} context={request.context!r} "
147 + f"log_from={request.log_from} notifications_from={request.notifications_from} "
148 + f"timezone={request.timezone!r} seq_base={seq_base}"
149 + )
150
151 def mark_dirty(
152 self,
@@ -215,13 +221,12 @@ class StateMonitor:
221 self.debounce_seconds, self._on_debounce_fire, identity
222 )
223 self._debounce_handles[identity] = handle
218 - if runtime.is_development():
219 - PrintStyle.debug(
220 - f"[StateMonitor] schedule_push namespace={projection.namespace} sid={projection.sid} "
221 - f"delay_s={self.debounce_seconds} "
222 - f"dirty={projection.dirty_version} pushed={projection.pushed_version} "
223 - f"reason={projection.dirty_reason!r} wave={projection.dirty_wave_id!r}"
224 - )
224 + _debug_log(
225 + f"[StateMonitor] schedule_push namespace={projection.namespace} sid={projection.sid} "
226 + f"delay_s={self.debounce_seconds} "
227 + f"dirty={projection.dirty_version} pushed={projection.pushed_version} "
228 + f"reason={projection.dirty_reason!r} wave={projection.dirty_wave_id!r}"
229 + )
230
231 def _on_debounce_fire(self, identity: ConnectionIdentity) -> None:
232 with self._lock:
@@ -288,17 +293,16 @@ class StateMonitor:
293 }
294
295 try:
291 - if runtime.is_development():
292 - logs_len = (
293 - len(snapshot.get("logs", []))
294 - if isinstance(snapshot.get("logs"), list)
295 - else None
296 - )
297 - PrintStyle.debug(
298 - f"[StateMonitor] emit state_push namespace={namespace} sid={sid} seq={seq} "
299 - f"context={request.context!r} logs_len={logs_len} "
300 - f"reason={dirty_reason!r} wave={dirty_wave_id!r}"
301 - )
296 + logs_len = (
297 + len(snapshot.get("logs", []))
298 + if isinstance(snapshot.get("logs"), list)
299 + else None
300 + )
301 + _debug_log(
302 + f"[StateMonitor] emit state_push namespace={namespace} sid={sid} seq={seq} "
303 + f"context={request.context!r} logs_len={logs_len} "
304 + f"reason={dirty_reason!r} wave={dirty_wave_id!r}"
305 + )
306 await manager.emit_to(
307 namespace,
308 sid,
@@ -308,17 +312,15 @@ class StateMonitor:
312 )
313 except ConnectionNotFoundError:
314 # Sid was removed before the emit; treat as benign.
311 - if runtime.is_development():
312 - PrintStyle.debug(
313 - f"[StateMonitor] emit skipped: sid not found namespace={namespace} sid={sid}"
314 - )
315 + _debug_log(
316 + f"[StateMonitor] emit skipped: sid not found namespace={namespace} sid={sid}"
317 + )
318 return
319 except RuntimeError:
320 # Dispatcher loop may be closing (e.g., during shutdown or test teardown).
318 - if runtime.is_development():
319 - PrintStyle.debug(
320 - f"[StateMonitor] emit skipped: dispatcher closing namespace={namespace} sid={sid}"
321 - )
321 + _debug_log(
322 + f"[StateMonitor] emit skipped: dispatcher closing namespace={namespace} sid={sid}"
323 + )
324 return
325 finally:
326 follow_up = False
@@ -339,10 +341,9 @@ class StateMonitor:
341 if not follow_up:
342 return
343
342 - if runtime.is_development():
343 - PrintStyle.debug(
344 - f"[StateMonitor] follow_up_push namespace={namespace} sid={sid} dirty={dirty_version} pushed={pushed_version}"
345 - )
344 + _debug_log(
345 + f"[StateMonitor] follow_up_push namespace={namespace} sid={sid} dirty={dirty_version} pushed={pushed_version}"
346 + )
347 try:
348 loop = self._dispatcher_loop or asyncio.get_running_loop()
349 except RuntimeError: