Files
verify-invoice-default/development/LOGGING.md

360 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 日志规范
本文件是 skill 日志规范的**权威入口**,面向技术人员与 AI 编程代理。涉及长任务、RPA、外部系统对接的技能,**必须**在实现前通读本文。
自动 hard policy(`POLICY-LOGGING-001`~`008`)只保证底线;阶段是否够细仍须对照本文事件词表做 review。复制技能后可用 [`tests/samples/test_logging_contract.py.sample`](../tests/samples/test_logging_contract.py.sample) 做契约断言。
---
## 0. 验收铁律(先读)
| 通道 | 给谁看 | 能否单独当排障依据 |
|------|--------|-------------------|
| 统一文件日志 `jiangchang.log` | 运维 / 离线打包 | **必须能单独定位到失败阶段与 error_code** |
| Activity / 任务中心 `emit` / `finish` | 用户进度与终态 | **不能**替代文件日志 |
| `print` / stdout | CLI 一眼结果 | **禁止**作为唯一失败记录 |
| 业务表 | 用户可见业务结果 | 有业务才建;**不是**默认排障通道 |
硬规定:
1. **没有关键节点文件日志 = 客户现场不可排障。**
2. **`emit` ≠ 文件日志**:任务中心有进度,而 `jiangchang.log` 只有 `cli_start`,视为交付不合格。
3. **失败出口必须先写文件日志,再 `print` / `finish`**:凡 `print(...ERROR:...)`、`finish(status=failed|partial)`、队列 `mark_failed`,同一出口须有 `log.warning` / `log.exception`(含 `error_code=` 或 `task_failed`)。
4. **域模块也要打点**:`scripts/service/<domain>/`、`*_flow.py`、`*_driver.py` 等业务文件不得零日志(见 `POLICY-LOGGING-006`)。
---
## 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 |
---
## 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 结果
**单内核多入口**:`execute_single_*` 不含 `job_context`;仅 `cmd_run` / `cmd_run_pick` 包裹。批量 pick 循环内 `checkpoint()` + `emit(..., progress=...)`;部分失败用 `finish(status="partial")`。完整范式见 [`SKILL_ACTION_RUNTIME.md`](SKILL_ACTION_RUNTIME.md)。
RPA 等待用 `interruptible_sleep`,**不要**裸 `asyncio.sleep`(否则暂停响应滞后)。
暂停在**步骤边界**生效(当前 `@rpa_step` / `emit` 执行完后、下一步开始前),与影刀「当前指令完成后暂停」一致。
Journal 会写入 `type: lifecycle` 事件(`paused` / `resumed` / `stopping`),供宿主任务中心显示状态。
### 2.3 可观测性三分法(禁止 task_logs)
| 信息类型 | 去哪里 | 技能怎么做 |
|----------|--------|------------|
| **过程**(步骤、异常栈、排查) | `jiangchang.log` / 宿主「运行日志」 | `log.info` / `log.exception`,关键节点见 §3 |
| **进度 / 终态**(任务中心) | Run Journal:`emit` / `finish` | `job_context` + `finish(status=...)` |
| **业务结果** | 业务表字段(status、产物路径、统计等) | repository 回写;见 [`WORK_ITEM_STATUS.md`](../references/WORK_ITEM_STATUS.md) |
模板**默认不建** `task_logs` 等通用任务审计表。**不要**用业务表冒充任务进度,也不要用任务表代替业务状态机。
### 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`**:`finish()` 字段应包含 `video_path`、`raw_video`、`video_log`、`video_warnings` 等;CLI 可打印录屏路径与诊断。
- 用户可见的中文步骤(「打开登录页」「点击提交」)。
- 失败截图:`{数据目录}/rpa-artifacts/{batch_id}/...`(受 `OPENCLAW_ARTIFACTS_ON_FAILURE` 控制,默认开)
详见 [`RPA.md`](RPA.md) §5 存证与录屏规范。
---
## 3. 固定事件词表(生产代码须出现)
日志消息推荐 **key=value**。下列**事件名字符串**须出现在 `log.info` / `log.warning` / `log.exception` 的消息中(可用 f-string / `%s`,但事件名本身须可 grep)。
### 3.1 长任务通用最低集(所有业务 skill)
| 事件名 | 级别 | 说明 |
|--------|------|------|
| `cli_start` | info | CLI 入口(模板已提供) |
| `task_start` | info | 主任务开始(含 `task_type=`) |
| `entitlement_ok` / `entitlement_failed` | info / warning | 鉴权结果 |
| `item_start` / `item_done` | info | 批量逐条时;单条可用 `task_start`/`task_success` 代替 |
| `task_success` 或 `task_failed` | info / warning\|exception | 终态;失败须含 `error_code=` |
### 3.2 RPA / 桌面 / 浏览器额外最低集
当 `scripts/service/` 使用 `RpaVideoSession` / `uiautomation` / `playwright` / `@rpa_step` 时,额外要求:
| 事件名 | 说明 |
|--------|------|
| `rpa_start` / `rpa_done` 或 `rpa_failed` | RPA 段起止(可与 `task_*` 同条合并,但事件名须可 grep) |
| `stage=` | 至少 3 个不同阶段里程碑(如 `open_window` / `locate_control` / `submit` / `wait_result`) |
| `risk_blocked` | 若有配额/工作时段/熔断等前置拦截 |
**不要**要求每个 Click 一行日志;要求的是**阶段里程碑 + 全部失败出口**。
### 3.3 其它常见节点(按业务选用)
| 阶段 | 建议事件 |
|------|----------|
| adapter 选择 | `adapter_selected adapter=...` |
| 账号 / lease | `account_acquired` / `account_released` |
| 外部 API | `external_call_start` / `external_call_done`(含 `elapsed_ms=` / `status=`) |
| 兄弟技能 | `sibling_call_start` / `sibling_call_done` |
| 重试 / 跳过 / 人工 | `retry_attempt` / `skipped` / `human_intervention` |
| 存证 | `artifact_saved` / `video_path=` |
### 3.4 最低可验收日志串(示例)
mock 或真实跑完一条主路径后,`jiangchang.log`(或宿主「运行日志」)应能 grep 到类似:
```text
cli_start argv=...
task_start task_type=add-one target_id=...
entitlement_ok task_type=add-one
rpa_start stage=open_window
rpa_start stage=locate_control
rpa_start stage=submit
task_failed error_code=ELEMENT_NOT_FOUND ...
```
成功路径则出现 `task_success` 而非 `task_failed`。若只有 `cli_start` → **不合格**。
---
## 3.5 必打节点与 Activity 对照
以下节点除文件日志外,长任务仍应 `emit` / `video.add_step`(用户可见),但**不能只 emit 不打文件日志**。
| 阶段 | 文件日志 | Activity |
|------|----------|----------|
| 任务开始 | `task_start` | `emit("开始…")` |
| RPA 步骤 | `stage=...` | `add_step` / `@rpa_step` |
| 批量进度 | `item_start` / `batch_progress` | `emit(..., progress=)` |
| 失败 | `task_failed error_code=` | `finish(failed\|partial)` |
---
## 4. 字段规范
推荐在 `log.info` 等消息中使用 **key=value** 结构化字段,便于 grep:
| 字段 | 含义 |
|------|------|
| `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** | 关键阶段、成功路径、外部调用开始/结束、批量进度 |
| **warning** | 可恢复问题、重试、跳过、人工介入、部分失败、鉴权失败(业务可预期) |
| **exception** | 异常路径;**必须**用 `log.exception(...)` 保留 traceback |
| **debug** | 低频诊断;**默认不依赖 debug 才能排查问题** |
原则:**生产排障应主要靠 info + warning + exception**,不要把 debug 当作唯一线索。
---
## 6. 敏感信息红线
### 禁止记录原始值
以下字段**不得**以明文出现在日志、Activity、`finish()` 字段、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",
)
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")
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:<CODE>:`、`HINT:`、模板提示文案。
2. **任务中心 / Run Journal** — `{JIANGCHANG_DATA_ROOT}/.jiangchang/runs/{job_id}.jsonl`(进度与 `finish` 终态)。
3. **统一日志文件** — `{JIANGCHANG_DATA_ROOT}/{JIANGCHANG_USER_ID}/logs/jiangchang.log`(grep `trace_id`、`task_failed`)。
4. **业务表** — 宿主数据管理中该技能自己的业务结果(有才建)。
5. **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) | 业务表结构与展示元数据 |
| [`CLI.md`](../references/CLI.md) | 最小命令与手工排查入口 |
| [`CONFIG.md`](CONFIG.md) | 敏感配置不进日志;`.env` 边界 |
| [`DEVELOPMENT.md`](DEVELOPMENT.md) | `service/` 层日志职责与发布前检查清单 |
| [`SKILL_ACTION_RUNTIME.md`](SKILL_ACTION_RUNTIME.md) | 任务中心 / async Job 与 `job_context` 边界 |
| [`POLICY_MATRIX.md`](POLICY_MATRIX.md) | 可自动检测的 logging hard policy 索引 |