#!/usr/bin/env python3 """Check API errors in the last hour, attempt auto-fix via Claude, and report to Slack.""" import json import os import shutil import subprocess import sys from datetime import datetime, timedelta, timezone sys.path.insert(0, os.path.dirname(__file__)) import slack_notifier PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) LOG_FILES = [ "trading.log", "sync_cost_basis.log", "cron.log", ] LOOKBACK_MINUTES = 60 def collect_recent_errors() -> list[str]: _TZ_UTC8 = timezone(timedelta(hours=8)) since = datetime.now(_TZ_UTC8) - timedelta(minutes=LOOKBACK_MINUTES) since_str = since.strftime("%Y-%m-%d %H:%M:%S") errors = [] for logfile in LOG_FILES: path = os.path.join(PROJECT_DIR, logfile) if not os.path.exists(path): continue with open(path) as f: for line in f: if "[ERROR]" not in line: continue if not line[:4].isdigit(): continue if line[:19] >= since_str: errors.append(line.rstrip()) return errors def attempt_auto_fix(errors: list[str]) -> str | None: """Ask Claude to diagnose and fix the errors. Returns a summary or None on failure.""" error_text = "\n".join(errors[:20]) prompt = f"""你是 bifitnex-trading 專案的維運工程師。 專案目錄: {PROJECT_DIR} 以下是最近一小時的錯誤 log: ``` {error_text} ``` 請: 1. 診斷錯誤的根因 2. 如果是程式碼問題,直接修復(編輯檔案) 3. 如果是外部問題(API 暫時不可用、網路問題等),不需要改程式碼,只需說明 最後用以下 JSON 格式回覆: ```json {{ "diagnosis": "錯誤根因簡述", "fixed": true/false, "changes": "修改了什麼(沒改就寫 null)", "suggestion": "如需人工介入的建議(不需要就寫 null)" }} ``` 只回傳 JSON,不要其他文字。""" claude_bin = shutil.which("claude") or os.path.expanduser("~/.local/bin/claude") try: result = subprocess.run( [claude_bin, "-p", prompt, "--output-format", "json", "--no-session-persistence", "--allowedTools", "Read,Edit,Glob,Grep"], capture_output=True, text=True, timeout=180, cwd=PROJECT_DIR, ) if result.returncode != 0: return None return _parse_fix_response(result.stdout) except (subprocess.TimeoutExpired, FileNotFoundError, Exception): return None def _parse_fix_response(raw: str) -> dict | None: """Parse Claude's JSON response.""" try: wrapper = json.loads(raw) text = wrapper.get("result", raw) if isinstance(wrapper, dict) else raw except json.JSONDecodeError: text = raw # Try to extract JSON from the text try: return json.loads(text) except json.JSONDecodeError: pass # Try finding JSON block in text start = text.find("{") end = text.rfind("}") + 1 if start >= 0 and end > start: try: return json.loads(text[start:end]) except json.JSONDecodeError: pass return None def main(): errors = collect_recent_errors() if not errors: return unique = list(dict.fromkeys(errors)) display = unique[:20] remaining = len(unique) - len(display) # Try auto-fix fix_result = attempt_auto_fix(unique) # Build Slack message lines = [ "", f"⚠️ *API 錯誤警報* (最近 {LOOKBACK_MINUTES} 分鐘,共 {len(unique)} 筆)", "", ] for e in display: lines.append(f"• `{e[:200]}`") if remaining > 0: lines.append(f"\n...另有 {remaining} 筆錯誤") # Append fix report lines.append("") if fix_result: lines.append("🔧 *自動修復報告:*") lines.append(f" 診斷:{fix_result.get('diagnosis', '未知')}") if fix_result.get("fixed"): lines.append(f" ✅ 已修復:{fix_result.get('changes', '-')}") else: lines.append(" ❌ 未自動修復") if fix_result.get("suggestion"): lines.append(f" 💡 建議:{fix_result.get('suggestion')}") else: lines.append("🔧 *自動修復:* Claude 分析失敗,請人工檢查") slack_notifier._send({"text": "\n".join(lines)}) if __name__ == "__main__": main()