"""키움 WS 진단 로그 — return_code/return_msg·스레드·trnm 추적.""" from __future__ import annotations import threading from typing import Any, Mapping, Optional from kis_trader.utils.env import get_env_bool def kiwoom_ws_diag_enabled() -> bool: try: return bool(get_env_bool("KIWOOM_WS_DIAG_ENABLED", True)) except Exception: return True def _thread_tag() -> str: t = threading.current_thread() return f"{t.name}#{getattr(t, 'ident', 0) or 0}" def format_kiwoom_return(msg: Mapping[str, Any]) -> str: rc = msg.get("return_code") rm = msg.get("return_msg") if rc is None and rm in (None, ""): return "" return f"rc={rc} msg={rm}" def log_kiwoom_ws( logger: Any, event: str, *, level: str = "info", rc: Any = None, msg: Any = None, trnm: Optional[str] = None, extra: Optional[str] = None, force: bool = False, ) -> None: """KIWOOM_WS_DIAG_ENABLED 또는 force=True 일 때 INFO/WARNING 로그.""" if not force and not kiwoom_ws_diag_enabled(): return parts = [f"🔎 [키움WS/{event}]", f"thr={_thread_tag()}"] if trnm: parts.append(f"trnm={trnm}") if rc is not None or (msg not in (None, "")): parts.append(f"rc={rc} msg={msg}") if extra: parts.append(str(extra)) line = " ".join(parts) try: getattr(logger, level, logger.info)(line) except Exception: pass def log_kiwoom_api_msg( logger: Any, trnm: str, data: Mapping[str, Any], *, level: str = "warning", prefix: str = "", ) -> None: """수신 JSON에 return_code 가 있고 0이 아니면 항상 로그 (진단 OFF여도).""" rc_raw = data.get("return_code") if rc_raw is None: return rc = str(rc_raw).strip() if rc in ("0", "0.0", ""): return rm = str(data.get("return_msg") or "").strip() seq = data.get("seq") or data.get("data") extra = f"seq={seq}" if seq not in (None, "", []) else "" head = f"{prefix} " if prefix else "" line = f"{head}키움 {trnm} rc={rc} msg={rm}" if extra: line = f"{line} {extra}" if kiwoom_ws_diag_enabled(): line = f"🔎 {line} thr={_thread_tag()}" try: getattr(logger, level, logger.warning)(line) except Exception: pass