Sentry 电话告警:只为压垮生产的错误响铃

Sentry 没有打电话这个动作。用 Webhook 把 issue 告警变成真正响铃的来电:集成配置、真实载荷结构、以及该怎么过滤。

更新于

目录

Sentry 不能给你打电话。它能发邮件、发 Slack、把告警交给 PagerDuty——但没有内置的语音动作。想在不买一整套事件管理平台的前提下拿到这个能力,办法是把 Sentry 的 issue 告警发给一个会响铃的 Webhook:建一个 internal integration,指向 Echobell 的来电频道,再在前面加一层过滤,只让真正压垮生产的错误通过。

这篇文章讲完整链路:集成、告警规则、Sentry 真正发出的载荷、读取它的模板,以及让大多数人半途放弃的两个坑。

为什么 Sentry 自己打不了电话

Sentry 的 issue 告警动作分三类:通知(邮件、Slack、Discord、Microsoft Teams)、建工单(Jira、GitHub、Azure DevOps)、交给寻呼产品(PagerDuty、Opsgenie)。每一类的终点要么是一块你得正好在看的屏幕,要么是另一个平台上按人头收费的席位。

下午两点这没问题。凌晨三点,一条 Slack 消息和静音没有区别,推送通知也打不过勿扰模式。对于那一小撮「晚两小时就要真金白银亏钱」的错误——结账接口全返 500、登录全被拒、后台任务在悄悄丢单——你需要的是一台会响的设备。

Webhook 就是这个接口。Sentry 可以把任意 HTTPS 端点作为告警规则的动作;Echobell 则把这个 HTTP 请求变成能穿透 iOS 专注模式的来电式提醒。

你需要什么

  • 一个你能进到 Settings → Developer Settings 的 Sentry 组织(owner 或 manager 权限)
  • 装好 Echobell(App Store / Google Play)
  • 五分钟

你这边不需要任何公网可达的东西。请求是 Sentry 主动发出的,你只负责接收。

第 1 步 —— 建一个会响铃的频道

在 Echobell 里新建频道,通知类型选 来电(Calling)。这正是整件事的意义所在:来电频道的行为像一通打进来的电话而不是推送,所以它能穿过专注模式和勿扰模式。

模板要写成凌晨三点也能看懂的样子。Sentry 的载荷嵌套很深,所以变量路径比平时长:

标题:{{data.event.level}}: {{data.event.metadata.type}}
正文:{{data.event.title}} — {{data.event.culprit}}

在高级设置里配好链接模板,这样点通知就能直接打开这个 issue:

{{data.event.web_url}}

从频道详情页复制 Webhook URL,形如:

https://hook.echobell.one/t/<channel-token>

第 2 步 —— 创建 Sentry internal integration

Sentry 只有通过 integration 才把 Webhook 暴露成告警规则动作,所以你必须建一个。它只是一个表单,不是一个服务——你不用写任何代码。

  1. 进入 Settings → Developer Settings → Custom Integrations
  2. Create New Integration → Internal Integration
  3. Name:Echobell(这就是你稍后在告警规则里选的名字)
  4. Webhook URL:第 1 步拿到的频道 URL
  5. 打开 Alert Rule Action 开关
  6. Permissions:Issue & Event → Read 就够了
  7. Webhooks 下面的复选框全部不要勾——原因见下面的坑
  8. 保存

internal integration 只在你自己的组织内可用,并且会自动安装。这套配置用不到它生成的 token。

第 3 步 —— 把集成挂到告警规则上

进入 Alerts → Create Alert → Issue Alert,或者编辑已有规则。

在 Then perform these actions 里添加 Send a notification via an integration,选 Echobell。

把 Action interval(「同一条告警重复触发时」的节流)至少设成 30 minutes。默认是每次触发都发,一个每分钟报 400 次的错误会一直打你电话,直到你把它关掉为止。

保存,然后用规则的测试/预览功能跑一次,先看到真实载荷到达,再去信任它。

第 4 步 —— 搞清楚到底收到了什么

大多数配置死在这一步,因为载荷的形状和你猜的不一样。Sentry 把所有东西都包了一层:

{
  "action": "triggered",
  "actor": { "id": "sentry", "name": "Sentry", "type": "application" },
  "data": {
    "event": {
      "event_id": "e4874d664c3540c1a32eab185f12c5ab",
      "level": "error",
      "title": "ReferenceError: heck is not defined",
      "culprit": "?(<anonymous>)",
      "platform": "javascript",
      "project": 1,
      "release": null,
      "metadata": { "type": "ReferenceError", "value": "heck is not defined" },
      "tags": [["level", "error"], ["browser", "Chrome 75.0.3770"]],
      "issue_id": "1117540176",
      "issue_url": "https://sentry.io/api/0/issues/1117540176/",
      "web_url": "https://sentry.io/organizations/test-org/issues/1117540176/events/e4874.../"
    },
    "triggered_rule": "Very Important Alert!"
  },
  "installation": { "uuid": "a8e5d2..." }
}

动手写模板之前,有四件事值得先知道:

  • 有用的东西全在 data.event 底下。 {{title}} 什么都渲染不出来;{{data.event.title}} 才是那条错误。
  • data.event.project 是数字 ID,不是 slug。 想让通知里出现可读的项目名,就把它当字面量写进频道标题模板,并且一个项目配一个频道。
  • 没有 environment 字段。 环境是以 ["environment", "production"] 这样的键值对出现在 data.event.tags 里的,而且它在数组里的位置不固定——所以别按下标取。环境过滤放到 Sentry 规则里做(第 5 步)。
  • data.triggered_rule 是规则名。当一个频道服务多条规则时,把它放进正文里很有用。

issue 告警的 Sentry-Hook-Resource 请求头是 event_alert。你可以在 Echobell 频道条件里要求它,这样别的东西就没法触发这个频道:

header["sentry-hook-resource"] == "event_alert"

第 5 步 —— 过滤到「值得一通电话」的程度

一个每来新 issue 就响铃的来电频道,比没有这个频道还糟:一周之内你就会把它静音,然后真正要紧的那次它也不会响。过滤要放在两个地方。

在 Sentry 里,用规则自带的 conditions 和 filters:

目标规则配置
只要生产环境把规则的 Environment 设为 production
只要真的坏了Filter:The event's level equals fatal(或 error)
别为偶发抖动响Condition:The issue is seen more than 25 times in 1 hour
只盯关键路径Filter:The event's tags match transaction contains /checkout
只要回归Condition:A resolved issue changes state from resolved to unresolved

在 Echobell 里,把频道条件当兜底,用来表达 Sentry 表达不了的东西,或者用来应付今天改不动的规则:

data.event.level == "fatal" || data.event.level == "error"

严重级别一般还是在 Sentry 规则里筛更好,因为节流也在那儿。放在 Echobell 里更好的场景是:你想用一条 Sentry 规则喂出两档紧急程度。

第 6 步 —— 给警告级别留一扇安静的门

分级的意义在于让「电话」保住它的分量。再建一个 时效性(Time Sensitive) 类型的 Echobell 频道,再加一条门槛更低的 Sentry 规则,指向第二个 internal integration(一个集成只存一个 Webhook URL,所以第二个频道需要第二个集成)。

一套能扛过真实一周的配置大致长这样:

Sentry 规则级别 / 阈值Echobell 频道行为
prod-fatalfatal,生产来电穿透专注模式响铃
prod-error-spikeerror,1 小时超 100 次时效性直接上锁屏,不响铃
new-issue-digest任意新 issue普通普通推送,有空再看

只在下班时间响

白天你本来就盯着 Sentry。Echobell 给条件提供了 UTC 系统时间变量,所以一个频道不用再加一条 Sentry 规则就能按小时区别对待:

data.event.level == "fatal" && (hour >= 17 || hour < 9)

如果你的周末是真的休息,再加一个星期判断:

data.event.level == "fatal" && (hour >= 17 || hour < 9 || dayOfWeek == 0 || dayOfWeek == 6)

这些全是 UTC,定死数字之前先从你自己的时区换算一遍。完整变量表见条件参考。

两个坑

坑 1:勾了 Webhooks 那几个框。 internal integration 有两条互相独立的 Webhook 路径。Alert Rule Action 开关让这个集成能在告警规则里被选中——这是你要的那条。而 Webhooks 复选框(issue、error、comment)订阅的是该资源的每一个事件:整个组织范围内每次 issue 被创建、解决、指派、归档、忽略。勾上 issue,同事解决一个问题你的电话就会响。全都不勾,就只有你的告警规则会触发 Webhook。

坑 2:用了旧版 Webhooks 插件。 老的按项目配置的 Legacy Integrations → WebHooks 插件还在,也还能用,而且看起来像条捷径——不用建集成就能粘个 URL。但它的载荷结构不一样(更扁平),请求没有签名,Sentry 自己也在把新用户往别处引。如果你用它,上面那些变量路径全都得换。请用 internal integration。

载荷大小、错误风暴与截断

规模上来之前,有三个限制值得先知道:

  • 1 MiB 请求体。 超过 1 MiB 的触发请求会被 Echobell 以 HTTP 413 拒绝。Sentry 的载荷带着完整堆栈和请求上下文,通常落在几十 KB,但一个带巨大请求体的事件可能逼近上限。Sentry 那边没有 max_alerts 这种旋钮,所以缓解办法是在 SDK 的 beforeSend 里清洗大的请求体——这件事你出于隐私考虑本来也该做。
  • 每个 token 每分钟 120 次请求。 超过之后触发接口会返回 429、RATE_LIMIT_EXCEEDED 和一个 Retry-After。让你待在线内的正是 Sentry 的 action interval,30 minutes 绰绰有余。
  • 通知正文 1500 字节。 更长的渲染结果在到达设备前会被截断。data.event.title 加 culprit 很宽裕;把 data.event.exception 整个倒进去则不行,而且在锁屏上根本读不了。细节留给链接模板。

和团队共享

一个 Echobell 频道可以被多人订阅,每个订阅者各自选自己的通知类型。所以同一条 Sentry 规则可以让值班工程师的手机响铃,同时对其他人只是一条普通推送——没有按人头计费,也没有排班配置。

它不是升级策略。这里没有「五分钟没人确认就打给下一个人」。真需要那个,你需要一个真正的 on-call 平台;Echobell 覆盖的是它下面那层投递。

这套方案给不了你什么

不如把话说明白:

  • 没有确认(ack)。 接起电话不会告诉 Sentry 任何事,也不会让其他订阅者的手机停下来。
  • 没有排班和升级。 要么订阅的人都收到,要么都收不到。
  • 除 Sentry 之外没有额外去重。 分组和节流都发生在 Sentry 规则里,Echobell 只投递到达的东西。
  • 没有双向同步。 在 Sentry 里解决问题不会清掉你手机上的任何东西。

如果这些是硬伤,那这不是对的工具。如果你真正需要的是「结账坏了就把我叫醒」,它大概是拿到这个结果最便宜的可靠方式。

排障

规则触发了但什么都没到。 检查集成里的 Alert Rule Action 是不是开着。如果是关的,这个集成根本不会出现在规则的动作列表里——而在你打开它之前保存过的规则,会保留一个失效的动作。

收到通知但内容是空的。 你的模板在读顶层键。Sentry 把所有东西都嵌在 data.event 下面。

响了些你没预料到的东西。 先查集成上的 Webhooks 复选框(坑 1),再看规则的 environment 是不是留在了 "All Environments"。

同一个错误反复响。 调高 Sentry 规则的 action interval。Echobell 的重试是另一回事——应用设置里的 重试失败来电 是在你漏接时重拨,不是一回事。

连测试都收不到。 先用 curl 触发一次频道,排除 Echobell 这一侧:

curl -X POST https://hook.echobell.one/t/<channel-token> \
  -H 'Content-Type: application/json' \
  -d '{"data":{"event":{"level":"fatal","title":"Test error","culprit":"manual test","metadata":{"type":"TestError"}}}}'

如果这个响了而 Sentry 不响,问题在集成,不在频道。

常见问题

Sentry 能原生打电话吗?

不能。Sentry 的 issue 告警动作只有通知、建工单,以及和寻呼类产品的集成。语音电话需要外部服务——要么是 PagerDuty 这类寻呼平台,要么是一个会响铃的 Webhook 接收端,比如 Echobell。

Sentry 告警能穿透勿扰模式吗?

只有当它以来电式提醒到达时才能。来电类型的 Echobell 频道行为像一通打进来的电话,iOS 的专注模式和勿扰模式会放行。任何应用的普通推送都不行。

用 Webhook 需要付费的 Sentry 套餐吗?

internal integration 和告警规则动作在 Sentry 的 Developer 套餐及以上都可用。Webhook 本身不额外收费。

为什么我的 Sentry 模板变量是空的?

几乎总是因为路径太短。告警载荷把事件嵌在 data.event 下,所以要写 {{data.event.title}} 而不是 {{title}}。触发一次频道,在应用里查看记录下来的请求体,就能看到你实际收到的结构。

怎么只对某一个环境告警?

设置 Sentry 告警规则上的 Environment 字段。别试图从 data.event.tags 里读——那是一个 [键, 值] 对数组,顺序没有保证。

同一个错误能同时打给两个人吗?

可以。共享频道,让每个人用自己想要的通知类型订阅。因为没有确认机制,所以选了「来电」的人都会被打电话。

过滤应该放在 Sentry 还是 Echobell?

能放 Sentry 就放 Sentry——节流和环境范围也都在规则里。放 Echobell 更合适的情况是:你想从一条规则里分出两档紧急程度、你需要一个按时段的窗口、或者你今天改不动那条规则。

小结

整套东西就四件:一个来电频道、一个打开了 Alert Rule Action 且 Webhooks 复选框全不勾的 internal integration、一条窄到配得上一通电话的告警规则,以及会读 data.event 的模板。这一页上剩下的所有内容,都是为了让它一个月之后还足够窄,让那声铃响仍然有意义。

在 iPhone 上下载 Echobell 或在 Google Play 获取,然后先把上面那条 curl 发一次,再把任何真正重要的事情托付给这条链路。

相关内容