# 日志规范 本文件是 skill 日志规范的**权威入口**,面向技术人员与 AI 编程代理。涉及长任务、RPA、外部系统对接的技能,**必须**在实现前通读本文,并在 code review 时对照「必打日志节点清单」人工检查。 --- ## 1. 为什么日志是必需的 OpenClaw 技能常在客户电脑上异步执行,且大量依赖 RPA、浏览器、ERP、微信等外部系统。这类任务具备以下特征: - **耗时长**:单次 run 可能数分钟甚至更久,stdout 长时间无输出不代表卡死。 - **失败点多**:网络抖动、页面改版、登录态失效、验证码、U 盾等都会导致中途失败。 - **环境差异大**:客户 OS、浏览器版本、代理、杀毒软件、屏幕分辨率各不相同,无法复现开发机行为。 - **必须离线排查**:技术支持往往无法远程登录客户电脑,只能依赖本地落盘的日志与 artifacts。 日志用于定位: | 维度 | 说明 | |------|------| | 失败阶段 | 任务停在鉴权、取号、打开页面、提交还是等待结果 | | 输入范围 | `task_type`、`target_id`、`input_id`、`batch_id` | | 外部系统状态 | HTTP 状态、RPA 步骤、兄弟技能返回码 | | 耗时 | `elapsed_ms`、批量 `current/total` | | 重试与人工介入 | 第几次重试、是否等待验证码 / U 盾 | | 存证路径 | 截图、录屏、`video_log`、失败 artifacts | **没有关键节点日志 = 客户现场不可排障。** 不要把 `print` 当作日志;stdout 面向用户与 Agent,文件日志与 Run Journal 面向运维。 --- ## 2. 日志分层 技能日志分四层,职责互不替代: ### 2.1 统一文件日志(开发 / 运维排查) **来源**:`jiangchang_skill_core.unified_logging`(技能内通过 `util.logging_config` 薄封装 import)。 | API | 用途 | |-----|------| | `setup_skill_logging` | CLI 入口启动时初始化(`cli/app.py` 的 `main()`) | | `get_skill_logger` | 获取带 `skill_slug` 的 logger | | `subprocess_env_with_trace` | 子进程 / 兄弟技能调用时传递 `trace_id` | **路径**: ```text {JIANGCHANG_DATA_ROOT}/{JIANGCHANG_USER_ID}/logs/jiangchang.log ``` **特征**:每行带 `trace_id`、`skill_slug`;适合 grep、离线打包发给技术支持。 ### 2.2 Activity / Run Journal(宿主 UI 与用户可见进度) **来源**:`jiangchang_skill_core.activity` | API | 用途 | |-----|------| | `emit` | 推送用户可读进度(不写 stdout);进入步骤前自动步骤闸门(SRCP) | | `step` | 结构化步骤 | | `finish` | 任务结束;**唯一**写单行 result JSON 到 stdout 的场景 | | `job_context` | 包裹 `cmd_run`;未捕获异常 / `JobStopped` 时自动 `finish` | | `rpa_step` | RPA 专用步骤:闸门 + ▶/✓ emit | | `interruptible_sleep` | RPA 等待(替代裸 `asyncio.sleep`),可暂停/停止 | | `checkpoint` | 长循环内 consult control(一般由 kit 自动调用) | **路径**: ```text {JIANGCHANG_DATA_ROOT}/.jiangchang/runs/{job_id}.jsonl ``` **约定**: - `emit` **不写 stdout**;长任务应持续 emit,避免用户以为卡死。 - `finish` 才输出单行 result JSON 供宿主解析。 - RPA 类技能:`RpaVideoSession.add_step` 会自动同步 activity(无需重复手写每步 emit)。 - **禁止**自建 `scripts/util/progress.py` 或向 stdout 打印 `type:progress` JSON;进度只走 Run Journal。 - 宿主通过 `{job_id}.control.json` 下发暂停/继续/停止;技能侧由 platform-kit 在步骤闸门自动处理(见 §2.5)。 ### 2.5 步骤控制(SRCP v1,platform-kit >= 1.2.0) **权威行为定义在本文**;platform-kit README 为实现细节参考。 | 通道 | 方向 | 载体 | |------|------|------| | 进度 | 技能 → 宿主 | Run Journal(`emit` / `rpa_step` / `add_step`) | | 结果 | 技能 → 宿主 | `finish()` → Journal 终态 + stdout 一行 result | | 控制 | 宿主 → 技能 | `{JIANGCHANG_DATA_ROOT}/.jiangchang/runs/{job_id}.control.json` | `control.json` 的 `command` 只认:`none` | `pause` | `resume` | `stop`。 技能作者三件事: 1. `cmd_run` 外包 `with job_context(skill=SKILL_SLUG):` 2. 关键步骤 `emit(...)` 或 `@rpa_step` / `video.add_step` 3. 每个出口 `finish(status=..., message=..., skill=SKILL_SLUG, **fields)` — **不要**手写多行 JSON 结果 RPA 等待用 `interruptible_sleep`,**不要**裸 `asyncio.sleep`(否则暂停响应滞后)。 暂停在**步骤边界**生效(当前 `@rpa_step` / `emit` 执行完后、下一步开始前),与影刀「当前指令完成后暂停」一致。 Journal 会写入 `type: lifecycle` 事件(`paused` / `resumed` / `stopping`),供宿主任务中心显示状态。 ### 2.3 `task_logs`(任务结果审计) **来源**:`db/task_logs_repository.save_task_log` - 适合 `python scripts/main.py logs` / `log-get ` 查询。 - 记录任务最终状态、`result_summary`(可含 `video_path`、`video_log` 等)。 - 面向「这次任务成功还是失败、摘要是什么」,不是逐步 trace。 - **鉴权失败与未捕获异常也必须写入 `task_logs`**,保证 `logs` / `log-get` 排查链不断(见模板 `cmd_run` 示范)。 表结构与字段见 [`references/SCHEMA.md`](../references/SCHEMA.md)。 ### 2.4 RPA video step / artifacts(用户可见动作与存证) **来源**:`RpaVideoSession`、`rpa-artifacts/` 目录 - video artifact 是**标准可启用能力**,**不保证默认每次生成 MP4**(模板默认 `OPENCLAW_RECORD_VIDEO=0`)。 - **`OPENCLAW_RECORD_VIDEO=0`**:不生成最终录屏,但 RPA / 长任务仍须保留 `RpaVideoSession`、`video.add_step` 与 Activity 进度同步。 - **`OPENCLAW_RECORD_VIDEO=1`**:`result_summary` 应包含 `video_path`、`raw_video`、`video_log`、`video_warnings` 等字段;CLI 可打印录屏路径与诊断。 - 用户可见的中文步骤(「打开登录页」「点击提交」)。 - 失败截图:`{数据目录}/rpa-artifacts/{batch_id}/...`(受 `OPENCLAW_ARTIFACTS_ON_FAILURE` 控制,默认开) 详见 [`RPA.md`](RPA.md) §5 存证与录屏规范。 --- ## 3. 必打日志节点清单 以下节点应在**生产代码**中有对应 `log.info` / `log.warning` / `log.exception`(或 Activity / RPA step)。复制模板后按业务增删,但**不可整体省略**。 | 阶段 | 建议日志 / emit | |------|-----------------| | CLI 入口启动 | `cli_start`(`cli/app.py`,已由模板提供) | | 参数解析完成 | `run_args target_id=... input_id=...` | | 鉴权开始 | `entitlement_check_start` | | 鉴权成功 | `entitlement_ok` | | 鉴权失败 | `entitlement_failed`(`warning`) | | 任务开始 | `task_start task_type=... target_id=... input_id=... batch_id=...` + `emit("开始处理任务")` | | adapter / test target 选择 | `adapter_selected adapter=... system=...` | | 账号 / profile / lease 获取 | `account_acquired account_id=...` | | 账号 / lease 释放 | `account_released account_id=...` | | 外部 API 开始 | `external_call_start system=... operation=...` | | 外部 API 结束 | `external_call_done system=... operation=... elapsed_ms=... status=...` | | 兄弟技能调用开始 / 结束 | 同上,或 `sibling_call_start/done skill_slug=...` | | RPA 操作开始 / 结束 | `rpa_start` / `rpa_done` + `video.add_step` 中文步骤 | | 打开页面 | step:「打开 xxx 页面」 | | 检查登录 | step + log | | 定位输入框 / 点击提交 | step + log | | 等待结果 | step + log(含 timeout 预期) | | 批量循环 | `batch_progress current=... total=...` | | 重试 | `retry_attempt n=... reason=...`(`warning`) | | 跳过 / 部分失败 | `skipped` / `partial_failure`(`warning`) | | 人工介入 | `human_intervention type=captcha|login|ukey|otp`(`warning`) | | 失败截图 / 视频 | `artifact_saved artifact_path=...` / `video_path=...` | | 任务成功 | `task_success ...` + `task_log_saved status=success` | | 任务失败 | `task_failed ...`(`exception` 保留 traceback)+ `task_log_saved status=failed` | --- ## 4. 字段规范 推荐在 `log.info` 等消息中使用 **key=value** 结构化字段,便于 grep: | 字段 | 含义 | |------|------| | `task_type` | 任务类型(与 `task_logs.task_type` 对齐) | | `target_id` | 目标标识(平台、店铺、报表类型等) | | `input_id` | 输入记录 ID | | `batch_id` | 批次 / 录屏批次 | | `stage` | 阶段:`auth` / `run` / `cleanup` | | `system` | 外部系统名:`erp` / `wechat` / `1688` | | `operation` | 操作名:`submit_order` / `fetch_report` | | `adapter` | adapter 档位:`mock` / `simulator_api` / `real_api` | | `elapsed_ms` | 耗时毫秒 | | `status` | `ok` / `failed` / `timeout` / `skipped` | | `error_code` | 业务错误码(非 Python 异常名) | | `artifact_path` | 截图 / 附件路径 | | `video_path` | 录屏成品路径 | 示例: ```text external_call_done system=erp operation=download_report elapsed_ms=4523 status=ok batch_progress current=7 total=20 target_id=store-001 ``` --- ## 5. 日志级别规范 | 级别 | 使用场景 | |------|----------| | **info** | 关键阶段、成功路径、外部调用开始/结束、批量进度、`task_log_saved` | | **warning** | 可恢复问题、重试、跳过、人工介入、部分失败、鉴权失败(业务可预期) | | **exception** | 异常路径;**必须**用 `log.exception(...)` 保留 traceback | | **debug** | 低频诊断;**默认不依赖 debug 才能排查问题** | 原则:**生产排障应主要靠 info + warning + exception**,不要把 debug 当作唯一线索。 --- ## 6. 敏感信息红线 ### 禁止记录原始值 以下字段**不得**以明文出现在日志、Activity、task_logs 的 `error_msg` / `result_summary`、stdout: - `password`、`token`、`cookie`、`secret`、`api_key`、`authorization` - 验证码、动态口令全文 - 身份证、银行卡、手机号**完整值** ### 建议记录的安全替代 | 替代字段 | 示例 | |----------|------| | `credential_ref` | vault / account-manager 引用 ID | | `account_id` | 账号表主键 | | `masked_user` | `138****8000` | | `domain` | `erp.example.com` | | `last4` | 卡号后四位 | | `hash` / `short_id` | 内容摘要 | 变量名可以叫 `token`,但日志里不能出现 `token=eyJhbG...` 或 f-string 拼接明文。 --- ## 7. 推荐代码示例(service 层) ```python from util.logging_config import get_skill_logger from jiangchang_skill_core.activity import emit, finish, job_context, rpa_step, interruptible_sleep from util.constants import SKILL_SLUG log = get_skill_logger() def cmd_run(target=None, input_id=None): task_type = "your_task" with job_context(skill=SKILL_SLUG): log.info( "task_start task_type=%s target_id=%s input_id=%s", task_type, target, input_id, ) emit("开始处理任务", skill=SKILL_SLUG, stage="run") try: ok, reason = check_entitlement(SKILL_SLUG) if not ok: log.warning("entitlement_failed task_type=%s reason=%s", task_type, reason) finish(status="failed", message=reason, skill=SKILL_SLUG, error_code="ENTITLEMENT_DENIED") return 1 t0 = time.monotonic() # ... 外部调用 / RPA:@rpa_step 或 video.add_step;等待用 interruptible_sleep ... elapsed_ms = int((time.monotonic() - t0) * 1000) log.info( "external_call_done system=%s operation=%s elapsed_ms=%d status=%s", "erp", "submit", elapsed_ms, "ok", ) tlr.save_task_log(..., status="success", ...) log.info("task_log_saved task_type=%s status=success", task_type) finish(status="success", message="任务完成", skill=SKILL_SLUG) return 0 except Exception: log.exception( "task_failed task_type=%s target_id=%s input_id=%s", task_type, target, input_id, ) emit("任务失败,已记录诊断信息", type="warn", skill=SKILL_SLUG, stage="run") tlr.save_task_log(..., status="failed", error_msg="...", ...) finish(status="failed", message="任务执行异常", skill=SKILL_SLUG) return 1 ``` 模板示范见 [`scripts/service/task_service.py`](../scripts/service/task_service.py) 的 `cmd_run`。 --- ## 8. 错误排查顺序 客户现场或测试机排障,建议按以下顺序(由快到慢、由用户可见到运维细节): 1. **CLI stdout** — 是否有 `ERROR::`、`HINT:`、模板提示文案。 2. **`python scripts/main.py logs`** — 最近任务是否写入 `task_logs`、status 与 `error_msg`。 3. **`python scripts/main.py log-get `** — 单条 JSON,含 `result_summary`(video 路径等)。 4. **统一日志文件** — `{JIANGCHANG_DATA_ROOT}/{JIANGCHANG_USER_ID}/logs/jiangchang.log`(grep `trace_id`、`task_failed`)。 5. **Run Journal** — `{JIANGCHANG_DATA_ROOT}/.jiangchang/runs/{job_id}.jsonl`(宿主 UI 进度来源)。 6. **RPA artifacts** — `rpa-artifacts/` 失败截图;若 `OPENCLAW_RECORD_VIDEO=1` 还有录屏 MP4、`video_log`。未开启录屏时可能只有截图或日志,需要完整录屏请在用户 `.env` 中设置 `OPENCLAW_RECORD_VIDEO=1`。 长时间无 stdout **不代表卡死**;先看 Run Journal / Activity,再看统一日志。 --- ## 9. 与现有文档关系 | 文档 | 关系 | |------|------| | [`RUNTIME.md`](RUNTIME.md) | 统一日志能力来源、`util.logging_config` 薄封装、数据根约定 | | [`TESTING.md`](TESTING.md) | 日志相关 hard policy 测试(`test_development_policy_guard.py`) | | [`RPA.md`](RPA.md) | RPA step、`RpaVideoSession`、video / artifacts 标准 | | [`SCHEMA.md`](../references/SCHEMA.md) | `task_logs` 表结构与查询命令 | | [`CLI.md`](../references/CLI.md) | `logs` / `log-get` 命令与手工排查入口 | | [`CONFIG.md`](CONFIG.md) | 敏感配置不进日志;`.env` 边界 | | [`DEVELOPMENT.md`](DEVELOPMENT.md) | `service/` 层日志职责与发布前检查清单 | | [`POLICY_MATRIX.md`](POLICY_MATRIX.md) | 可自动检测的 logging hard policy 索引 |