bookworm-smart-assistant/skills/debugger-expert/references/debugging-playbook.md

557 lines
15 KiB
Markdown
Raw Permalink 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.

# 系统化调试方法论与工具链 (Debugging Playbook)
> 本文档为 debugger-expert 技能的核心参考,涵盖调试方法论、心智模型和主流语言工具链。
---
## 一、科学调试方法论
### 1.1 假设-验证循环 (Scientific Debugging)
调试的本质是科学实验:观察现象 -> 形成假设 -> 设计实验 -> 验证结果。
```
┌─────────────┐
│ 观察现象 │ 收集错误信息、日志、堆栈
└──────┬──────┘
┌─────────────┐
│ 形成假设 │ 基于经验和证据提出可能原因
└──────┬──────┘
┌─────────────┐
│ 设计实验 │ 构造能验证或推翻假设的测试
└──────┬──────┘
┌─────────────┐
│ 验证结果 │ 假设成立 → 修复;假设失败 → 新假设
└──────┬──────┘
┌─────────────┐
│ 根因分析 │ 找到根本原因,而非表面症状
└─────────────┘
```
**关键原则**
- 每次只改一个变量,否则无法确定哪个改动有效
- 记录每次尝试和结果,避免重复无用操作
- 不要猜测,用证据说话
### 1.2 二分法定位 (Binary Search Debugging)
适用于"某处出错但不知道在哪"的场景:
```bash
# Git bisect 自动定位引入 Bug 的 commit
git bisect start
git bisect bad # 当前版本有问题
git bisect good v1.2.0 # 这个版本没问题
# Git 会自动 checkout 中间的 commit你只需测试并标记
git bisect good # 或 git bisect bad
# 最终定位到引入 Bug 的精确 commit
git bisect reset # 结束 bisect
```
**代码中的二分法**
- 在代码中间插入日志,确认上半段还是下半段出问题
- 注释掉一半代码,逐步缩小范围
- 对于数据问题,检查中间步骤的数据是否正确
### 1.3 最小复现用例 (Minimal Reproduction)
```
完整应用 → 剥离无关模块 → 剥离无关依赖 → 最小可复现代码
```
**构建步骤**
1. 在全新环境中尝试复现
2. 逐步去除无关代码,直到去掉任何一行都无法复现
3. 记录精确的复现步骤(环境、输入、操作序列)
---
## 二、调试心智模型
### 2.1 自上而下 (Top-Down)
从用户可见的症状出发,沿调用链向下追踪:
```
用户看到的错误
→ 前端组件
→ API 调用
→ 后端 Handler
→ Service 层
→ 数据库查询
```
**适用场景**:错误信息明确,能清晰追踪调用链。
### 2.2 自下而上 (Bottom-Up)
从底层日志或异常出发,向上追溯调用者:
```
数据库报错 connection refused
→ ORM 连接池状态
→ 服务配置
→ 环境变量
→ Docker Compose 配置
```
**适用场景**:底层有明确的报错日志,需要理解为什么被触发。
### 2.3 差异对比法 (Differential Debugging)
比较"正常"和"异常"两种情况的差异:
```bash
# 对比两个环境的配置差异
diff <(ssh prod "env | sort") <(ssh staging "env | sort")
# 对比两个请求的差异
diff response_good.json response_bad.json
# 对比两个 commit 的代码差异
git diff abc123 def456 -- src/
```
### 2.4 回退法 (Rollback Debugging)
当问题突然出现时,回退到已知正常状态:
```bash
# 回退到上一个正常的 commit
git stash # 保存当前改动
git checkout <good-commit>
# 测试是否正常
# 然后逐个引入改动,找到引入问题的变更
```
---
## 三、JavaScript/TypeScript 调试工具链
### 3.1 Chrome DevTools
#### Sources 面板
```javascript
// 代码中插入断点
debugger; // 浏览器会在此暂停
// 条件断点(在 DevTools 中右键行号设置)
// 条件示例item.id === 42
// Logpoints不暂停只输出日志
// 右键行号 → Add logpoint → 输入: "value is", myVar
```
#### Network 面板
```
关键检查项:
- Status: 检查 HTTP 状态码
- Timing: 查看各阶段耗时DNS, TCP, TTFB, Content Download
- Headers: 验证请求头/响应头Content-Type, Authorization, CORS headers
- Preview/Response: 检查实际响应数据
- 右键 → Copy as cURL: 在终端中重现请求
```
#### Performance 面板
```
录制步骤:
1. 点击录制按钮
2. 执行要分析的操作
3. 停止录制
4. 检查 Main 线程火焰图
5. 关注长任务(超过 50ms 的红色标记)
```
#### Memory 面板
```
内存泄漏排查:
1. 拍摄 Heap Snapshot快照1
2. 执行可疑操作
3. 拍摄 Heap Snapshot快照2
4. 选择 "Comparison" 视图,对比两个快照
5. 按 "Size Delta" 排序,查看增长最多的对象
```
### 3.2 VS Code 调试配置
```jsonc
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
// Node.js 应用调试
{
"type": "node",
"request": "launch",
"name": "Debug Node App",
"program": "${workspaceFolder}/src/index.ts",
"preLaunchTask": "tsc: build",
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"console": "integratedTerminal"
},
// Next.js 全栈调试
{
"type": "node",
"request": "launch",
"name": "Debug Next.js",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["dev"],
"port": 9230,
"console": "integratedTerminal",
"serverReadyAction": {
"pattern": "- Local:.+(https?://.+)",
"uriFormat": "%s",
"action": "debugWithChrome"
}
},
// 附加到已运行的进程
{
"type": "node",
"request": "attach",
"name": "Attach to Process",
"port": 9229,
"restart": true
}
]
}
```
#### 条件断点与 Logpoints
```
VS Code 中:
- 条件断点:右键行号 → Add Conditional Breakpoint → 输入条件表达式
- Hit Count 断点:右键 → Add Conditional Breakpoint → Hit Count → 输入次数
- Logpoints右键行号 → Add Logpoint → 使用 {} 插入表达式
示例: "User {user.name} logged in, role: {user.role}"
```
### 3.3 Node.js 调试
```bash
# 启动调试模式
node --inspect src/server.js # 默认 9229 端口
node --inspect-brk src/server.js # 在第一行暂停
# 使用 ndb更好的 Node 调试器)
npx ndb node src/server.js
# 内存快照对比
node --expose-gc -e "
global.gc();
const before = process.memoryUsage();
// ... 执行操作 ...
global.gc();
const after = process.memoryUsage();
console.log('Heap used delta:', after.heapUsed - before.heapUsed);
"
# 生成 Heap Snapshot
node -e "
const v8 = require('v8');
const fs = require('fs');
const snapshot = v8.writeHeapSnapshot();
console.log('Snapshot written to:', snapshot);
"
```
---
## 四、Python 调试工具链
### 4.1 pdb/ipdb 常用命令速查
```python
# 在代码中设置断点
import pdb; pdb.set_trace() # 标准 pdb
import ipdb; ipdb.set_trace() # 增强版支持语法高亮、Tab 补全)
breakpoint() # Python 3.7+ 推荐写法
```
```
常用命令:
n (next) - 执行下一行(不进入函数)
s (step) - 单步执行(进入函数)
c (continue) - 继续执行到下一个断点
r (return) - 执行到当前函数返回
l (list) - 显示当前代码上下文
ll (longlist) - 显示整个函数代码
p expr - 打印表达式值
pp expr - 美观打印表达式值
w (where) - 显示调用堆栈
u (up) - 向上移动堆栈帧
d (down) - 向下移动堆栈帧
b 42 - 在第 42 行设置断点
b func_name - 在函数入口设置断点
b 42, x > 10 - 条件断点:仅当 x > 10 时触发
cl (clear) - 清除所有断点
q (quit) - 退出调试器
```
### 4.2 debugpy (VS Code 远程调试)
```python
# 在代码中嵌入调试服务器
import debugpy
debugpy.listen(("0.0.0.0", 5678))
print("等待调试器连接...")
debugpy.wait_for_client() # 阻塞直到 VS Code 连接
```
```jsonc
// .vscode/launch.json - 远程附加
{
"type": "debugpy",
"request": "attach",
"name": "Attach to Remote Python",
"connect": { "host": "localhost", "port": 5678 },
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "/app" // Docker 容器中的路径
}
]
}
```
### 4.3 内存分析
```python
# tracemalloc - 内置内存跟踪
import tracemalloc
tracemalloc.start()
# ... 执行可疑代码 ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("[ 内存分配 Top 10 ]")
for stat in top_stats[:10]:
print(stat)
# objgraph - 对象引用图
import objgraph
objgraph.show_most_common_types(limit=20) # 最多的对象类型
objgraph.show_growth(limit=10) # 对象增长情况
objgraph.show_backrefs(obj, max_depth=5, # 谁引用了这个对象
filename='refs.png')
```
---
## 五、Go 调试工具链
### 5.1 Delve (dlv) 常用命令
```bash
# 启动调试
dlv debug ./cmd/server # 编译并调试
dlv debug ./cmd/server -- --port 8080 # 带参数
dlv attach <pid> # 附加到运行中的进程
dlv test ./pkg/service # 调试测试
# 远程调试(服务器端)
dlv debug --headless --listen=:2345 --api-version=2 ./cmd/server
# 本地连接
dlv connect localhost:2345
```
```
常用命令:
break (b) main.go:42 - 设置断点
break funcName - 在函数入口设置断点
condition <id> i == 5 - 条件断点
continue (c) - 继续执行
next (n) - 下一行
step (s) - 单步进入
stepout (so) - 跳出当前函数
print (p) variable - 打印变量
locals - 显示所有局部变量
goroutines (grs) - 列出所有 goroutine
goroutine <id> - 切换到指定 goroutine
stack (bt) - 显示调用堆栈
```
```jsonc
// .vscode/launch.json - Go 调试
{
"type": "go",
"request": "launch",
"name": "Debug Go Server",
"program": "${workspaceFolder}/cmd/server",
"args": ["--config", "config.dev.yaml"],
"env": { "GO_ENV": "development" }
}
```
### 5.2 pprof 性能分析
```go
// 在应用中启用 pprof
import _ "net/http/pprof"
// 确保有 HTTP 服务在运行pprof 会注册到 DefaultServeMux
// 或手动注册到自定义 mux
import "net/http/pprof"
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP)
```
```bash
# CPU 分析(采集 30 秒)
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# 内存分析
go tool pprof http://localhost:6060/debug/pprof/heap
# Goroutine 分析(排查泄漏)
go tool pprof http://localhost:6060/debug/pprof/goroutine
# 交互式命令
(pprof) top 20 # 热点函数 Top 20
(pprof) list funcName # 查看函数逐行消耗
(pprof) web # 生成调用图并在浏览器打开
(pprof) flame # 生成火焰图
```
### 5.3 Race Detector
```bash
# 编译和运行时检测数据竞争
go run -race ./cmd/server
go test -race ./...
go build -race -o server ./cmd/server
# 输出示例:
# WARNING: DATA RACE
# Goroutine 7 (running) at:
# main.go:42 +0x1a8
# Previous write at:
# main.go:38 +0x130
# Goroutine 6 (running) at:
# main.go:38 +0x130
```
**常见修复方式**
- 使用 `sync.Mutex``sync.RWMutex` 保护共享数据
- 使用 `channel` 代替共享内存
- 使用 `sync/atomic` 处理简单的计数器
- 使用 `sync.Map` 代替普通 map 的并发读写
---
## 六、日志分析技巧
### 6.1 结构化日志
```typescript
// Node.js - 使用 pino 结构化日志
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level: (label) => ({ level: label }),
},
});
logger.info({ userId: 123, action: 'login', ip: '10.0.0.1' }, '用户登录成功');
// 输出: {"level":"info","userId":123,"action":"login","ip":"10.0.0.1","msg":"用户登录成功"}
```
```python
# Python - 使用 structlog 结构化日志
import structlog
logger = structlog.get_logger()
logger.info("用户登录", user_id=123, action="login", ip="10.0.0.1")
```
### 6.2 日志级别策略
```
ERROR - 需要立即处理的错误(数据库挂了、支付失败)
WARN - 可恢复但异常的情况(重试成功、降级处理)
INFO - 重要业务事件(用户注册、订单创建)
DEBUG - 开发调试信息(函数入参、中间状态)
TRACE - 极细粒度追踪(循环内每步状态)
```
### 6.3 关联 ID 追踪 (Correlation ID)
```typescript
// Express 中间件 - 为每个请求生成关联 ID
import { randomUUID } from 'crypto';
app.use((req, res, next) => {
req.correlationId = req.headers['x-correlation-id'] || randomUUID();
res.setHeader('x-correlation-id', req.correlationId);
// 注入到日志上下文
req.logger = logger.child({ correlationId: req.correlationId });
next();
});
// 在所有后续日志中自动携带 correlationId
req.logger.info({ userId: user.id }, '处理用户请求');
```
---
## 七、生产环境调试
### 7.1 只读调试原则
```
生产环境调试铁律:
1. 绝不修改生产数据
2. 绝不在生产环境执行写操作
3. 使用只读副本进行数据查询
4. 优先分析日志和监控数据
5. 必要时使用 feature flag 控制变更
```
### 7.2 Feature Flag 回退
```typescript
// 使用 feature flag 安全回退
if (featureFlags.isEnabled('new-payment-flow')) {
return newPaymentHandler(req);
} else {
return legacyPaymentHandler(req); // 随时可回退
}
// 关闭 flag 不需要部署,立即生效
```
### 7.3 蓝绿切换排查
```bash
# 检查当前活跃环境
kubectl get service myapp -o jsonpath='{.spec.selector.version}'
# 流量切换到旧版本
kubectl patch service myapp -p '{"spec":{"selector":{"version":"blue"}}}'
# 确认切换成功
kubectl get endpoints myapp
```
### 7.4 生产日志快速过滤
```bash
# 按错误级别过滤
kubectl logs deploy/myapp --since=1h | jq 'select(.level == "error")'
# 按关联 ID 追踪一个请求的完整链路
kubectl logs deploy/myapp --since=1h | jq 'select(.correlationId == "abc-123")'
# 按用户 ID 过滤
kubectl logs deploy/myapp --since=1h | jq 'select(.userId == 42)'
# Docker Compose 环境
docker compose logs --since=1h app | grep "ERROR"
docker compose logs -f app 2>&1 | jq 'select(.level == "error")'
```