from __future__ import annotations import faulthandler import json import time import traceback from pathlib import Path from typing import Any, Dict, Optional def _safe_jsonable(value: Any) -> Any: if value is None or isinstance(value, (str, int, float, bool)): return value if isinstance(value, dict): return {str(k): _safe_jsonable(v) for k, v in value.items()} if isinstance(value, (list, tuple)): return [_safe_jsonable(v) for v in value] try: import numpy as np if isinstance(value, np.ndarray): if value.ndim == 0: return float(value) return { "shape": list(value.shape), "dtype": str(value.dtype), } if isinstance(value, (np.floating,)): return float(value) if isinstance(value, (np.integer,)): return int(value) if isinstance(value, (np.bool_,)): return bool(value) except Exception: pass return str(value) class WorkerDiagnostics: def __init__(self, log_path: Path): self.log_path = Path(log_path) self.log_path.parent.mkdir(parents=True, exist_ok=True) self._fh = open(self.log_path, "a", encoding="utf-8") self.note("worker start") try: faulthandler.enable(file=self._fh, all_threads=True) except Exception: pass def note(self, message: str) -> None: ts = time.strftime("%Y-%m-%d %H:%M:%S") self._fh.write(f"\n[{ts}] {message}\n") self._fh.flush() def log_state(self, label: str, state: Optional[Dict[str, Any]]) -> None: payload = _safe_jsonable(state or {}) self.note(f"{label}: {json.dumps(payload, ensure_ascii=True, sort_keys=True)}") def log_exception(self, label: str, exc: BaseException, state: Optional[Dict[str, Any]] = None) -> None: self.note(f"{label}: {type(exc).__name__}: {exc}") if state: self.log_state("state", state) self._fh.write(traceback.format_exc()) if not traceback.format_exc().endswith("\n"): self._fh.write("\n") self._fh.flush() def close(self) -> None: try: if faulthandler.is_enabled(): faulthandler.disable() except Exception: pass try: self._fh.flush() self._fh.close() except Exception: pass def __del__(self) -> None: self.close()