Initial public release
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
"""Append-only structured log for host-side BLE events.
|
||||
|
||||
One JSON line per event, in `config/.ble_debug.log`. Written from the BLE
|
||||
thread; reads happen from anywhere. Trimmed to the most recent N lines on
|
||||
each write so the file doesn't grow forever.
|
||||
|
||||
This is independent of Python's `logging` module so it survives across
|
||||
restarts and gets written even if the GUI crashes.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from utils.constants import APPDATA_DIR
|
||||
|
||||
_LOG_PATH = Path(APPDATA_DIR) / ".ble_debug.log"
|
||||
_MAX_LINES = 2000 # trimmed when exceeded
|
||||
|
||||
_lock = threading.Lock()
|
||||
|
||||
|
||||
def _trim_locked() -> None:
|
||||
try:
|
||||
with open(_LOG_PATH, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
except FileNotFoundError:
|
||||
return
|
||||
if len(lines) <= _MAX_LINES:
|
||||
return
|
||||
keep = lines[-_MAX_LINES:]
|
||||
tmp = _LOG_PATH.with_suffix(".tmp")
|
||||
with open(tmp, "w", encoding="utf-8") as f:
|
||||
f.writelines(keep)
|
||||
os.replace(tmp, _LOG_PATH)
|
||||
|
||||
|
||||
def event(_event_name: str, **fields) -> None:
|
||||
"""Append a structured event line.
|
||||
|
||||
First positional arg is the event name (renamed to avoid collision with
|
||||
a `kind` keyword that some call sites legitimately want to log).
|
||||
`fields` are arbitrary JSON-serializable extras.
|
||||
"""
|
||||
rec = {
|
||||
"t": time.time(),
|
||||
"ts": time.strftime("%H:%M:%S", time.localtime()),
|
||||
"kind": _event_name,
|
||||
}
|
||||
rec.update(fields)
|
||||
line = json.dumps(rec, default=str) + "\n"
|
||||
with _lock:
|
||||
try:
|
||||
_LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(_LOG_PATH, "a", encoding="utf-8") as f:
|
||||
f.write(line)
|
||||
# Cheap probabilistic trim: only check size every ~50 lines.
|
||||
if int(rec["t"] * 1000) % 50 == 0:
|
||||
_trim_locked()
|
||||
except Exception:
|
||||
# Logging failures must not break BLE.
|
||||
pass
|
||||
# Mirror to stdout for live debugging. The variable was previously
|
||||
# named `kind` which raised NameError and silently suppressed every
|
||||
# stdout mirror via the broad except — fixed to use the local arg.
|
||||
try:
|
||||
print(f"[BLE.dbg {rec['ts']}] {_event_name} "
|
||||
f"{json.dumps(fields, default=str)}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def path() -> str:
|
||||
return str(_LOG_PATH)
|
||||
|
||||
|
||||
def clear() -> None:
|
||||
with _lock:
|
||||
try:
|
||||
_LOG_PATH.unlink()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
Reference in New Issue
Block a user