mcpField
O
observability-triage

observability-triage

github.com/every-app云与 DevOps25.5k195 次安装2 天前更新
代码仓库

关于 observability-triage

用于在 Cloudflare Workers Observability 中诊断生产错误的技能,面向负责排查 Workers 的开发者。它涵盖准确统计调用、调查工作流失败、性能分析及过滤已确认的噪声。

主要功能

  1. 01查询 Workers Observability
  2. 02准确统计调用次数
  3. 03检查工作流实例失败
  4. 04采集 CPU 和堆分析
  5. 05过滤已知运行时噪声

适用场景

  1. 01排查生产错误激增
  2. 02追踪失败的工作流步骤
  3. 03比较不同部署的错误
  4. 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-api MCP 服务会在 mcp__cloudflare-api__execute 中预绑定 accountId,而 npx wrangler whoami 会打印该值。要排查的 Worker 是本仓库部署的 Worker(参见 deploy/alchemy/alchemy.run.ts):主应用 Worker 以及辅助 Worker(审计引擎、落地页、自托管)。
  • 通过 cloudflare-api MCP 服务(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。

  • 使用 cf CLI 采集(执行一次 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 判断。

添加条目

只有在确认不存在一方发出的日志位置(搜索消息;检查上述运行时与应用的试金石)且没有遗留错误状态后,才将条目添加到此列表。每个条目都必须包含识别标记(如果指纹稳定,则包括指纹)以及其真正的信号条件。

评论

支持 Markdown