debug-session-workflow.md 7.4 KB

调试会话工作流(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. 在自己的脚本里接入

最小可用

from cq2_sdk.debug_log import DebugLogger

with DebugLogger() as log:
    log.step("login_attempt", account="longwu988")
    # ... 你的代码 ...

跑完自动在 D:\传奇\debug_logs\ 留下一个 JSONL 文件。

HTTP 调用要关联

with DebugLogger() as log:
    req_id = log.http_request("POST", "https://api.jiyuhz.cn/auth/v3/login/safe/byPwd",
                              headers={...}, body="<rsa>")
    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 读日志时能直接看到完整往返。

异常要抓住

with DebugLogger() as log:
    try:
        do_risky_thing()
    except Exception as e:
        log.error(e, step="do_risky_thing", extra_ctx=...)

或者不抓——上下文管理器会在 __exit__ 时自动把异常落 error 事件。

2. 看自己写的日志

# 看最新一次 session 的所有事件
Get-Content "D:\传奇\debug_logs\session_*.jsonl" | Select-Object -Last 1
# (PowerShell 不太好用 jq,参考下面的"用 Python 读")
# 如果装了 jq(推荐装一个)
jq -r '"\(.ts) \(.event) \(.msg // .name // .status // "")"' \
   "D:/传奇/debug_logs/session_20260916_103345_a1b2c3.jsonl"
# 用 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)

每条事件都至少有:

{
  "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 字符,超出部分标记 ...<truncated N chars>。 完整 body 想要的话自己加字段(比如 body_full)。

5. 实战模式

模式 A:开发新协议

# 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:调试登录失败

# 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:复现"上次能跑、今天不行"

# 拿两个 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. 跑一次完整演示

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