关于 observability-triage
用于在 Cloudflare Workers Observability 中诊断生产错误的技能,面向负责排查 Workers 的开发者。它涵盖准确统计调用、调查工作流失败、性能分析及过滤已确认的噪声。
主要功能
- 01查询 Workers Observability
- 02准确统计调用次数
- 03检查工作流实例失败
- 04采集 CPU 和堆分析
- 05过滤已知运行时噪声
适用场景
- 01排查生产错误激增
- 02追踪失败的工作流步骤
- 03比较不同部署的错误
- 04分析繁忙的生产隔离区
MCP 相关
使用 cloudflare-api MCP 服务查询 Cloudflare Workers Observability;若相关工具不可用,README 说明了认证流程。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ every-app/ open-seo --skill observability-triage安装后在 Claude Code 中输入/observability-triage确认已加载。
详细介绍
可观测性排查
查询 Cloudflare Workers 可观测性数据,排查生产环境错误并正确计数,同时跳过已调查并确认无果的噪声。应用下方已知噪声列表,不要重新调查其中的条目。
访问
- 运行时解析账户,切勿硬编码:
cloudflare-apiMCP 服务会在mcp__cloudflare-api__execute中预绑定accountId,而npx wrangler whoami会打印该值。要排查的 Worker 是本仓库部署的 Worker(参见deploy/alchemy/alchemy.run.ts):主应用 Worker 以及辅助 Worker(审计引擎、落地页、自托管)。 - 通过
cloudflare-apiMCP 服务(mcp__cloudflare-api__execute)查询。如果缺少该服务的工具,请运行其身份验证流程并将 URL 提供给用户——wrangler 的 OAuth 令牌在可观测性 API 上会返回 403(缺少作用域),因此不要浪费时间尝试使用 wrangler 令牌调用 curl。 - PostHog 是第二个错误来源,但无法查看
exceededMemory/canceled/responseStreamDisconnected结果——只有这里能回答有关 Worker 结果的问题。
查询方法(已验证的格式)
POST /accounts/{account_id}/workers/observability/telemetry/query。以下格式都至关重要;API 返回的 400 错误信息含糊不清:
-
必须提供
queryId: "adhoc"。时间范围使用 epoch 毫秒:timeframe: { from, to }。 -
按结果统计调用次数:
{ "queryId": "adhoc", "timeframe": { "from": 0, "to": 0 }, "parameters": { "datasets": ["cloudflare-workers"], "filters": [ { "key": "$metadata.type", "operation": "eq", "value": "cf-worker-event", "type": "string" } ], "calculations": [{ "operator": "count", "alias": "count" }], "groupBys": [ { "value": "$workers.scriptName", "type": "string" }, { "value": "$workers.outcome", "type": "string" } ], "limit": 100 }, "view": "calculations", "limit": 100 } -
cf-worker-event筛选条件用于确保统计的是调用次数;没有该条件,就会统计每一行日志。 -
原始样本:设置
view: "events",calculations/groupBys为空,limit设小一些。事件包含$metadata(service、trigger、level、message、fingerprint、requestId)和$workers(outcome、scriptName、scriptVersion、wallTimeMs、cpuTimeMs、eventType)。 -
按消息分组统计错误级别的应用日志:筛选
$metadata.level eq error,按$metadata.message分组。 -
已验证的筛选操作:
eq、neq。百分位数:API 要求使用"median",而不是"p50"。在客户端对获取到的事件按子字符串筛选。 -
实用的深入分析:按
$metadata.trigger(路由)、$workers.scriptVersion(部署是否导致窗口期内的错误率变化)、$workers.event.request.headers.user-agent分组。
计数注意事项
- 分组结果未排序,且实际上最多只返回约 10 行(无论
limit设为多少)。 未出现某个分组 ≠ 数量为零。要查看错误结果,请添加$workers.outcome neq ok,不要寄希望于错误行恰好出现在返回结果中;在客户端排序。 - 单个隔离区死亡会产生大量事件。 OOM 会在同一时刻终止所有被分配到该隔离区的请求——判断用户影响前,先按时间戳聚合原始 OOM 事件。
- 消息前缀会拆分分组。 带有前导时间戳的日志(better-auth 的格式)会把一个错误拆成 N 个计数为 1 的分组;按消息分组会严重低估数量。采样事件并在客户端合并。
- 生产部署需要手动进行——修复主应用不代表生产环境已运行修复后的版本。得出修复无效的结论前,请检查
$workers.scriptVersion和各脚本的modified_on。
工作流故障
- 将 Error 作为参数记录会丢失其消息。
console.error("x failed:", error)在 Workers Logs 中显示为x failed: at async …(只有堆栈帧,没有Error: message行),而记录对象中的 Error 会序列化为{remote: true}或{}。在堆栈中查找调用位置,然后从较新的日志行中的describeError(error)(src/server/lib/errors.ts)或下面的 Workflows 实例中读取实际消息。 - 引擎会记录步骤尝试失败的原因。
GET /accounts/{account_id}/workflows/{workflow_name}/instances?status=errored&date_start=<iso>会列出出错的实例(工作流名称位于wrangler.jsonc中:审计 Worker 上的ai-visibility-workflow、rank-check-workflow、site-audit-workflow,以及local-rank-workflow);GET …/instances/{id}会返回error,以及每个步骤及其尝试记录和每次尝试的error.name/error.message。只有这里会保留步骤失败的消息。 - 尝试记录中的
WorkflowInternalError: Attempt failed due to internal workflows error是引擎自身的故障,不是应用代码导致的:运行该步骤的调用约 30 s 后会被报告为canceled。这种情况会成批出现,与 Cloudflare 状态事件(Workflows、Durable Objects 或托管生产环境的 Ashburn 区域发生网络事件)时间吻合。同一批故障也会导致绑定的create()调用被拒绝(at async WorkflowImpl.create (cloudflare-internal:workflows-api…))。阅读代码前,请先查看该时间段的cloudflarestatus.com/history。 canceled对工作流调用来说是正常状态。 引擎会在步骤完成后或调用休眠时取消该调用(每个工作流每天会有数百次,且没有对应的出错实例),因此应统计出错的实例,而不是被取消的调用。
性能分析(CPU 和堆火焰图)
Cloudflare 可按需从正在运行的生产环境 isolate 中采集 CPU 或分配性能分析数据(于 2026-10-09 发布)。只有在要回答“CPU 或内存消耗在哪里”时才使用它;日志和调用指标无法回答这个问题。日常错误排查不需要它。一次采集只会对一个已运行的 isolate 在指定时间窗口内进行采样;窗口保持在 10 到 15 s。
-
使用
cfCLI 采集(执行一次npm i -g cf、cf login)。它会将 pprof protobuf 写入标准输出(原始 API 响应经过 gzip 压缩;下方的读取工具两种格式都支持):cf workers versions profile latest --worker-id open-seo --duration-ms 10000 --profile-type cpu > open-seo-cpu.pprof cf workers versions profile latest --worker-id open-seo --duration-ms 15000 --profile-type heap > open-seo-heap.pprof对应的 API:使用
{ "duration_ms": 10000, "profile_type": "cpu" }调用POST /accounts/{account_id}/workers/workers/{worker_id}/versions/latest/profile(通过GET /accounts/{account_id}/workers/workers获取 Worker ID)。仪表板会以火焰图形式显示相同的采集结果:Worker → Observability → Flamegraph view。 -
无需 Go 即可读取:执行
node .agents/skills/observability-triage/scripts/pprof-top.mjs open-seo-cpu.pprof,即可打印自耗时和包含子调用的最热堆栈帧。Google 的pprof也可以打开该文件。 -
它只会对已经在处理流量的 isolate 进行采样;不会替你启动任何服务。app worker 始终符合条件。只有在审计正在运行时,audit worker 才会产生样本,否则样本数为 0,因此请先启动审计。闲置的预览和自托管 worker 会报错:“The Worker has no loaded isolate in its recent execution locations” 或 “No recent executions were found”。
-
一次采集只针对一个 colo 中的一个 isolate,该 isolate 从近期运行过的实例中选出,因此在相信占比很小的堆栈帧之前,请重复采集。在正常负载下,对一个 isolate 进行 10 s 的 CPU 采集,约有 2,000 个样本,采样间隔为 1 ms。
-
无需 source map 即可读出函数名(Vite 构建会保留函数名);文件名则是类似
index-OuwxKDQa.js的 chunk 哈希。生产部署会将 source map 上传到 PostHog 错误跟踪,并在部署前删除,因此 Cloudflare 不会获得这些 source map,这里的 chunk 名称会一直是哈希。 -
堆性能分析展示的是在时间窗口内进行的分配(每分配约 512 kB 记录一个堆栈),而非保留的内存:它可以找出内存 churn,而非内存泄漏。
-
将它与调用数据结合,查看哪些路由消耗了这些堆栈帧:按
$metadata.trigger对cf-worker-event分组,并计算$workers.cpuTimeMs的sum、median和p90。对一个 isolate 进行 10 s 采集时,各堆栈帧的权重取决于该 isolate 恰好处理的请求,因此在采取行动之前,应根据路由的平均值,将热点堆栈帧占比换算为毫秒数:如果一个占三分之一的堆栈帧来自以 2 ms 认证失败请求为主的性能分析数据,那么它在每个请求上消耗的时间远低于 1 ms。
已知噪声 — 过滤掉这些条目,不要重新调查
只有在调查证实没有可修复或降级的第一方发出位置后,条目才会列在这里。每个条目都会保留一项条件,说明何时应重新将其视为有效信号。
1. SAM chat Durable Object 生命周期(关闭代码 1006)
- 消息(同一现象,按三种方式统计):
Connection closed: this Durable Object instance is no longer active. Reconnect or retry the request.(休眠/驱逐)、Durable Object reset because its code was updated.(部署),以及配对的调用摘要,其中$metadata.error为close。 - 识别标记:
eventType: "hibernatableWebSocket"、入口点SamChatAgent/OnboardingChatAgent、webSocketType: "close", code: 1006, wasClean: false、outcome: "exception"、个位数的wallTimeMs、cpuTimeMs: 0、没有堆栈。指纹:3aa4cac26653d09a0a41100a33d413ae(异常)、0ae15457af49b4d9a117eeecf66b040a(摘要)。 - 无法修复的原因:DO 会在 JS 下被销毁——workerd 交付
webSocketClose时,其 IoContext 已中止,因此第一次await永远不会结束。partyserver已对整个关闭路径执行 try/catch;重写onClose什么也捕获不到。 - 不会造成影响:转录内容按消息保存在 DO SQLite 中,PartySocket 无条件重连,并且在已中止的轮次中不会触发信用额度计量(
onChatResponse)。 - 真正的信号:持续上升且不与部署相关(这意味着休眠以外的对话中途驱逐)。
2. Network connection lost.
- 识别标记:指纹
be89d4ff7a64cb4d4dceae0f51cfe708,或完全一致的消息。运行时生成的结构:source.level不存在、source.exception存在、$metadata.origin: "fetch"——与每条应用日志正好相反(存在source.level,没有exception)。 - 无法修复的原因:这是 workerd 自身记录的客户端在流传输中途断开连接;异常不会进入应用代码——同一
requestId的关联请求事件,其outcome: "ok"。不存在发出该日志的位置;wrangler.jsonc可观测性配置没有逐条消息过滤器。不要在流处理程序周围添加 try/catch(徒劳的形式主义)。 - 真正的信号:如果此组中
/mcp的占比上升,应将其视为工具调用延迟问题的表现(客户端超时并取消),并转交给 /mcp 性能跟踪,而不是归入此日志组。
运行时与应用的试金石
调查任何不熟悉的错误事件前:source.level 不存在 + source.exception 存在 ⇒ 该日志由 Workers 运行时写入,而非应用。没有可供 grep 搜索的调用位置;应根据关联请求的 outcome 判断。
添加条目
只有在确认不存在一方发出的日志位置(搜索消息;检查上述运行时与应用的试金石)且没有遗留错误状态后,才将条目添加到此列表。每个条目都必须包含识别标记(如果指纹稳定,则包括指纹)以及其真正的信号条件。