# 调试会话工作流(Debug Session Workflow) > 配套 `architecture-logs-as-bridge.md`。本篇讲"怎么做",上一篇讲"为什么"。 ## TL;DR 1. 写脚本时 `from cq2_sdk.debug_log import DebugLogger`,每个跑流程的入口包一层 2. 跑完后让 OpenCode 读 `D:\传奇\debug_logs\session_*.jsonl` 3. OpenCode 根据日志改 SDK,跑下一轮 ## 0. 装好以后 代码已经落地(单测 24/24 通过): ``` src/cq2_sdk/debug_log.py # 核心 logger,~170 行 tests/unit/test_debug_log.py # 24 个单测 scripts/demo_debug_log.py # 4 种典型用法演示 docs/architecture-logs-as-bridge.md # 架构哲学 docs/debug-session-workflow.md # 本文档 ``` **不需要装新依赖**(DebugLogger 只用 stdlib:json / threading / pathlib / uuid)。 ## 1. 在自己的脚本里接入 ### 最小可用 ```python from cq2_sdk.debug_log import DebugLogger with DebugLogger() as log: log.step("login_attempt", account="longwu988") # ... 你的代码 ... ``` 跑完自动在 `D:\传奇\debug_logs\` 留下一个 JSONL 文件。 ### HTTP 调用要关联 ```python with DebugLogger() as log: req_id = log.http_request("POST", "https://api.jiyuhz.cn/auth/v3/login/safe/byPwd", headers={...}, body="") try: resp = httpx.post(...) log.http_response(req_id, resp.status_code, body=resp.text, duration_ms=elapsed) except Exception as e: log.http_error(req_id, e) ``` `req_id` 把请求和响应配对,OpenCode 读日志时能直接看到完整往返。 ### 异常要抓住 ```python with DebugLogger() as log: try: do_risky_thing() except Exception as e: log.error(e, step="do_risky_thing", extra_ctx=...) ``` 或者不抓——上下文管理器会在 `__exit__` 时自动把异常落 `error` 事件。 ## 2. 看自己写的日志 ```powershell # 看最新一次 session 的所有事件 Get-Content "D:\传奇\debug_logs\session_*.jsonl" | Select-Object -Last 1 # (PowerShell 不太好用 jq,参考下面的"用 Python 读") ``` ```bash # 如果装了 jq(推荐装一个) jq -r '"\(.ts) \(.event) \(.msg // .name // .status // "")"' \ "D:/传奇/debug_logs/session_20260916_103345_a1b2c3.jsonl" ``` ```python # 用 Python 读(最稳) import json with open(r"D:\传奇\debug_logs\session_xxx.jsonl", encoding="utf-8") as f: for line in f: rec = json.loads(line) print(rec["ts"], rec["event"], rec.get("msg", rec.get("name", ""))) ``` ## 3. 让 OpenCode 读日志 直接说人话就行: > "看 `D:\传奇\debug_logs\session_20260916_180812_789abc.jsonl`,找出 > `box_996.py` 里 sign 算法应该用哪一种" > "对比这两个 session 的 http_request 事件,看今天新加的 retry 逻辑有没有生效: > `session_xxx.jsonl` 和 `session_yyy.jsonl`" > "session 末尾有 3 个 error 事件,定位是哪一步出的问题,然后帮我修" OpenCode 会自己用 `grep` / `read` 工具处理 JSONL,**不需要你手动解析**。 ## 4. 日志字段约定(给 OpenCode 看的 schema) 每条事件都至少有: ```json { "ts": "2026-09-16T10:23:45.123+00:00", // UTC ISO 8601,毫秒精度 "session": "s_20260916_102345_a1b2c3", // session id "script": "smoke.py", // 哪个脚本写的 "event": "http_request", // 事件类型 // ... 事件特定字段 } ``` 事件类型清单: | event | 触发 | 必有字段 | |---|---|---| | `session_start` | DebugLogger 构造时 | `start_ts`, `pid`, `extra` 透传 | | `session_end` | `close()` 或 context manager 退出 | (无) | | `step` | `log.step(name, ...)` | `name` | | `info` / `warn` | `log.info/warn(msg, ...)` | `level`, `msg` | | `error` | `log.error(exc_or_str, ...)` | `level`, `msg`, `exc_type`(可空), `step`(可空) | | `http_request` | `log.http_request(...)` | `req_id`, `method`, `url`, `headers`, `body_preview`, `step` | | `http_response` | `log.http_response(...)` | `req_id`, `status`, `headers`, `body_preview`, `duration_ms`, `step` | | `http_error` | `log.http_error(...)` | `req_id`, `msg`, `exc_type`, `level`, `step` | | 任意自定义 | `log.log("my_event", ...)` | 你的字段 | **所有 body_preview 自动截断到 800 字符**,超出部分标记 `...`。 完整 body 想要的话自己加字段(比如 `body_full`)。 ## 5. 实战模式 ### 模式 A:开发新协议 ```python # scripts/probe_new_endpoint.py from cq2_sdk.debug_log import DebugLogger with DebugLogger(extra={"purpose": "probe_new_endpoint", "issue": "#42"}) as log: log.step("probe", endpoint="/new/api/v1/whatever") # 试不同参数,看哪个返回 200 for params in variants: req_id = log.http_request("POST", url, body=params) resp = httpx.post(url, json=params) log.http_response(req_id, resp.status_code, body=resp.text) ``` 跑完让 OpenCode 看 `http_response.status`,挑出 200 的那个 body 当模板。 ### 模式 B:调试登录失败 ```python # scripts/debug_login.py from cq2_sdk.debug_log import DebugLogger with DebugLogger(extra={"purpose": "debug_login_101000"}) as log: log.step("login_attempt", account="longwu988") rid = log.http_request("POST", "https://api.jiyuhz.cn/auth/v3/login/safe/byPwd", body=rsa_login_info) resp = httpx.post(...) log.http_response(rid, resp.status_code, body=resp.text, duration_ms=elapsed) if resp.json()["code"] == 101000: log.warn("server demanded geetest, checking geetest solver", challenge=resp.json()["data"]["gt"]) ``` 把 session 文件丢给 OpenCode,它能直接定位到 101000 的那一行, 告诉你 geetest 字段缺什么。 ### 模式 C:复现"上次能跑、今天不行" ```bash # 拿两个 session diff diff <(jq -S . "D:/传奇/debug_logs/session_昨天.jsonl") \ <(jq -S . "D:/传奇/debug_logs/session_今天.jsonl") ``` 或者让 OpenCode 帮你 diff。 ## 6. 注意事项 | ❌ 不要 | ✅ 应该 | |---|---| | 在一次 `with` 块里写太多脚本逻辑 | 一个脚本一次 `with DebugLogger() as log` | | 把日志写到 `D:\传奇\debug_logs\` 之外 | 用 `log_dir` 参数明确指定 | | 一个 session 跑多轮 | 一轮 = 一个 session 文件,方便 diff | | 写密码、token 明文到 `body_preview` | 自己 mask:`body_preview=mask_token(body)` | | 让 OpenCode 改日志文件 | OpenCode 只读,脚本只写 | | 共享一个 session 文件给多个进程 | 各自开自己的,append 模式不保证原子 | ## 7. 跑一次完整演示 ```powershell cd D:\传奇\cq2_sdk uv run python scripts/demo_debug_log.py ``` 会输出 4 个 demo 模式 + 最后 5 行 JSONL。把生成的 session 文件路径丢给 OpenCode 试读。 ## 8. 失败排查 | 现象 | 原因 | 修法 | |---|---|---| | `Permission denied: D:\传奇\debug_logs\` | 目录被其他进程锁 | 关掉 Fiddler/编辑器;或换 `log_dir` 到别的位置 | | JSONL 里中文是 `\uXXXX` 转义 | `ensure_ascii=False` 失效(极少见) | 重装 Python 3.12+ | | 文件没出现 | 异常抛出但没进 `with` 块 | 用 try/finally 显式 `log.close()` | | 多线程下某些行缺失 | 锁问题(理论上不会) | 升级到 1.0+ 报 bug;先 `log.info` 单线程验证 | | OpenCode 读不动大文件 | 单个 session 太大(>10MB) | 按 step 分文件,或加 `body_preview_limit=200` |