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: