feat: 初始化高校人事请假考勤技能 (process-leave-attendance-default)
All checks were successful
技能自动化发布 / release (push) Successful in 15s
All checks were successful
技能自动化发布 / release (push) Successful in 15s
基于 skill-template 脚手架开发,包含: - 5 张业务表(教职工、请假政策、考勤日历、请假记录、假期余额) - 请假核算引擎(扣薪计算、审批层级判定、年假抵扣) - CLI 入口(import-employees / calculate-leave / render-html) - HTML 审批与扣薪报告生成 - 示例数据与完整测试套件(295 tests passed)
This commit is contained in:
359
development/LOGGING.md
Normal file
359
development/LOGGING.md
Normal file
@@ -0,0 +1,359 @@
|
||||
# 日志规范
|
||||
|
||||
本文件是 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 索引 |
|
||||
Reference in New Issue
Block a user