☰
Kener Incident Updates 深度解析:事件更新的状态机、时间线、编辑删除与订阅通知机制
2026/10/14 17:09:46 网站建设 项目流程
  • 可观测性
  • 后端
  • 前端
  • 运维

【免费下载链接】kener

Stunning status pages, batteries included!

项目地址:https://gitcode.com/gh_mirrors/ke/kener
点击查看免费下载

Incident Updates(事件更新,在 Kener 中亦称 comments)是 Kener 状态页系统中向用户传达事件进展、并驱动事件状态流转的核心机制。本文基于 Kener v4 文档与仓库源码,完整讲解更新的组成要素(消息、状态、时间戳)、创建/编辑/删除的完整操作流程、ACTIVE/INACTIVE 可见性控制、公开页时间线渲染方式以及订阅通知链路,并给出可落地的更新频率与文案最佳实践。读完本文,你将掌握如何用更新驱动一次事件从 INVESTIGATING 到 RESOLVED 的完整生命周期,并能结合源码理解状态与end_date_time的联动逻辑。

什么是 Incident Updates

Incident Update 是一条带时间戳的消息,它同时承担多种职责:

  • 描述当前状态或已采取的行动;
  • 改变事件的当前状态(state);
  • 出现在公开状态页上;
  • 形成完整的事件审计轨迹(audit trail);
  • 与用户透明沟通进展。

在数据库层面,更新以 "comment"(评论)的形式存储在incident_comments表中(因此文档与源码中经常混用 "update" 与 "comment" 两个词),但在状态页上被渲染为一条按时间倒序排列的时间线。

从源码结构看,Kener 将"事件更新"设计为事件生命周期中的唯一状态变更入口:incidentController.ts中的AddIncidentComment是事件公开沟通的唯一咽喉点,注释明确说明"订阅者通知完全由事件评论驱动"——无论是来自告警、仪表盘还是 API 创建的事件,只有发布评论才会触发订阅者邮件,告警队列本身不会重复推送事件通知(见 incidentController.ts)。

Update 的组成要素

每条更新由三个必填要素构成:

Message(消息,必填)

用户实际看到的更新正文。支持完整的 Markdown 格式化,包括:

  • 加粗与斜体
  • 标题(Headers)
  • 列表(有序与无序)
  • 链接
  • 代码块
  • 表格

示例:

We've identified the root cause as a database connection pool exhaustion. **Actions Taken:** - Increased connection pool size from 100 to 500 - Restarted application servers - Enabled connection pool monitoring **Next Steps:** - Monitor error rates for 15 minutes - Verify connection pool health

值得注意:Kener 在前端渲染时并非直接输出 Markdown,而是先经mdToHTML转换为 HTML,再通过SveltePurify组件安全地注入页面,从而兼顾富文本效果与 XSS 防护(见 IncidentItem.svelte)。

State(状态,必填)

该更新发布后事件所处的状态。这是最关键的要素,因为:

  • 事件的当前状态 = 最近一条更新的状态;
  • 在更新中修改状态,事件状态随之改变;
  • 状态的推进驱动着事件的整个生命周期。

可用状态定义于 global-constants.ts 的IncidentState联合类型:

状态含义
INVESTIGATING初步调查中
IDENTIFIED已定位根因
MONITORING已应用修复,观察稳定性
RESOLVED问题完全解决

重要联动:当把状态设置为RESOLVED时,Kener 会自动将事件的end_date_time设置为该更新的时间戳。源码中AddIncidentComment的逻辑如下(见 incidentController.ts):

if (state === GC.RESOLVED) { incidentUpdate.end_date_time = commented_at; } else { if (incidentExists.state === GC.RESOLVED) { await db.setIncidentEndTimeToNull(incident_id); } }

即:状态变为 RESOLVED 时写入结束时间;反过来,如果事件当前已是 RESOLVED 而新更新将状态改回非 RESOLVED,则清空end_date_time(事件重新开放)。这一逻辑同样存在于编辑更新的UpdateCommentByID(见 incidentController.ts)。

Timestamp(时间戳,必填)

该更新发生的时间。

  • 默认值:当前日期与时间;
  • 可修改:可用于回溯补记(backdating)、让时间线对齐真实事件、事后补录更新;
  • 展示:按你的本地时区显示,但底层以 UTC 存储。

数据库层面对应commented_at整数列(UNIX 时间戳)。值得一提的是,v4 API 在写入时间戳时会通过GetMinuteStartTimestampUTC将其规范化到分钟起点(见 comments/+server.ts/api/v4/incidents/[incident_id]/comments/+server.ts#L79-L95)),未传时间戳时则取当前时刻的分钟起点。

数据表结构

incident_comments表由初始迁移创建(见 migrations/20250111153517_init.ts):

列类型说明
idincrements, 主键更新 ID
commenttext, 非空更新正文(Markdown)
incident_idinteger, 非空所属事件
commented_atinteger, 非空更新时间戳(UTC)
created_at/updated_attimestamp记录创建与最后修改时间
statusstring, 默认ACTIVE可见性状态
statestring, 默认INVESTIGATING更新时的状态

对应的 TypeScript 类型IncidentCommentRecord定义在 src/lib/server/types/db.ts。

创建更新

创建事件时附带 Initial Update

新建事件时,可选择性提供初始更新(Initial Update):

  1. 填写事件详情;
  2. 在 "Initial Update" 字段中添加文本(支持 Markdown);
  3. 该更新自动以状态INVESTIGATING创建;
  4. 时间戳与事件开始时间一致。

源码中的CreateNewIncidentWithCommentAndMonitor正是如此实现:创建事件后立即以GC.INVESTIGATING状态、data.start_date_time为时间戳写入初始评论(见 incidentController.ts)。

为已有事件添加更新

  1. 进入事件详情页;
  2. 点击Add Update按钮;
  3. 输入消息(支持 Markdown);
  4. 选择新的状态;
  5. 可选地调整时间戳;
  6. 点击Add Update提交。

提交后发生的事:

  • 更新被加入事件时间线;
  • 事件状态变为该更新的状态;
  • 若状态为 RESOLVED,则end_date_time被设置为更新时间戳;
  • 更新立即出现在状态页上;
  • 若配置了订阅系统,订阅者收到通知。

通过 v4 API 创建

Kener 的 v4 API 提供了等价能力。向POST /api/v4/incidents/{incident_id}/comments发送 JSON:

{ "comment": "We've identified the root cause as a database connection pool exhaustion.", "state": "IDENTIFIED", "timestamp": 1700000000 }

服务端校验规则(见 comments/+server.ts/api/v4/incidents/[incident_id]/comments/+server.ts#L50-L139)):

  • comment必填,且必须是非空字符串;
  • timestamp可选,缺省取当前时间;必须是合法的 UTC 秒级时间戳,并会被规范化到分钟起点;
  • state必填,且必须是INVESTIGATING | IDENTIFIED | MONITORING | RESOLVED之一,否则返回 400;
  • 创建成功后返回 201,同时以该评论的状态更新事件的state与(必要时)end_date_time。

编辑更新

你可以编辑任意已有更新:

  1. 在事件时间线中找到目标更新;
  2. 点击Edit(铅笔图标)按钮;
  3. 修改消息;
  4. 如需要,修改状态;
  5. 如需要,调整时间戳;
  6. 点击Save保存。

关键的状态变化:

变为 RESOLVED:

  • 将事件的end_date_time设为该更新时间戳;
  • 在状态页上关闭事件;
  • 触发解决类通知。

从 RESOLVED 改回其他状态:

  • 清空事件的end_date_time;
  • 重新打开事件;
  • 状态页上重新显示为进行中。

对应的服务端实现UpdateCommentByID会先校验事件与评论均存在,然后执行数据库更新,并根据新状态同步事件的state/end_date_time(见 incidentController.ts)。

注意两条编辑路径的差异:仪表盘 UI 的编辑会同步事件状态;而 v4 API 的PATCH /api/v4/incidents/{incident_id}/comments/{comment_id}在更新评论本身时不会改变事件的状态字段,仅当需要时在评论内调整其state(见 comments/[comment_id]/+server.ts)。在设计自动化流程时需留意这一区别。

删除更新

删除更新同样在时间线中操作:

  1. 在时间线中找到目标更新;
  2. 点击Delete(垃圾桶图标)按钮;
  3. 确认删除。

警告:此操作不可撤销(前端 UI 路径为永久移除)。

删除后的影响:

  • 更新被永久移除;
  • 事件状态回退到前一条更新的状态;
  • 时间线自动调整;
  • 如果删除的是最近一条更新,事件将采用倒数第二条更新的状态;
  • 特殊情况:如果删除了全部更新,事件保留创建时的原始状态。

需要区分的是:仪表盘 UI 的删除是硬删除,而 v4 API 的DELETE是软删除——将评论status置为DELETED,数据库记录仍然保留,且 GET 查询时 DELETED 评论按 404 处理(见 comments/[comment_id]/+server.ts)。在 v4 API 删除事件时,其下所有评论同样会被标记为DELETED(见 incidents/[incident_id]/+server.ts)。

更新的可见性状态(Update Status)

每条更新带有一个status字段用于控制可见性:

ACTIVE(默认)

更新在公开状态页与事件时间线上可见。这是默认状态,数据库列默认值为ACTIVE。

INACTIVE

更新对公众隐藏,但仍保留在数据库中。

典型使用场景:

  • 面向内部、不打算给用户看的备注;
  • 可能包含敏感信息的更新;
  • 草稿性质的更新;
  • 历史归档记录。

如何设置:目前只能通过 API 调用管理(UpdateCommentStatusByID,见 incidentController.ts),仪表盘 UI 支持未来可能补充。

除文档提到的 ACTIVE / INACTIVE 外,源码中还存在第三种状态DELETED(软删除标记),前述 v4 API 的删除即写入该值。公开页面与公共 API 在拉取评论时只返回status = "ACTIVE"的记录——数据仓库层getActiveIncidentComments通过andWhere("status", "ACTIVE")过滤(见 repositories/incidents.ts),控制器层GetIncidentActiveComments亦只返回活动评论(见 incidentController.ts)。

时间线展示

在公开状态页上,更新按以下方式展示:

  • 排序:最新在前(时间倒序)。底层查询以commented_at降序排列,getActiveIncidentComments还会以id降序作为次级排序(见 repositories/incidents.ts)。
  • 展示信息:
    • 状态徽章(按状态着色);
    • 时间戳;
    • 消息内容(渲染后的 Markdown)。
  • 样式:
    • 每条更新独立成卡片;
    • 使用与状态匹配的颜色;
    • 清晰的视觉分隔。

前端实现位于事件详情页 incidents/[incident_id]/+page.svelte 与首页组件 IncidentItem.svelte,状态徽章通过text-{comment.state.toLowerCase()}绑定状态色。

更新通知(订阅系统)

当配置了订阅系统(详见 订阅文档/docs/content/v4/subscriptions.md))后:

订阅者会收到:

  • 新更新的邮件通知;
  • 邮件主题包含事件 ID 与状态,格式为[#${incident.id}:${comment.state}] ${incident.title};
  • 更新消息的 HTML 渲染正文;
  • 指向完整事件页面的链接(cta_url指向/incidents/{incident.id},按钮文案 "View Incident")。

通知触发的时机:

  • 新增更新;
  • 状态变化;
  • 事件创建(伴随初始更新);
  • 事件解决。

底层实现:AddIncidentComment在写入评论并同步事件状态后调用notifySubscribersOfComment,将title、cta_url、cta_text、update_text(mdToHTML渲染结果)、update_subject、update_id、event_type: "incidents"等变量推入subscriberQueue;每个评论使用稳定的去重 IDsubscriber-incidents-${comment.id},确保队列重试或重复推送时订阅者只会收到一次通知(见 incidentController.ts)。

更新最佳实践

更新频率

关键事件(Critical):

  • 每 15–30 分钟更新一次;
  • 即使没有进展也要更新(例如 "Still investigating...");
  • 保持用户知情。

重大事件(Major):

  • 每 30–60 分钟更新一次;
  • 在有显著进展时更新;
  • 在状态转换时更新。

轻微事件(Minor):

  • 状态变化时更新;
  • 应用修复时更新;
  • 解决时更新。

内容指南

要清晰(Be Clear):使用简单的语言;避免行话和缩写;必要时解释技术术语。

要具体(Be Specific):说明什么受影响,而不只是你在做什么;说明用户会看到什么;给出可能的预估时间。

要诚实(Be Honest):存在不确定性时如实承认;不要承诺做不到的事;预估变化时及时更新。

好的示例:

## Investigating (Good) We're seeing 20% of API requests failing with 503 errors. Our team is investigating the cause. Retries should succeed, but you may experience delays.
## Identified (Good) We've identified a database replication lag as the cause. The secondary database is catching up. ETA for full resolution: 15 minutes.
## Monitoring (Good) Database replication is back in sync. Error rates have dropped to normal levels. We're monitoring for the next 20 minutes to ensure stability before resolving.
## Resolved (Good) All systems are operating normally. Total incident duration: 47 minutes. **Root Cause:** Database replication lag due to a large batch import. **Prevention:** We've implemented rate limiting on batch imports and improved monitoring.

反面示例:

## Bad - Too Vague We're working on it.
## Bad - Too Technical Increased innodb_buffer_pool_size and optimized query Q47392.
## Bad - Unprofessional Really sorry about this mess! Not sure what happened.

状态推进

如无必要,不要跳级:

  • 帮助用户理解进展;
  • 提供更细致的时间线;
  • 透明度更高。

允许跳级的情形:

  • INVESTIGATING → RESOLVED(快速修复);
  • IDENTIFIED → RESOLVED(无需观察期)。

状态可以回退:

  • MONITORING → INVESTIGATING(问题复现);
  • RESOLVED → INVESTIGATING(事件重新打开);
  • 复杂事件的正常情况。

从源码看,状态回退是显式支持的:事件已处于 RESOLVED 时,任何非 RESOLVED 状态的新更新都会调用db.setIncidentEndTimeToNull清空结束时间(见 incidentController.ts),事件在状态页重新显示为进行中。

Markdown 使用建议

善用结构:

## Summary Brief overview of current status **Impact:** - What's affected - Who's affected **Next Steps:** - What we're doing - Expected timeline

善用列表:便于扫读;行动项更清晰;可读性更好。

用加粗突出重点:突出重要信息;吸引用户注意力;但不要过度使用。

链接到资源:指向相关监控的状态页;指向临时规避方案的文档;指向支持渠道。

特殊使用场景

回溯补记更新(Backdating)

如果是在事后创建更新:

  1. 添加更新;
  2. 将时间戳调整到实际发生时间;
  3. 保持时间线准确;
  4. 保留历史事实。

示例:问题发生在上午 10:00,但你在下午 2:00 才补录。将各条更新的时间戳分别设为 10:00 AM、10:30 AM 等。

一次补录多条更新

对于进展密集的复杂事件:

  1. 按时间顺序添加更新;
  2. 调整时间戳使其合理分布;
  3. 确保状态推进符合逻辑;
  4. 最近一条更新决定当前状态。

解决类更新(Resolution Updates)

解决事件时,建议包含以下内容:

摘要(Summary):修复了什么;解决验证情况;置信度。

根因(Root Cause):事件由什么引起;为什么发生;技术细节(可选)。

预防(Prevention):防止复发的措施;监控改进;流程变更。

完整示例:

All services are fully operational. The incident has been resolved. **Root Cause:** A misconfigured load balancer was routing traffic to unhealthy backend servers. **Resolution:** - Fixed load balancer health check configuration - Restarted affected backend servers - Verified traffic routing correctly **Prevention:** - Added health check validation to deployment pipeline - Implemented automated health check monitoring - Updated runbooks for faster diagnosis **Total Duration:** 1 hour 15 minutes

值得一提的是,Kener 的自动生成事件(告警驱动)同样以结构化 Markdown 更新为输出:事件创建时IncidentCreateAlertMarkdown会生成包含 Monitor Name、Monitor Tag、Incident Status、Severity、Alert Type、Alert Value、Failure Threshold 等字段的 Markdown 表格;告警解除时ClosureCommentAlertMarkdown会追加Total duration: X minutes与完整告警详情表格(见 incidentController.ts)。这是理解"自动生成的事件如何维护时间线"的最佳源码参考。

下一步

  • 事件对监控状态的影响/docs/content/v4/incidents/impact-on-monitoring.md) —— 事件状态如何影响监控状态展示
  • 创建与管理事件/docs/content/v4/incidents/creating-managing.md) —— 回到事件管理基础
  • 自动生成的事件/docs/content/v4/incidents/auto-generated.md) —— 告警如何自动创建并更新事件
  • 可观测性
  • 后端
  • 前端
  • 运维

【免费下载链接】kener

Stunning status pages, batteries included!

项目地址:https://gitcode.com/gh_mirrors/ke/kener
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询