harveyz-skill 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -0
- package/bin/cli.js +81 -0
- package/bundles.json +24 -0
- package/lib/bundles.js +39 -0
- package/lib/installer.js +46 -0
- package/lib/targets.js +23 -0
- package/package.json +42 -0
- package/skills/analysis/skill-analyzer/CHANGELOG.md +78 -0
- package/skills/analysis/skill-analyzer/SKILL.md +150 -0
- package/skills/harness/full-stack-debug-env/SKILL.md +468 -0
- package/skills/harness/full-stack-debug-env/references/README.md +31 -0
- package/skills/harness/full-stack-debug-env/references/tech-stacks/browser-spa.md +89 -0
- package/skills/harness/full-stack-debug-env/references/tech-stacks/docker-compose.md +77 -0
- package/skills/harness/full-stack-debug-env/references/tech-stacks/electron.md +123 -0
- package/skills/harness/full-stack-debug-env/references/tech-stacks/node-process.md +76 -0
- package/skills/superpowers-fork/brainstorming/SKILL.md +148 -0
- package/skills/superpowers-fork/executing-plans/SKILL.md +104 -0
- package/skills/superpowers-fork/systematic-debugging/SKILL.md +217 -0
- package/skills/superpowers-fork/using-git-worktrees/SKILL.md +192 -0
- package/skills/superpowers-fork/writing-plans/SKILL.md +187 -0
- package/skills/task/pm-task-dispatch/SKILL.md +165 -0
- package/skills/task/pm-task-dispatch/references/agent-mapping.md +59 -0
- package/skills/task/pm-task-dispatch/references/task-template.md +145 -0
- package/skills/task/task-close/SKILL.md +148 -0
- package/skills/task/task-close/references/output-templates.md +76 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: full-stack-debug-env
|
|
3
|
+
description: Use when setting up a log monitoring environment before running agents or test scenarios on a multi-component application — captures frontend browser console, backend processes, workers, daemons, and other services as independent log files so agents can query each layer precisely
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 全栈调试环境
|
|
7
|
+
|
|
8
|
+
## 概述
|
|
9
|
+
|
|
10
|
+
在执行 Agent 或测试场景之前,识别项目的技术栈,根据技术栈推导出所有日志产生方式和需要覆盖的测试场景,为每个来源建立独立的日志文件,使 Agent 可以精准查询任意层而不受其他层噪音干扰。
|
|
11
|
+
|
|
12
|
+
**技术栈无关。** 本 Skill 通过主动扫描识别技术栈,不预设任何框架或运行时。
|
|
13
|
+
|
|
14
|
+
## 适用场景
|
|
15
|
+
|
|
16
|
+
- 在多组件应用上运行自动化 Agent 之前
|
|
17
|
+
- 问题可能跨越前后端,需要逐层排查时
|
|
18
|
+
- 需要完整审计轨迹的测试 Harness 搭建
|
|
19
|
+
|
|
20
|
+
**不需要本 Skill:**
|
|
21
|
+
- 单进程脚本,仅有 stdout
|
|
22
|
+
- 已有可查询的集中式日志系统(Datadog、Splunk 等)
|
|
23
|
+
|
|
24
|
+
## 执行总览
|
|
25
|
+
|
|
26
|
+
1. **识别技术栈** — 探索项目,填写技术栈摘要表
|
|
27
|
+
2. **推导接入清单** — 对照推导表,确定每个来源的日志文件和测试场景
|
|
28
|
+
3. **搭建环境** — 查 references,按原则接入每个来源
|
|
29
|
+
4. **编写验证脚本** — 依据场景清单写验证脚本,覆盖所有来源
|
|
30
|
+
5. **运行验证脚本** — 真实执行,全部 OK 再继续;有 FAIL 先修复
|
|
31
|
+
6. **运行场景** — 执行测试,按层查询日志诊断问题
|
|
32
|
+
7. **清理** — 停止所有后台捕获进程
|
|
33
|
+
8. **生成文档** — README.md(Reference)+ docs/how-to/debug-env.md(How-to)
|
|
34
|
+
9. **回馈 references** — 评估本次新发现是否值得写入 references/
|
|
35
|
+
|
|
36
|
+
## 产出物与路径约定
|
|
37
|
+
|
|
38
|
+
本 Skill 产生两类产出物,默认路径如下。调用 Skill 时如另行指定路径,以指定路径为准。
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
<project-root>/
|
|
42
|
+
tmp/
|
|
43
|
+
logs/ ← 日志文件目录(gitignore,不提交)
|
|
44
|
+
<source>.log
|
|
45
|
+
<source>.log
|
|
46
|
+
...
|
|
47
|
+
harness/
|
|
48
|
+
debug/
|
|
49
|
+
verify-logs.<ext> ← 验证脚本(提交到版本控制)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**① 日志文件** — `tmp/logs/`
|
|
53
|
+
- 每个来源一个独立文件,文件名在步骤 2 确定
|
|
54
|
+
- 不提交;确保 `tmp/` 已加入 `.gitignore`
|
|
55
|
+
- 跨终端会话保留,手动或脚本清理
|
|
56
|
+
|
|
57
|
+
**② 验证脚本** — `harness/debug/verify-logs.<ext>`
|
|
58
|
+
- 提交到版本控制,供后续会话和团队成员复用
|
|
59
|
+
- 扩展名跟随项目语言约定;文件名固定为 `verify-logs`
|
|
60
|
+
|
|
61
|
+
`tmp/` 存放所有运行时临时输出;`harness/debug/` 存放调试工具脚本,两者职责分离。
|
|
62
|
+
|
|
63
|
+
**步骤 1 结束时,确认日志目录的实际路径**——后续所有步骤统一使用,不再变动。
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 步骤 1 — 技术栈识别
|
|
68
|
+
|
|
69
|
+
**目标:** 搞清楚这个项目用了什么技术,产出一张技术栈摘要表。用什么方法都行——读文档、读配置文件、读代码、看 README、看目录结构——只要结论准确。
|
|
70
|
+
|
|
71
|
+
不要假设已知什么。对于陌生项目,从项目根目录和文档开始探索;对于熟悉的项目,直接确认关键维度即可。
|
|
72
|
+
|
|
73
|
+
需要识别的维度:
|
|
74
|
+
|
|
75
|
+
- **后端运行时与框架**:语言、框架、是否有多个服务
|
|
76
|
+
- **前端类型**:Web SPA、桌面客户端(Electron/Tauri)、移动端(iOS/Android)、还是无前端
|
|
77
|
+
- **数据库**:类型和数量
|
|
78
|
+
- **基础设施**:裸进程、Docker Compose、systemd、k8s,或其他
|
|
79
|
+
- **后台任务**:有无 worker、队列、定时任务
|
|
80
|
+
- **外部依赖**:有无第三方 API 调用、Webhook、消息队列
|
|
81
|
+
|
|
82
|
+
### 输出技术栈摘要
|
|
83
|
+
|
|
84
|
+
识别完成后,填写这张表——后续步骤都从这里派生:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
项目: <name>
|
|
88
|
+
日志目录: <project-root>/tmp/logs/ # 或调用时指定的路径
|
|
89
|
+
|
|
90
|
+
技术栈:
|
|
91
|
+
后端运行时: <Node.js | Python | Ruby | Go | Java | ...>
|
|
92
|
+
后端框架: <Express | FastAPI | Rails | Gin | Spring | ...>
|
|
93
|
+
前端类型: <SPA(React/Vue) | Electron | iOS | Android | 无>
|
|
94
|
+
数据库: <PostgreSQL | MySQL | MongoDB | SQLite | Redis | 无>
|
|
95
|
+
基础设施: <裸进程 | Docker Compose | systemd | k8s>
|
|
96
|
+
后台任务: <无 | Celery | Bull | Sidekiq | cron | ...>
|
|
97
|
+
外部服务: <无 | 支付/Webhook | 消息队列 | 第三方 API>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 步骤 2 — 从技术栈推导日志来源与测试场景
|
|
103
|
+
|
|
104
|
+
根据步骤 1 的技术栈摘要,**逐行对照下表**,勾选适用项,不适用的跳过。每一行同时给出:该技术栈会产生什么日志、日志文件命名建议、以及需要覆盖的测试场景类型。
|
|
105
|
+
|
|
106
|
+
**基础设施层**
|
|
107
|
+
|
|
108
|
+
| 技术栈维度 | 日志产生方式 | 建议日志文件名 | 需覆盖的场景 |
|
|
109
|
+
|---|---|---|---|
|
|
110
|
+
| **裸进程**(Node / Python / Go / Ruby 等) | stdout / stderr | `backend.log` | 正常路径、启动错误、崩溃退出 |
|
|
111
|
+
| **进程写文件**(自定义日志路径) | .log / .jsonl 文件 | 复用原文件或 symlink | 同上,另加日志滚动后不断流 |
|
|
112
|
+
| **Docker Compose** | 容器标准流 | `<container-name>.log` | 容器重启、健康检查失败、跨容器调用 |
|
|
113
|
+
| **systemd 服务** | journald | `<service>.log` | 服务启停、OOM、权限拒绝 |
|
|
114
|
+
|
|
115
|
+
**前端层**
|
|
116
|
+
|
|
117
|
+
| 技术栈维度 | 日志产生方式 | 建议日志文件名 | 需覆盖的场景 |
|
|
118
|
+
|---|---|---|---|
|
|
119
|
+
| **SPA 前端**(React / Vue / Angular) | 浏览器 DevTools console | `browser.log` | JS 运行时错误、网络请求失败、状态异常 |
|
|
120
|
+
| **Electron 应用** | renderer console + 主进程 stdout | `renderer.log` + `main.log` | IPC 通信、渲染崩溃、preload 错误 |
|
|
121
|
+
| **iOS(模拟器)** | os_log | `ios.log` | 启动流程、网络请求、原生崩溃 |
|
|
122
|
+
| **Android(模拟器 / 设备)** | adb logcat | `android.log` | 同上 |
|
|
123
|
+
|
|
124
|
+
**数据库层**
|
|
125
|
+
|
|
126
|
+
| 技术栈维度 | 日志产生方式 | 建议日志文件名 | 需覆盖的场景 |
|
|
127
|
+
|---|---|---|---|
|
|
128
|
+
| **PostgreSQL** | pg 查询日志 | `postgres.log` | 慢查询、约束冲突、连接耗尽 |
|
|
129
|
+
| **MySQL / MariaDB** | 错误日志 | `mysql.log` | 同上 |
|
|
130
|
+
| **MongoDB** | mongod 日志 | `mongo.log` | 慢查询、复制集错误 |
|
|
131
|
+
| **Redis** | redis-server 日志 | `redis.log` | 内存淘汰、连接失败 |
|
|
132
|
+
|
|
133
|
+
**异步与外部依赖层**
|
|
134
|
+
|
|
135
|
+
| 技术栈维度 | 日志产生方式 | 建议日志文件名 | 需覆盖的场景 |
|
|
136
|
+
|---|---|---|---|
|
|
137
|
+
| **后台 Worker**(Celery / Bull / Sidekiq) | worker 进程 stdout / 日志文件 | `worker.log` | 任务入队、处理中、失败重试、死信队列 |
|
|
138
|
+
| **cron / 定时任务** | cron 日志或任务 stdout | `cron.log` | 任务触发、执行结果、超时 |
|
|
139
|
+
| **外部 API 调用** | 应用内出站请求日志(代码插桩) | `http-out.log` | 超时、4xx/5xx、重试次数 |
|
|
140
|
+
| **Webhook / 第三方回调** | 入站 HTTP 请求 | `webhook.log` | 签名验证、幂等处理、延迟到达 |
|
|
141
|
+
| **消息队列**(Kafka / RabbitMQ / SQS) | broker 日志 + consumer 日志 | `queue.log` | 消息积压、消费失败、重平衡 |
|
|
142
|
+
| **跨服务 HTTP**(微服务) | 网络层请求/响应 | `http-trace.log` | 服务间调用失败、超时传播、熔断 |
|
|
143
|
+
|
|
144
|
+
**完成后输出接入清单:**
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
日志目录: <步骤 1 已确认的路径>
|
|
148
|
+
|
|
149
|
+
需要接入的来源:
|
|
150
|
+
[ ] backend.log — 来源: <启动命令 / 日志文件路径> [常驻]
|
|
151
|
+
[ ] browser.log — 来源: <测试框架钩子 / preload 拦截> [常驻]
|
|
152
|
+
[ ] worker.log — 来源: <启动命令 / 日志文件路径> [常驻]
|
|
153
|
+
[ ] step-logs/ — 来源: <任务步骤输出> [任务驱动型]
|
|
154
|
+
[ ] <其他>.log — 来源: <...> [常驻 / 任务驱动型]
|
|
155
|
+
|
|
156
|
+
说明:[常驻] 来源在应用启动后即持续写入,验证时必须 OK;[任务驱动型] 来源只在业务任务运行时才产生输出,无活跃任务时验证报 SKIP 属正常,不计入 FAIL。
|
|
157
|
+
|
|
158
|
+
需要覆盖的场景:
|
|
159
|
+
[ ] 正常路径(端到端全流程)
|
|
160
|
+
[ ] 错误传播(某层出错,其他层如何感知)
|
|
161
|
+
[ ] 异步任务(入队 → 处理 → 完成 / 失败)
|
|
162
|
+
[ ] 认证 / 会话(登录、token 刷新、权限拒绝)
|
|
163
|
+
[ ] 外部依赖失败(第三方 API 超时、数据库断连)
|
|
164
|
+
[ ] <其他项目特有场景>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 步骤 3 — 环境搭建
|
|
170
|
+
|
|
171
|
+
**目标:** 让步骤 2 清单里每一个日志来源,都持续写入各自独立的日志文件。
|
|
172
|
+
|
|
173
|
+
### 先查 References
|
|
174
|
+
|
|
175
|
+
在动手之前,查阅 `references/` 目录,找到与当前技术栈匹配的最佳实践文档,直接参照执行:
|
|
176
|
+
|
|
177
|
+
| 技术栈 | 参考文档 |
|
|
178
|
+
|---|---|
|
|
179
|
+
| Node.js 裸进程(Express、Fastify、Koa 等) | `references/tech-stacks/node-process.md` |
|
|
180
|
+
| Electron(主进程 + renderer) | `references/tech-stacks/electron.md` |
|
|
181
|
+
| Docker Compose 多容器 | `references/tech-stacks/docker-compose.md` |
|
|
182
|
+
| 浏览器 SPA(React、Vue、Angular 等) | `references/tech-stacks/browser-spa.md` |
|
|
183
|
+
|
|
184
|
+
如果没有匹配的文档,按下方原则自行设计。**设计完成后,把结论写入 `references/tech-stacks/` 下对应文件或新建文件**,供下次复用。新文件格式见 `references/README.md`。
|
|
185
|
+
|
|
186
|
+
### 时间戳标准化
|
|
187
|
+
|
|
188
|
+
每行日志都必须以 ISO 8601 时间戳开头(格式:`2024-01-15T14:30:01.234Z`)。这是唯一的格式要求,其余内容保持原样(无损)。
|
|
189
|
+
|
|
190
|
+
三种情况的处理方式:
|
|
191
|
+
|
|
192
|
+
| 来源状态 | 处理方式 |
|
|
193
|
+
|---|---|
|
|
194
|
+
| 已有 ISO 8601 时间戳 | 不动,直接写入 |
|
|
195
|
+
| 有时间戳但格式不同 | capture 层在写入前转换为 ISO 8601 |
|
|
196
|
+
| 没有时间戳 | capture 层在每行写入前注入当前时间 |
|
|
197
|
+
|
|
198
|
+
capture 层的职责是:在日志内容写入文件之前,在行首拼上时间戳,不修改原始内容。**已有时间戳的来源不要重复注入**,避免每行出现两个时间戳。
|
|
199
|
+
|
|
200
|
+
各技术栈的具体处理方式见 `references/` 对应文档。
|
|
201
|
+
|
|
202
|
+
### 启动模式
|
|
203
|
+
|
|
204
|
+
日志捕获有两种挂载方式,根据项目情况选择:
|
|
205
|
+
|
|
206
|
+
**模式 A — 独立启动/关闭**
|
|
207
|
+
捕获进程与应用进程分离,独立管理。适合调试时临时开启、不影响正常启动流程的场景。
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
启动应用(原有方式不变)
|
|
211
|
+
↓
|
|
212
|
+
单独启动日志捕获
|
|
213
|
+
↓
|
|
214
|
+
执行测试场景
|
|
215
|
+
↓
|
|
216
|
+
单独关闭日志捕获
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**模式 B — 集成到应用启动脚本**
|
|
220
|
+
在应用的启动脚本(`start.sh`、`npm start`、`Makefile` 等)里一并启动捕获,关闭时一并停止。适合需要每次启动都自动捕获、或团队共用的场景。
|
|
221
|
+
|
|
222
|
+
启动脚本应支持一个参数开关,**默认开启**捕获,需要时可以关闭。具体实现方式(CLI flag、配置文件项、脚本参数等)根据项目现有的启动约定来决定,不强制规定。
|
|
223
|
+
|
|
224
|
+
两种模式可以混用——核心来源集成到启动脚本,临时需要的来源(如数据库慢查询)独立开关。
|
|
225
|
+
|
|
226
|
+
### 搭建原则
|
|
227
|
+
|
|
228
|
+
**原则 1 — 每个来源一个独立文件**
|
|
229
|
+
每个组件写自己的日志文件(`backend.log`、`browser.log`、`worker.log`),不合并。这样可以精准查询、独立 rotate、互不干扰。
|
|
230
|
+
|
|
231
|
+
**原则 2 — 先于任何操作启动**
|
|
232
|
+
日志捕获环境必须在触发任何测试场景之前完全就绪。启动后先确认环境正常,再执行场景。
|
|
233
|
+
|
|
234
|
+
**原则 3 — 主动接入,而非被动等待**
|
|
235
|
+
不要假设日志会"自动出现"。每个来源都需要显式接入:
|
|
236
|
+
- 写到文件的 → 确认路径正确,持续跟踪
|
|
237
|
+
- 写到 stdout/stderr 的 → 在进程层面重定向到文件
|
|
238
|
+
- 浏览器 console → 必须在前端运行时或测试框架里挂钩,永远不会自动流到服务端
|
|
239
|
+
- 系统级服务 → 通过该平台的日志系统读取
|
|
240
|
+
- 外部依赖(第三方 API、Webhook)→ 在本地接收层插入记录点,无法直接读取外部服务的日志
|
|
241
|
+
|
|
242
|
+
**原则 4 — 不改变应用行为**
|
|
243
|
+
接入方式只捕获、不干扰。不能因为加了日志捕获而改变应用的运行逻辑或性能特征。
|
|
244
|
+
|
|
245
|
+
**原则 5 — 验证脚本说了算**
|
|
246
|
+
不能用"手动点了一下"来声明验证通过。必须编写验证脚本并真实运行,脚本输出作为覆盖完整性的唯一证明。
|
|
247
|
+
|
|
248
|
+
所有来源接入完毕后,进入步骤 4 编写验证脚本。
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## 步骤 4 — 编写验证脚本
|
|
253
|
+
|
|
254
|
+
**在步骤 3 所有来源接入完毕后,立即为本项目编写验证脚本。** 语言和形式跟随项目约定。
|
|
255
|
+
|
|
256
|
+
脚本必须满足以下条件:
|
|
257
|
+
|
|
258
|
+
**触发条件**
|
|
259
|
+
- 依据步骤 2 场景清单,用真实操作触发每个日志来源,不能只靠"应用已在运行"来假设日志会自然产生
|
|
260
|
+
- 覆盖正常路径、会产生错误日志的路径,以及异步组件(如果项目有)
|
|
261
|
+
- 如果项目有前端,包含前端层的触发操作
|
|
262
|
+
|
|
263
|
+
**时序条件**
|
|
264
|
+
- 如果项目有异步来源,触发后必须等待足够时间再验证;等待时长根据项目实际异步延迟确定,不能用固定值
|
|
265
|
+
- 校准方法:在开发环境手动计时,从触发操作到日志文件首行写入的实际延迟为基准,再加 50% 作为缓冲。典型参考范围:本地 shell 子进程 1–2 秒,网络请求 2–5 秒,队列消费 5–15 秒。
|
|
266
|
+
- 纯同步项目可以不等待
|
|
267
|
+
|
|
268
|
+
**验证条件**
|
|
269
|
+
- 步骤 2 清单中的每个日志文件都必须有对应的检查项,一个不能少
|
|
270
|
+
- 每个来源必须检查:文件存在、有新内容写入(不是旧文件)、时间戳格式符合 ISO 8601
|
|
271
|
+
- 输出必须逐项列出 OK / MISSING / NO\_NEW\_OUTPUT,不能只打印一个汇总结论
|
|
272
|
+
|
|
273
|
+
**任务驱动型来源的特殊处理**
|
|
274
|
+
|
|
275
|
+
有些来源只在任务运行时才产生输出(典型:步骤日志、work/ 下的文件),环境验证时可能没有活跃任务。对这类来源:
|
|
276
|
+
- 文件存在但长时间未更新 → 报 **SKIP**,不报 FAIL
|
|
277
|
+
- 文件完全不存在 → 同样报 SKIP,并注明"需要先创建任务"
|
|
278
|
+
- 不计入通过/失败统计
|
|
279
|
+
|
|
280
|
+
判断一个来源是否属于"任务驱动型":由步骤 2 推导时标注——若该来源的输出依赖业务任务触发(而非应用启动即产生),则为任务驱动型。
|
|
281
|
+
|
|
282
|
+
**退出条件**
|
|
283
|
+
- 任何非任务驱动型来源 FAIL,脚本以非零状态退出,阻止继续往下走
|
|
284
|
+
- 全部非任务驱动型来源 OK(任务驱动型可以 SKIP)才允许进入步骤 5
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## 步骤 5 — 运行验证脚本
|
|
289
|
+
|
|
290
|
+
运行步骤 4 编写的验证脚本,确认所有来源输出全部 OK。
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
bash harness/debug/verify-logs.sh # 或项目对应的验证命令
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**全部 OK → 进入步骤 6。**
|
|
297
|
+
|
|
298
|
+
**有 FAIL → 先修复,再重跑。** 按以下顺序诊断:
|
|
299
|
+
|
|
300
|
+
| FAIL 类型 | 最可能的原因 | 检查方向 |
|
|
301
|
+
|---|---|---|
|
|
302
|
+
| 文件不存在 | 捕获进程未启动 / 路径错误 | 确认后台进程在运行;检查写入路径是否与脚本检查路径一致 |
|
|
303
|
+
| 文件为空 | 时间戳注入层崩溃 / 进程未产生输出 | 直接运行应用,看 stdout 是否有输出;检查 capture 层命令语法 |
|
|
304
|
+
| ISO 8601 检查失败 | 来源已有时间戳但格式不同,或注入层未生效 | `head -3 <log-file>` 查看实际行首格式;对照 `references/tech-stacks/` 确认注入方式 |
|
|
305
|
+
| 无新内容写入 | 触发请求未到达该层 / 异步延迟未等够 | 手动触发一次操作,看文件行数是否增加;若有异步,延长等待时间 |
|
|
306
|
+
|
|
307
|
+
修复后重新运行验证脚本,直到所有来源 OK。
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## 步骤 6 — 运行场景
|
|
312
|
+
|
|
313
|
+
**目标:** 触发测试场景,通过日志逐层定位问题。
|
|
314
|
+
|
|
315
|
+
按步骤 2 场景清单依次执行各场景(正常路径、错误路径、异步任务等)。执行时按以下原则查询日志:
|
|
316
|
+
|
|
317
|
+
**单层查询**
|
|
318
|
+
- 每次只读一个来源的文件,不混合
|
|
319
|
+
- 按错误关键词过滤时,针对该层的典型错误词(不同层的错误词不同,按层确定)
|
|
320
|
+
- 需要实时跟踪时,仅跟踪目标层
|
|
321
|
+
|
|
322
|
+
**跨层时序关联(按需)**
|
|
323
|
+
- 仅在需要还原"某层触发 → 另一层响应"的完整时间线时才做跨层合并
|
|
324
|
+
- 前提:各日志文件的行首都有 ISO 8601 时间戳,才能按时间正确排序合并
|
|
325
|
+
- 合并是临时操作,不替代独立文件;分析完毕后继续按层查询
|
|
326
|
+
|
|
327
|
+
**诊断失败的推荐顺序**
|
|
328
|
+
1. 先从最可能出错的层入手,单独查那个文件
|
|
329
|
+
2. 确认该层的错误内容和时间点
|
|
330
|
+
3. 用时间点去其他层日志里查前后上下文
|
|
331
|
+
4. 仍不清楚时临时合并多层做时序分析
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## 步骤 7 — 清理
|
|
336
|
+
|
|
337
|
+
- **模式 A(独立启动)**:手动停止所有后台捕获进程
|
|
338
|
+
- **模式 B(集成启动脚本)**:随应用一起关闭,无需额外操作
|
|
339
|
+
|
|
340
|
+
按需保留或删除日志文件。
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## 常见错误
|
|
345
|
+
|
|
346
|
+
| 错误 | 修正 |
|
|
347
|
+
|---|---|
|
|
348
|
+
| 触发场景之后才启动日志捕获 | 必须先启动捕获,再触发 |
|
|
349
|
+
| 只捕获后端,忽略浏览器 console | 浏览器 console 需要主动挂钩,永远不会自动出现在服务端日志 |
|
|
350
|
+
| 步骤 2 只看"有进程"而不看技术栈 | 技术栈决定日志方式;同一个"后端进程"可能同时写 stdout 和文件 |
|
|
351
|
+
| 异步 worker 没有日志就下结论 | Worker 可能在触发后数秒才写日志——等一等再查 |
|
|
352
|
+
| 把所有日志合并成一个文件 | 高频来源(DB 查询、心跳)会淹没应用层事件;保持独立文件,按需临时合并 |
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## 完成前检查
|
|
357
|
+
|
|
358
|
+
在宣布环境搭建完成之前,逐项确认:
|
|
359
|
+
|
|
360
|
+
**覆盖完整性**
|
|
361
|
+
- [ ] 步骤 2 推导出的每一个来源,都有对应的日志文件正在接收输出
|
|
362
|
+
- [ ] 浏览器 / renderer console 有独立的 hook,不依赖服务端日志(如果项目有前端)
|
|
363
|
+
- [ ] 异步来源(worker、cron、消息队列)已接入,不会因为"暂时没有任务"就漏掉(如果项目有异步组件)
|
|
364
|
+
|
|
365
|
+
**格式一致性**
|
|
366
|
+
- [ ] 每个日志文件的行首都有 ISO 8601 时间戳
|
|
367
|
+
- [ ] 没有来源被重复注入时间戳(行首出现两个时间戳)
|
|
368
|
+
- [ ] 没有来源仍在写入统一的合并文件
|
|
369
|
+
|
|
370
|
+
**启动模式**
|
|
371
|
+
- [ ] 已明确选择模式 A 或模式 B(或混用)
|
|
372
|
+
- [ ] 模式 B:启动脚本已有开关,默认开启捕获
|
|
373
|
+
|
|
374
|
+
**实测验证**
|
|
375
|
+
- [ ] 已编写验证脚本,覆盖步骤 2 清单中的所有来源
|
|
376
|
+
- [ ] 验证脚本已真实运行,输出全部 OK(无 MISSING / NO_NEW_OUTPUT)
|
|
377
|
+
- [ ] 跨层时序关联已确认可以正常执行——各文件行首时间戳格式一致,可以按时间排序合并(如果需要)
|
|
378
|
+
|
|
379
|
+
**目录结构与路径**
|
|
380
|
+
- [ ] `tmp/logs/` 已存在且已加入 `.gitignore`
|
|
381
|
+
- [ ] 验证脚本已放在 `harness/debug/verify-logs.<ext>`,已提交到版本控制
|
|
382
|
+
- [ ] 日志文件确实写入 `tmp/logs/`,没有散落在其他位置
|
|
383
|
+
|
|
384
|
+
**references 更新**
|
|
385
|
+
- [ ] 本次新设计的捕获方案已记录到 `references/tech-stacks/` 对应文件(或新建),供下次复用
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## 步骤 8 — 生成文档
|
|
390
|
+
|
|
391
|
+
以上检查全部通过后,生成两份文档:
|
|
392
|
+
|
|
393
|
+
### harness/debug/README.md(Reference)
|
|
394
|
+
|
|
395
|
+
供 Agent 按需查找,内容精确、结构化,不含解释性文字:
|
|
396
|
+
|
|
397
|
+
- 日志目录绝对路径
|
|
398
|
+
- 来源列表:每个来源的日志文件名、对应的接入方式(启动命令 / 钩子位置)
|
|
399
|
+
- 验证脚本路径及运行方式
|
|
400
|
+
- 查询方式:按来源列出如何读取、如何过滤错误、如何跨层关联
|
|
401
|
+
- 启动/停止捕获的操作
|
|
402
|
+
|
|
403
|
+
### docs/how-to/debug-env.md(How-to)
|
|
404
|
+
|
|
405
|
+
供人阅读,解释背景和操作思路:
|
|
406
|
+
|
|
407
|
+
- 本环境覆盖哪些来源、为什么这样划分
|
|
408
|
+
- 如何启动和关闭(含两种模式的说明)
|
|
409
|
+
- 遇到问题时的排查思路(先查哪层、怎么缩小范围)
|
|
410
|
+
- 如何新增来源或修改现有配置
|
|
411
|
+
- 验证脚本的使用场景和频率建议
|
|
412
|
+
|
|
413
|
+
生成完成后,确认以下两项:
|
|
414
|
+
- [ ] `harness/debug/README.md` 已存在且非空
|
|
415
|
+
- [ ] `docs/how-to/debug-env.md` 已存在且非空
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## 步骤 9 — 回馈 references
|
|
420
|
+
|
|
421
|
+
文档生成完成后,回顾本次执行,判断是否有新发现值得写入 `references/`。这一步让 Skill 随每次使用积累知识,不断缩短下次执行的摩擦。
|
|
422
|
+
|
|
423
|
+
### 评估标准
|
|
424
|
+
|
|
425
|
+
对本次遇到的每一个"意外"或"自行设计的方案",用以下标准判断:
|
|
426
|
+
|
|
427
|
+
| 标准 | 问题 | 满足才考虑写入 |
|
|
428
|
+
|------|------|---------------|
|
|
429
|
+
| **可复用** | 这个方案在其他项目里也会遇到,而不仅限于本项目的业务逻辑? | 是 |
|
|
430
|
+
| **非显然** | 如果下次遇到同类技术栈,agent 靠通用知识大概率会踩同样的坑? | 是 |
|
|
431
|
+
| **影响实质** | 不知道这一点,会导致日志为空、验证失败、或需要多轮调试? | 是 |
|
|
432
|
+
| **技术栈归属** | 这个知识点属于某个具体技术栈(Node.js / Electron / Docker 等),而不是项目特有的业务规则? | 是 |
|
|
433
|
+
| **未被覆盖** | 现有 `references/` 文件中没有类似内容? | 是 |
|
|
434
|
+
|
|
435
|
+
**五条全满足:写入。有任何一条不满足:不写。**
|
|
436
|
+
|
|
437
|
+
### 不该写入的典型情况
|
|
438
|
+
|
|
439
|
+
- 某应用自己的 token 持久化方式(项目特有)
|
|
440
|
+
- 对某个 URL 格式的处理(业务逻辑)
|
|
441
|
+
- 已经在 references 里有类似描述的变体(重复)
|
|
442
|
+
- 仅规避了这次环境偶然问题的 workaround(不可复用)
|
|
443
|
+
|
|
444
|
+
### 写入方式
|
|
445
|
+
|
|
446
|
+
满足标准后,直接编辑对应的 `references/tech-stacks/<tech-stack>.md`:
|
|
447
|
+
- 已有文件:在最相关的章节补充,或加入"常见陷阱"
|
|
448
|
+
- 无对应文件:新建,格式参照 `references/README.md`
|
|
449
|
+
|
|
450
|
+
写入后无需额外操作,references 会在下次执行本 Skill 时自动被查阅。
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## 快速参考
|
|
455
|
+
|
|
456
|
+
步骤详情见上方对应节,遇到不确定时回到该节重读。
|
|
457
|
+
|
|
458
|
+
| 步骤 | 关键产出 |
|
|
459
|
+
|---|---|
|
|
460
|
+
| 1 识别技术栈 | 技术栈摘要表 |
|
|
461
|
+
| 2 推导接入清单 | 日志文件清单 + 场景清单 |
|
|
462
|
+
| 3 搭建环境 | 每个来源持续写入独立文件 |
|
|
463
|
+
| 4 编写验证脚本 | 项目专属验证脚本 |
|
|
464
|
+
| 5 运行验证脚本 | 全部 OK 才继续 |
|
|
465
|
+
| 6 运行场景 | 日志可查、问题可定位 |
|
|
466
|
+
| 7 清理 | 后台捕获进程停止 |
|
|
467
|
+
| 8 生成文档 | README.md(Reference)+ debug-env.md(How-to)|
|
|
468
|
+
| 9 回馈 references | 新发现写入 references/tech-stacks/(五条标准全满足才写)|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# References — 技术栈日志最佳实践
|
|
2
|
+
|
|
3
|
+
本目录按技术栈积累日志捕获的最佳实践。执行第三阶段(环境搭建)时,**先查这里**,找到对应技术栈后直接参照执行;没有找到再自行设计,设计完后把结论补充进来。
|
|
4
|
+
|
|
5
|
+
## 当前收录
|
|
6
|
+
|
|
7
|
+
最佳实践文件统一放在 `tech-stacks/` 子目录:
|
|
8
|
+
|
|
9
|
+
| 文件 | 适用技术栈 |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `tech-stacks/node-process.md` | Node.js 裸进程(Express、Fastify、Koa、NestJS 等) |
|
|
12
|
+
| `tech-stacks/electron.md` | Electron(主进程 + preload + renderer) |
|
|
13
|
+
| `tech-stacks/docker-compose.md` | Docker Compose 多容器应用 |
|
|
14
|
+
| `tech-stacks/browser-spa.md` | 浏览器 SPA(React、Vue、Angular、Svelte 等) |
|
|
15
|
+
|
|
16
|
+
## 如何添加新条目
|
|
17
|
+
|
|
18
|
+
在新项目中设计了某个技术栈的日志捕获方案后,在 `tech-stacks/` 下新建文件:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
references/tech-stacks/<tech-stack>.md
|
|
22
|
+
|
|
23
|
+
内容结构:
|
|
24
|
+
- 适用场景
|
|
25
|
+
- 日志产生方式(日志去哪里)
|
|
26
|
+
- 捕获原则(先决条件、权衡)
|
|
27
|
+
- 接入方式(具体做法)
|
|
28
|
+
- 常见陷阱
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
文件名用技术栈名,小写加连字符,如 `python-django.md`、`rails.md`、`kafka-consumer.md`。
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# 浏览器 SPA 日志最佳实践
|
|
2
|
+
|
|
3
|
+
适用场景:React、Vue、Angular、Svelte 等 Web 前端,运行在浏览器中
|
|
4
|
+
|
|
5
|
+
## 核心问题
|
|
6
|
+
|
|
7
|
+
浏览器 console 完全独立于服务端,不会自动流到任何文件。必须在以下某个层面主动捕获:
|
|
8
|
+
|
|
9
|
+
1. **测试框架层**(Playwright、Cypress、Puppeteer)— 推荐,无需改业务代码
|
|
10
|
+
2. **应用代码层**(全局 console 拦截 + 发到后端)— 适合需要生产日志的场景
|
|
11
|
+
3. **网络代理层**(mitmproxy)— 适合捕获网络请求,不捕获 console
|
|
12
|
+
|
|
13
|
+
## 时间戳处理
|
|
14
|
+
|
|
15
|
+
浏览器 console 原生没有时间戳。必须在 hook 代码里主动添加 `new Date().toISOString()`。下方所有示例均已包含。
|
|
16
|
+
|
|
17
|
+
## 方案一:Playwright(推荐用于自动化测试)
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
// 在 test setup 或 beforeEach 中注册
|
|
21
|
+
page.on('console', msg => {
|
|
22
|
+
const ts = new Date().toISOString();
|
|
23
|
+
fs.appendFileSync(LOG, `${ts} ${msg.type()} ${msg.text()}\n`);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
page.on('pageerror', err => {
|
|
27
|
+
const ts = new Date().toISOString();
|
|
28
|
+
fs.appendFileSync(LOG, `${ts} uncaught ${err.message}\n`);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
page.on('requestfailed', req => {
|
|
32
|
+
const ts = new Date().toISOString();
|
|
33
|
+
fs.appendFileSync(LOG, `${ts} request-failed ${req.url()} ${req.failure().errorText}\n`);
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`page.on('requestfailed')` 能捕获网络请求失败,这是服务端日志看不到的视角。
|
|
38
|
+
|
|
39
|
+
## 方案二:Cypress
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
// cypress/support/commands.js 或 e2e.js
|
|
43
|
+
Cypress.on('window:console', (type, ...args) => {
|
|
44
|
+
const ts = new Date().toISOString();
|
|
45
|
+
cy.task('log', `${ts} ${type} ${args.join(' ')}`);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
Cypress.on('uncaught:exception', (err) => {
|
|
49
|
+
const ts = new Date().toISOString();
|
|
50
|
+
cy.task('log', `${ts} uncaught ${err.message}`);
|
|
51
|
+
return false; // 不让 Cypress 自动 fail
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
需在 `cypress.config.js` 定义 `log` task 写入独立的 `browser.log`。
|
|
56
|
+
|
|
57
|
+
## 方案三:应用代码全局拦截(适合非测试场景)
|
|
58
|
+
|
|
59
|
+
在应用入口(main.js / index.js / App.tsx 最顶部)注入:
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
const LOG_ENDPOINT = '/api/client-logs'; // 后端提供接收接口
|
|
63
|
+
|
|
64
|
+
['log', 'warn', 'error'].forEach(level => {
|
|
65
|
+
const orig = console[level];
|
|
66
|
+
console[level] = (...args) => {
|
|
67
|
+
orig(...args);
|
|
68
|
+
// 发到后端(fire-and-forget)
|
|
69
|
+
fetch(LOG_ENDPOINT, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: { 'Content-Type': 'application/json' },
|
|
72
|
+
body: JSON.stringify({ level, args: args.map(String), ts: Date.now() })
|
|
73
|
+
}).catch(() => {}); // 静默失败,不递归触发 console.error
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
window.addEventListener('error', e =>
|
|
78
|
+
fetch(LOG_ENDPOINT, { method: 'POST', body: JSON.stringify({
|
|
79
|
+
level: 'uncaught', message: e.message, stack: e.error?.stack }) }));
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
后端接收后写入独立的 `browser.log` 文件。注意 body 里的 `ts` 字段已是 epoch ms,后端写文件时转为 ISO 8601。
|
|
83
|
+
|
|
84
|
+
## 常见陷阱
|
|
85
|
+
|
|
86
|
+
- `console.log` 里的对象在 Playwright 的 `msg.text()` 里是 `[object Object]` → 用 `msg.args()` 逐个 `jsonValue()` 展开
|
|
87
|
+
- Cypress 的 `uncaught:exception` 默认会让测试 fail → 根据需要 `return false` 决定是否忽略
|
|
88
|
+
- 应用代码拦截在生产环境发送日志会带来隐私和流量问题 → 仅在 dev/staging 启用
|
|
89
|
+
- `fetch` 失败时不要调用 `console.error`,否则递归死循环
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Docker Compose 日志最佳实践
|
|
2
|
+
|
|
3
|
+
适用场景:用 docker-compose.yml 编排的多容器应用
|
|
4
|
+
|
|
5
|
+
## 日志产生方式
|
|
6
|
+
|
|
7
|
+
Docker 将每个容器的 stdout/stderr 统一收集为容器日志,通过 `docker logs` 读取。
|
|
8
|
+
容器内写到文件的日志,Docker 不会自动收集,需单独处理。
|
|
9
|
+
|
|
10
|
+
**最佳实践:让容器把日志写到 stdout/stderr,而不是文件。**
|
|
11
|
+
这样 Docker 统一管理,tail 最简单。如果应用写文件,考虑用 volume mount 后在宿主机 tail。
|
|
12
|
+
|
|
13
|
+
## 时间戳处理
|
|
14
|
+
|
|
15
|
+
Docker 自带时间戳(`docker logs --timestamps`),但格式是 RFC3339(`2024-01-15T14:30:01.234567890Z`),与 ISO 8601 兼容,**不需要额外处理**。
|
|
16
|
+
|
|
17
|
+
但默认 `docker logs -f` 不输出时间戳——需要加 `--timestamps` 标志:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
docker logs -f --timestamps mycontainer 2>&1 >> container.log &
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
如果容器应用本身在日志行里也有时间戳,两者都会出现。这是无损的,不需要去掉任何一个。
|
|
24
|
+
|
|
25
|
+
## 接入方式
|
|
26
|
+
|
|
27
|
+
每个容器写入各自独立的日志文件,文件名即来源标识:
|
|
28
|
+
|
|
29
|
+
**遍历所有容器:**
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
LOGS=/tmp/myproject-logs
|
|
33
|
+
mkdir -p "$LOGS"
|
|
34
|
+
|
|
35
|
+
for c in $(docker compose ps -q); do
|
|
36
|
+
name=$(docker inspect --format='{{.Name}}' "$c" | tr -d '/')
|
|
37
|
+
docker logs -f --timestamps "$c" >> "$LOGS/${name}.log" 2>&1 &
|
|
38
|
+
done
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**单容器:**
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
docker logs -f --timestamps <container_name> >> "$LOGS/api.log" 2>&1 &
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**从历史日志开始(适合容器已在运行):**
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
docker logs --since 5m -f --timestamps <container_name> >> "$LOGS/api.log" 2>&1 &
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 容器内写文件的日志
|
|
54
|
+
|
|
55
|
+
如果容器把日志写到容器内文件(如 `/app/logs/app.log`),有两种方案:
|
|
56
|
+
|
|
57
|
+
**方案 A:exec 进容器 tail(简单,适合临时调试)**
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
docker exec <container> tail -F /app/logs/app.log >> "$LOGS/api-file.log" &
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**方案 B:volume mount 到宿主机(推荐,持久)**
|
|
64
|
+
|
|
65
|
+
在 docker-compose.yml 中挂载日志目录:
|
|
66
|
+
```yaml
|
|
67
|
+
volumes:
|
|
68
|
+
- ./logs/api:/app/logs
|
|
69
|
+
```
|
|
70
|
+
然后在宿主机 tail 该目录(若文件无时间戳,capture 层注入)。
|
|
71
|
+
|
|
72
|
+
## 常见陷阱
|
|
73
|
+
|
|
74
|
+
- 容器重启后 `docker logs -f` 会断开 → 需要重新 attach,或用 `--follow` 加监控脚本
|
|
75
|
+
- 部分镜像把日志写到 `/dev/null`(silent by default)→ 检查应用的日志级别环境变量
|
|
76
|
+
- `docker compose logs -f` 会混合所有容器输出且格式难以 grep → 分容器 tail 更可控
|
|
77
|
+
- 容器时区与宿主机不同 → 日志时间戳可能不对齐,需注意 `TZ` 环境变量
|