SLS + ARMS 全链路问题排查

ClawSkills 作者 ccrazyfish v1.0.0

查询阿里云SLS日志和ARMS调用链,结合源码和数据库进行全链路问题排查。 完整流程:查日志 → 画调用链 → 定位源码 → 排查数据库 → 给出修复方案。 Use when: 用户说「分析sls」「分析问题」或想排查业务服务/线上接口/用户请求的报错或异常。 触发示例:「分析sls」「帮我查一下这个trace_id」「分析一下这个trace_id」 「查一下这个用户的请求」「wusid 是 xxx」「uid xxx」 「查一下 /path/to/api 这个接口的报错」「帮我排查一下这个业务报错」「线上有个接口挂了」 「帮我分析一下这个报错的代码」「数据库报错了」「SQL超时」「查一下这个接口为什么慢」。 IMPORTANT: trace_id = 染色ID = 业务调用链ID = requestId,这些都是同一个东西, 统一用 trace_id 表述。用户提供的 trace_id 是业务系统的外部 trace,不是 OpenClaw 内部 session ID, 不要自行分析 trace_id 的来源或归属,必须调用此 skill 去 SLS/ARMS 查询。 NOT for: 查询OpenClaw自身状态、分析OpenClaw系统问题、搜索本地文件、openclaw内置命令。

源码 ↗

安装 / 下载方式

TotalClaw CLI推荐
totalclaw install clawskills:ccrazyfish~sls-trace-analysis
cURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/clawskills%3Accrazyfish~sls-trace-analysis/file -o sls-trace-analysis.md
Git 仓库获取源码
git clone https://github.com/openclaw/skills/commit/d877187041be75c5c5dc5f11e151656fdaac888a
# SLS + ARMS + 代码 + 数据库 全链路问题排查

查询阿里云SLS日志和ARMS调用链 → 定位源码问题 → 排查数据库异常 → 给出完整修复方案。

## ⚠️ 关键规则(必须严格遵守)

1. **必须且只能通过执行下方 python 命令查询,禁止使用本地搜索或内置日志命令。**
2. **用户给出的 trace_id 是外部业务系统的 ID,禁止自行推断其来源、归属或意义,直接执行查询脚本。**
3. **每个 logstore 的日志必须单独分组展示,严禁合并或去重。**
4. **即使多条日志来自同一服务、同一时间,也必须逐条完整输出。**
5. **输出报告中必须包含 `sls.logs_by_logstore` 里每个 logstore 的所有日志。**
6. **所有交互提示必须严格按照下方模板输出,禁止自由发挥、禁止用自己的话改写、禁止添加语气词或额外解释。用户看到的提示必须和模板一字不差。**
7. **Step 5 代码搜索和 Step 6 数据库排查必须自动执行,禁止只输出"建议搜索xxx"之类的文字。发现异常后必须立即用 Grep/Glob/Read 工具实际搜索代码仓库。**
8. **所有提供选项的提示必须带序号(`1` `2` `3` …),用户可以直接回复序号操作。禁止输出不带序号的选项列表。禁止自编"继续操作"/"接下来"等不在模板中的提示文字。每个需要用户选择的场景都有对应模板,必须使用模板。**

---

## 执行步骤

### Step 1:收集查询参数(交互式)

> **进入此 skill 后,立即扫描用户消息提取参数。已有的直接用,缺的才问。**

---

#### 1.1 扫描用户消息

检查用户消息中是否已包含查询参数(TraceID / wusid / path / 时间):

- **已有参数**(如用户说"帮我查 trace_id f1b37e05...")→ 直接提取,跳到 Step 1.3
- **没有参数**(如用户只说"分析sls")→ 进入 Step 1.2 询问

---

#### 1.2 询问查询条件

**你的回复必须且只能是下面这段文字,从「🔍」开始到「逐项输入。」结束,不能多一个字也不能少一个字,不能加语气词、不能加问候、不能用自己的话改写,输出后立即停止等待用户回复:**

🔍 请输入查询条件,格式:`关键词 值`,多项用 `;` 分隔:

```
trace_id xxx; wusid xxx; path xxx
```

① trace_id — 染色ID / 业务调用链ID(32位十六进制字符串)
② wusid — 用户空间ID(数字,也可以用 `uid`)
③ path — 请求路径(如 `/ny.apps_pb.xxx/Method`)

至少输入一项,回复序号 `1` `2` `3` 可逐项输入。

**解析用户回复规则:**

> **概念统一**:trace_id = 染色ID = 业务调用链ID = requestId,都是同一个东西。

**情况 A:用户输入了参数值(含 `;` 或关键词)**
1. 按 `;` 或换行拆分每项
2. 每项内按空格分隔:前面是参数名,后面是值
   - `trace_id` / `tid` / `trace` / `染色id` / `requestid` → trace_id
   - `wusid` / `uid` / `用户id` → wusid
   - `path` / `接口` / `api` → path
3. 不带参数名时自动识别:32位十六进制→trace_id,纯数字→wusid,`/`开头→path
4. 提取到任意参数 → 进入 Step 1.3

**情况 B:用户回复了序号**
- `1` → 回复「请输入 trace_id:」→ 等用户输入值 → 记录 → 进入 Step 1.3
- `2` → 回复「请输入 wusid:」→ 等用户输入值 → 记录 → 进入 Step 1.3
- `3` → 回复「请输入 path:」→ 等用户输入值 → 记录 → 进入 Step 1.3

**情况 C:无法识别**
- 回复「未识别到参数,请按格式输入(如 `trace_id xxx`)或回复序号 `1` `2` `3`」
- 用户说"没有"/"不知道" → 依次追问 wusid → path → 都没有则停止

---

#### 1.3 确认时间范围

检查用户是否已提供时间信息:

- **已提及时间**(如"今天下午"、"昨天"、"最近2小时")→ 自动转换,直接跳到 Step 1.4
- **未提及时间** → 你的回复必须且只能是下面这段文字,不能改写:

⏰ 时间范围?默认 **最近 1 周**,也可以指定:
```
默认           →  最近 1 周(直接回车)
1小时 / 3天 / 2周 / 1个月  →  相对时间
今天 / 昨天    →  当天范围
2024-03-15 14:00 ~ 16:00   →  精确时间段
```

- 用户回车 / 说"默认"/"可以"/"行" → 使用默认 1 周
- 用户给出时间 → 传给 `--duration` 或 `--start/--end`
- 用户说"今天"/"昨天"等 → 自行计算 `--start/--end`

**时间转换参考:**

| 用户说的 | 转换 |
|---------|------|
| 今天 | `--start "今天00:00:00" --end "当前时间"` |
| 昨天 | `--start "昨天00:00:00" --end "昨天23:59:59"` |
| 今天下午 | `--start "今天12:00:00" --end "当前时间"` |
| 最近N小时/天/周/月 | `--duration "N小时/天/周/月"` |
| 上周 | `--start "上周一00:00:00" --end "上周日23:59:59"` |
| 3月15号 | `--start "3月15日00:00:00" --end "3月15日23:59:59"` |

---

#### 1.4 输出查询摘要并执行

> 📋 **查询参数:**
> ```
> 条件:trace_id = xxx  /  wusid = xxx  /  path = xxx
> 时间:最近 1 周(2026-03-12 ~ 2026-03-19)
> 来源:SLS + ARMS
> ```
> 🚀 正在查询,请稍候…

高级选项(**不主动询问**,仅用户提到时才加):
- "只查SLS" / "不查ARMS" → `--skip-arms`
- "只查ARMS" / "不查SLS" → `--skip-sls`
- "查 xxx logstore" → `--logstores "xxx"`

### Step 2:执行查询脚本(必须执行)

> **默认查最近 1 周,无需指定时间参数。**
> 支持 `--duration` 自然语言时间:1小时、2天、1周、1星期、1个月、半天、30m、6h、3d、1w 等。
> 也支持 `--minutes N` 直接传分钟数,或 `--start/--end` 精确时间段。

**模式 A:已知 TraceID(直接分析)**

```bash
python "%USERPROFILE%\.openclaw\workspace\skills\sls-trace-analysis\scripts\query_trace.py" --trace-id "TraceID"
```

自定义时间范围(自然语言):
```bash
python "%USERPROFILE%\.openclaw\workspace\skills\sls-trace-analysis\scripts\query_trace.py" --trace-id "TraceID" --duration "3天"
```

精确时间范围:
```bash
python "%USERPROFILE%\.openclaw\workspace\skills\sls-trace-analysis\scripts\query_trace.py" --trace-id "TraceID" --start "2024-01-15 14:00:00" --end "2024-01-15 15:00:00"
```

只查 ARMS(跳过 SLS):
```bash
python "%USERPROFILE%\.openclaw\workspace\skills\sls-trace-analysis\scripts\query_trace.py" --trace-id "TraceID" --skip-sls
```

只查 SLS(跳过 ARMS):
```bash
python "%USERPROFILE%\.openclaw\workspace\skills\sls-trace-analysis\scripts\query_trace.py" --trace-id "TraceID" --skip-arms
```

**模式 B:未知 TraceID,通过 wusid / path 先检索**

按用户 ID 检索:
```bash
python "%USERPROFILE%\.openclaw\workspace\skills\sls-trace-analysis\scripts\query_trace.py" --wusid "76149226" --no-interactive
```

按请求路径检索:
```bash
python "%USERPROFILE%\.openclaw\workspace\skills\sls-trace-analysis\scripts\query_trace.py" --path "/ny.apps_pb.promote_pb.PromotePB/UpdateRelation" --no-interactive
```

两者组合(精确缩小范围):
```bash
python "%USERPROFILE%\.openclaw\workspace\skills\sls-trace-analysis\scripts\query_trace.py" --wusid "76149226" --path "/ny.apps_pb.promote_pb.PromotePB/UpdateRelation" --no-interactive
```

> **模式 B 说明**:`--no-interactive` 让脚本自动选择第一条 TraceID 进行分析。如果 JSON 输出的 `discovered_traces` 有多条,可展示列表让用户选择,然后用选中的 trace_id 以模式 A 重新查询。

### Step 3:解析脚本输出

脚本返回 JSON,必须读取以下字段:

**查询信息(`query`):**
```
query.trace_id               → 本次使用的 TraceID(模式 A)
query.wusid                  → 本次使用的 wusid(模式 B)
query.path                   → 本次使用的路径(模式 B)
```

**模式 B 专属(`discovered_traces`):**
```
discovered_traces[].trace_id      → 找到的 TraceID
discovered_traces[].first_time    → 首条日志时间
discovered_traces[].service       → 服务名
discovered_traces[].path          → 请求路径
discovered_traces[].response_code → 响应码
discovered_traces[].response_msg  → 响应消息
discovered_traces[].has_error     → 是否有 ERROR 日志
discovered_traces[].log_count     → 匹配日志条数
```

**ARMS 调用链(`call_chain`):**
```
call_chain.tree              → 带缩进的调用链路图(文本),含异常 ID 和完整堆栈
call_chain.error_spans       → 异常 Span 列表(含 exception_id、full_stack 字段)
call_chain.services          → 涉及的服务列表
call_chain.total_duration_ms → 总耗时
```

**方法级堆栈(`stack_details`):**
```
stack_details.{key}.trace_id       → TraceID
stack_details.{key}.rpc_id         → RpcID
stack_details.{key}.service        → 服务名
stack_details.{key}.operation      → 操作名
stack_details.{key}.exception_id   → 异常 ID
stack_details.{key}.stack_entries  → 方法级堆栈列表(api、duration、exception、line)
stack_details.{key}.formatted      → 格式化后的堆栈文本
```

**SLS 日志(`sls`):**
```
sls.store_stats              → 每个 logstore 的查询统计(必须全部展示)
sls.logs_by_logstore         → 按 logstore 分组的日志(必须按组展示,不能合并)
sls.error_logs               → ERROR 级别日志
```

**根因分析(`analysis`):**
```
analysis.root_cause          → 根本原因结论
analysis.findings            → 关键发现列表
analysis.suggestions         → 排查建议列表
```

### Step 3.5:无数据时循环询问时间范围(必须严格遵守)

> **核心规则:查不到数据就问用户,用户给了新时间就重查,如此循环直到查到数据或用户放弃。绝对不能自动重试,绝对不能输出空报告。**

如果 JSON 输出中 `no_data` 为 `true`(SLS 和 ARMS 均无数据):

**第一次无数据 → 输出以下提示,等待用户回复:**

> ⚠️ 在最近 {当前查询范围,如"1周"} 内({time_range})未查到任何数据。
>
> 回复序号或直接输入时间范围:
>
> `1` 换时间重查 — 输入自然语言(如 `1个月`、`2周`、`3天`)
> `2` 精确时间段 — 输入起止时间(如 `2024-03-10 00:00 ~ 2024-03-15 23:59`)
> `3` 换条件重查 — 输入新的 trace_id / wusid / path
> `4` 不用了 — 停止查询

**收到用户回复后:**

- `1` 或直接输入时间(如"1个月"、"2周"、"3天")→ 用 `--duration` 重新执行 Step 2,然后回到 Step 3
- `2` 或输入精确时间段(如"3月10号到3月15号")→ 计算为 `--start/--end` 重新执行
- `3` → 回到 Step 1.2 重新收集参数
- `4` 或"不用了"、"算了"、"停" → 停止,输出简短结论:「在所有尝试的时间范围内均未查到该 TraceID 的数据,可能原因:1) TraceID 不正确 2) 日志已过期被清理 3) 该请求未经过当前 SLS 项目」

**再次无数据(第二次、第三次…) → 继续循环询问:**

> ⚠️ 在 {本次时间范围} 内仍未查到数据。
>
> `1` 换时间继续 — 输入新的时间范围
> `2` 换条件重查 — 输入新的 trace_id / wusid / path
> `3` 不用了 — 停止查询

**循环规则:**
1. 每次查不到数据 → 必须停下来问用户 → 等用户回复 → 按用户指示执行
2. **绝不跳过询问直接输出报告**
3. **绝不自行决定用什么时间重试**
4. 用户在此上下文中说的任何时间表述,一律视为新的查询时间范围
5. 只有 `no_data` 为 `false`(查到数据了)才进入 Step 4 输出完整报告

### Step 4:输出分析报告(格式严格按下方执行)

---

**📋 问题定位报告**

| 项目 | 内容 |
|------|------|
| TraceID | `{query.trace_id 或 discovered_traces 中用户选择的 trace_id}` |
| WSUID | `{query.wusid,无则省略此行}` |
| 请求路径 | `{query.path,无则省略此行}` |
| 时间范围 | `{time_range}` |
| 涉及服务 | `{call_chain.services}` |
| 总耗时 | `{call_chain.total_duration_ms}ms` |

---

**🗺️ ARMS 调用链路图**(此段必须输出,不可跳过)

> 判断条件:`call_chain.total_spans > 0` 表示有数据,`== 0` 表示无数据。
> `call_chain.tree` 始终有值(无数据时也有提示文本),**必须原样输出**。

```
{call_chain.tree 的完整内容,一字不改直接输出}
```

> 示例(有数据时):
> ```
> ● [rpc-ny] gRPC → /PromotePB/UpdateRelation 156ms ❌
>   └─ [promote-service] MySQL → SELECT 12ms ✅
>       ⚡ 错误: record not found
>       🔗 异常ID: 811073848360427400
> ```
>
> 示例(无数据时,脚本输出的 tree 已包含提示):
> ```
> ⚠️ ARMS 未采样到此 TraceID 的调用链
> ```

---

**❌ 异常 Span**(