bisect: kill-switch every audit-log write

Reported: bisection commit c0bfd4d that disabled the sticky footer,
diagnostics sidebar, and compact-CSS didn't fix the blank-page
symptom. User adds that Ctrl+C also can't kill the launcher.

Ctrl+C-doesn't-work + every-page-blank together points at a hang in
the Python process, not an exception. The most likely hang point in
the chrome path is the audit log's file I/O — ``open()`` inside the
``with`` block in ``log_event`` blocks on a stuck filesystem (Windows
antivirus quarantining ``~/.datatools/logs/datatools-*.jsonl`` on
every write is a plausible culprit on the user's machine). A blocking
``open`` call does NOT raise — try/except can't recover from it —
which is why our prior defensive wrap didn't help.

Add a module-level ``_DISABLED = True`` kill switch. ``log_event``,
``log_session_start``, and ``log_page_open`` each early-return at
the very top of the function when the flag is set, before any
file-system call. Path resolution (``audit_log_path``) still works
since it's needed for the diagnostics sidebar (still disabled in
c0bfd4d, but kept harmless).

If pages render after this commit, file I/O from the audit log is
confirmed as the culprit; we'll redesign with an async writer
queue and a tighter timeout. If they still don't, the cause is
somewhere we haven't bisected yet and we move to a hard revert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 02:14:29 +00:00
parent c0bfd4dbc9
commit 1caedbbbc7

View File

@@ -57,6 +57,15 @@ _LOG_PATH: Path | None = None
_SESSION_ID: str | None = None _SESSION_ID: str | None = None
_SESSION_STARTED: bool = False _SESSION_STARTED: bool = False
# Kill switch — when True, every log_* function is a no-op. Set to
# True while bisecting a "blank pages" report where ``open()`` inside
# the log writer was suspected of blocking on the user's filesystem
# (Windows + antivirus + ``~/.datatools/logs/``). A blocking ``open``
# call doesn't raise so try/except can't recover it; the only safe
# bisect is "don't touch the disk at all." Toggle back to False once
# the user confirms pages render.
_DISABLED: bool = True
def audit_log_dir() -> Path: def audit_log_dir() -> Path:
"""Return the directory where audit logs are written. """Return the directory where audit logs are written.
@@ -147,6 +156,8 @@ def log_event(
Failures are swallowed silently — a broken audit log must not Failures are swallowed silently — a broken audit log must not
take the GUI down. take the GUI down.
""" """
if _DISABLED:
return
try: try:
event = { event = {
"ts": datetime.now(tz=timezone.utc).isoformat(timespec="milliseconds"), "ts": datetime.now(tz=timezone.utc).isoformat(timespec="milliseconds"),
@@ -172,6 +183,8 @@ def log_event(
def log_session_start() -> None: def log_session_start() -> None:
"""Write the session-start banner. Idempotent within one process.""" """Write the session-start banner. Idempotent within one process."""
if _DISABLED:
return
global _SESSION_STARTED global _SESSION_STARTED
with _LOCK: with _LOCK:
if _SESSION_STARTED: if _SESSION_STARTED:
@@ -222,6 +235,8 @@ def log_page_open(slug: str) -> None:
Whole body is wrapped in try/except — the audit log is best- Whole body is wrapped in try/except — the audit log is best-
effort and MUST NOT crash the page that called it. effort and MUST NOT crash the page that called it.
""" """
if _DISABLED:
return
try: try:
try: try:
import streamlit as st import streamlit as st