PostHog 产品告警工程指南:为产品添加告警与扩展共享告警平台
【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog
本篇基于 PostHog 仓库内的工程技能文档 .agents/skills/adding-product-alerting/SKILL.md 及其四份参考文档整理。核心主题是:当某个 PostHog 产品需要接入告警能力时,如何组合已有的生命周期状态机、目的地(destination)、投递、调度、邮件与前端原语完成"接入(Adopt)";当某项能力可跨产品复用时,又该如何按平台契约"扩展(Extend)"共享告警基础设施。读完本文,你将掌握路由决策方法、十条平台不变量、products/alerts/backend/各模块的公开契约,以及端到端验证告警链路的标准做法。
先做路由决策:需求应该走哪条路径
原文档开篇即要求"路由优先"(Route first):任何与告警相关的工程请求,先按下表确定路径,再阅读对应参考文档,避免在错误的层动手。
| 需求 | 路径 | 阅读文档 |
|---|---|---|
| 给一个产品添加告警能力 | Adopt(接入) | adopting-platform-alerting.md |
| 构建/扩展产品告警编辑器、目的地 UI、高级选项、评估历史 | Frontend | frontend-alerting.md |
| 添加生命周期规则、目的地类型、投递行为、调度原语、邮件能力、向导选项或共享评估特性 | Extend(扩展) | extending-platform-alerting.md |
| 只改某一个已有产品的行为 | Adopt first(先接入) | 保持产品自持,除非该行为可复用且有真实的第二个用例 |
| 理解归属或选择正确的层 | Architecture | architecture.md |
| 配置/撰写已有的 logs 或 error tracking 告警 | Out of scope | 使用authoring-log-alerts或authoring-error-tracking-alerts技能 |
| 添加实时应用内通知 | Out of scope | 使用sending-notifications技能 |
两条路径都服务于同一目的:共享告警平台既是"积木"也是"契约"。文档强调:在创建产品本地告警框架之前,必须先从本技能出发。
十条平台不变量(Platform invariants)
无论走哪条路径,以下规则必须保持:
- 评估保持领域专属。产品自己决定数据是否越限;共享生命周期消费的是归一化的
CheckInput。 - 只有一个生命周期状态机。复用 state_machine.py,产品间的真实差异通过
AlertPolicy表达,而不是分叉(fork)代码。 - 只有一个产品 mutator。所有对持久化的
state或consecutive_failures的写入都必须经过产品适配器的apply_outcome。 - 派发与持久化必须一致。对 HogFunction 通知,在内事件生产者确认(acknowledge)事件之前,不得持久化任何依赖通知的状态迁移;若生产失败,恢复 check 前的结果。注意:该确认只证明事件进入了内事件传输层,不证明下游目的地执行。
- 目的地是白名单制。共享支持某个目的地类型,并不会自动让它在每个产品中都可见可选。
- 调度数学是共享的,到期资格判定是产品自持的。固定节奏、日历锚点、时区与调度限制辅助函数来自 scheduling.py;模型专属的到期谓词与持久化留在接入方。
- 共享代码里没有产品分支。生命周期模块保持纯 Python;可复用的 Django 行为放在
products/alerts/backend/的其他位置。 - 前端数据在产品边界归一化。共享编辑器组件渲染归一化后的定义、目的地、高级选项、调度与历史;产品 API 调用、payload 与评估专属字段留在产品适配器。
- 默认值保持向后兼容。新的平台选项必须让既有接入方显式选择后才能改变行为。
- 配置、路由与投递是一个契约。受支持的目的地必须能被持久化、可见、在告警触发时被选中、并到达投递 worker。不能只测其中一步。
架构分层:代码应该放在哪里
architecture.md 给出的分层地图如下,编辑代码前先用它决定归属:
| 层 | 位置 | 职责 |
|---|---|---|
| 纯生命周期决策 | products/alerts/backend/state_machine.py | 状态迁移、策略决策、通知动作 |
| 共享告警基础设施 | products/alerts/backend/ | 调度数学、目的地配置与持久化、内事件投递、邮件传输、insight 告警模型/API 与 insight 评估 |
| 产品适配器 | products/<name>/backend/ | 领域评估、模型快照、单一 mutator、事件 payload、允许的目的地、到期查询、历史与编排 |
| 共享告警创建 UI | frontend/src/lib/components/Alerting/AlertWizard/ | 可复用的 HogFunction 目的地、触发器与配置流程 |
| 共享产品告警 UI | products/alerts/frontend/components/ | 与容器无关的编辑器布局、定义原语、高级选项、目的地编辑器、调度展示与评估图表 |
| 产品 UI | products/<name>/frontend/或frontend/src/scenes/<name>/ | 表单逻辑、API 调用、产品字段、归一化适配器、入口、详情表与向导配置 |
两个参考接入方(reference adopters)是理解整个平台的钥匙:
products/logs:固定节奏调度、HogFunction 目的地、投递回滚、产品自持的 Temporal 编排,以及共享产品告警编辑器组件的参考实现;- Insight 告警:日历锚点、周末跳过与邮件投递的参考实现。两者都使用共享的静默时段(quiet hours)调度限制。每个产品保留自己的模型、到期查询与调度持久化。
evaluation包在 insight 查询种类之间共享,但不是给无关产品用的通用评估器。
生命周期契约:CheckInput 进,AlertCheckOutcome 出
state_machine.py 的模块文档注释明确:它遵循 Prometheus/Alertmanager 的拆分思路——评估留在各领域产品,生命周期决策集中在这一处,契约就是"进一个CheckInput,出一个AlertCheckOutcome"。模块是纯 Python,无任何 Django 或产品模型导入。
核心类型与函数:
AlertState:NOT_FIRING、FIRING、PENDING_RESOLVE(仅输入态)、ERRORED、SNOOZED、BROKEN;CheckInput:归一化一次产品评估,字段为threshold_breached、is_inconclusive、error_message、is_transient_error;AlertSnapshot:机器做决策所需的最小字段集——state、cooldown、last_notified_at、snooze_until、consecutive_failures,以及 N-of-M 滑窗字段(evaluation_periods、datapoints_to_alarm、recent_events_breached,1-of-1 即"任何越限即触发");evaluate_alert_check(...)/evaluate_alert_failure(...):返回AlertCheckOutcome(含new_state、notification、consecutive_failures、update_last_notified_at、error_message、disable),不做任何持久化;- 控制面辅助函数
apply_enable、apply_disable、apply_snooze、apply_threshold_change等返回ControlPlaneOutcome,与AlertCheckOutcome共享new_state + consecutive_failures,从而能统一流经产品本地的apply_outcome; NotificationAction(NONE/FIRE/RESOLVE/ERROR/BROKEN)告诉产品该投递哪种事件。
产品差异通过AlertPolicy(frozen dataclass)表达,源码中的注释非常克制:"每个标志都编码一个真实观测到的产品差异——不要投机地加标志,也不要未经核查所有接入方就改默认值"。仓库中可以看到两套现成策略:
LOGS_ALERT_POLICY = AlertPolicy() # 默认即 logs 行为 BILLING_ALERT_POLICY = AlertPolicy( broken_is_terminal=False, # BROKEN 可被一次成功检查重新评估 transient_errors_count_toward_broken=True, notify_error_on_every_failure=True, cooldown_gates_initial_fire=False, cooldown_gates_resolve=False, renotify_while_firing=True, # 持续越限时每冷却窗重复通知 clear_check_ends_snooze=True, # 清除检查即解除 snooze disable_when_broken=True, # 达到 BROKEN 同时禁用告警 )关键参数还包括max_consecutive_failures(默认MAX_CONSECUTIVE_FAILURES = 5,设为None则失败永不升级为 BROKEN)、errors_set_errored_state(是否把评估失败暴露为可见的 ERRORED 状态)、notify_resolve(是否发送恢复通知)等。
错误行为是"承重"的(error behavior is load-bearing),文档特别列出五条语义:失败的检查不清除已在 firing 的告警;inconclusive 检查保持状态与失败计数不变;瞬态错误默认静默(除非策略显式选择计数);错误通知通常只发生在进入错误态的第一个失败边缘;投递失败不得消耗本应重试的生命周期或失败计数边缘。源码中对应实现很直观:evaluate_alert_failure对瞬态错误直接返回_stay式的原状态结果(注释解释了集群抖动会同时命中整批告警,广播会制造噪音),失败计数用于向 BROKEN 升级,而错误通知基于snapshot.consecutive_failures == 0判断"每条错误链只通知一次"。
产品接入时还需扩展 semgrep 规则.semgrep/rules/security/alert-state-must-go-through-state-machine.yaml,用静态检查强制"state 写入必须走状态机"。相关决策测试见 test_state_machine.py 与 test_insight_alert_state_machine.py。
目的地契约:EventKindSpec 与 DESTINATION_SPECS 注册表
产品面向的目的地设置统一从products.alerts.backend.facade.api导出(见 facade/api.py 的__all__):
validate_destination_databuild_alert_destination_configcreate_alert_destination_hog_functionssoft_delete_alert_destinationssoft_delete_all_alert_destinationssend_alert_email
从 destination_configs.py 的实现可以看到契约的具体形状:
DestinationType枚举定义了共享支持的目的地:slack、discord、webhook、teams(Microsoft Teams);EventKindSpec(frozen dataclass)描述"一种事件"的目的地无关内容:event_id、display_kind、header、details、主操作 URL/标签、webhook body、product_label等;共享 builder 依据DESTINATION_SPECS注册表把它转换成 HogFunction payload。每个目的地类型在该注册表中拥有自己的模板 ID、必填字段、input 构建、回读(read-back)与读取脱敏(redaction)——添加一个目的地类型,就是在那里加一条注册项;- 产品自己拥有事件 ID、事件属性、措辞、动作,以及"允许的目的地列表"。
删除是 fail-closed 的:必须始终用team_id、alert_id和产品允许的事件 ID 三重限定。create_alert_destination_hog_functions会拒绝创建该告警已有的目的地,因此文档要求"锁住告警行再调用"。目的地相关测试分布在 test_destination_configs.py 与 test_destinations.py。
投递契约:把生产、flush、确认当成三个阶段
HogFunction 通知 worker 直接使用products.alerts.backend.destinations,文档给出严格的四步序列:
produce_alert_internal_event(...)返回ProduceResult或None;flush_alert_internal_events(...)刷新共享生产者——批处理 worker 每产生一批只 flush 一次;alert_internal_event_delivered(...)检查 flush 之后生产者是否确认了每条内事件;- 产品只为已确认(acknowledged)的内事件持久化依赖通知的生命周期变更。
语义边界很重要:生产者确认只证明"内事件传输层收到了事件",不证明下游 HogFunction 执行,更不证明最终 Slack/Discord/webhook/Teams 投递成功。helper 负责记录与捕获生产者失败;回滚、重试时机、调度推进与检查历史语义归产品所有。文档以 logs 为参考:内事件未确认时,在该告警下一次节奏上重新评估。邮件侧则要求调用方通过 facade 的send_alert_email(...)发起,并自持收件人、授权、主题、模板、上下文、错误处理,以及一个稳定的campaign_key——它承担必需的邮件重试与去重语义,不能在 helper 内部生成不稳定的 key。
调度契约:分片、网格对齐与日历锚点
scheduling.py 同样是纯 Python,拥有可复用的调度数学:
compute_shard_offset_seconds(alert_id, check_interval_minutes, ...):用alert_id.int % shard_count确定性地把 UUID 键控的告警分配到调度器 tick 上,避免同一时刻全量告警同时触发。源码注释给了具体例子:60 秒调度间隔、5 分钟节奏下,告警分布在[0, 60, 120, 180, 240]秒的 5 个桶上;并说明用alert_id.int而非进程内hash()是因为它跨 Pod 重启稳定,且 UUIDv7 低位是随机段、取模均匀。advance_next_check_at(...):从上一次计划推进,跳过错过的间隔,把时间戳 snap 到"以午夜为锚"的节奏网格上,再应用分片偏移。注释解释了原因:调度器 cron 每分钟触发一次,若返回的next_check_at带亚分钟偏移(如 12:05:30),cron 会等待而浪费整个 tick;网格对齐让同节奏告警落在同一规范网格上,已漂移的告警在下一次运行时惰性自愈。CalendarInterval/to_calendar_interval(...):面向产品的间隔契约(real_time、every_15_minutes、hourly、daily、weekly、monthly),镜像posthog.schema.AlertCalculationInterval。next_calendar_check_time(...):计算固定节奏或团队本地时区的每日/每周/每月锚点,并在使用pytz本地化时显式处理AmbiguousTimeError(取 is_dst=True)与NonExistentTimeError(取 is_dst=False 再 normalize),保证 DST 切换下墙钟行为不变。validate_and_normalize_schedule_restriction(...)与parse_blocked_windows_tuples(...):校验产品 payload 并产出纯"阻断窗口"契约。scan_next_unblocked_utc(...)、is_utc_datetime_blocked(...)、is_weekend(...):时区感知地应用限制,且全程不导入 Django 模型。
文档特别强调两条实践纪律:计算分片时用调度器真实间隔;创建、更新、推进告警时使用同一个分片函数,保证稳态节奏稳定。产品负责把自己存储的枚举与 payload 翻译成共享契约;到期资格、调度持久化、重试与编排保留在产品侧。调度测试见 test_scheduling.py 与 test_grid_scheduling.py。
前端契约:两条共享路径,数据在产品边界归一化
frontend-alerting.md 与 architecture.md 共同定义前端归属。共享产品告警 UI 位于products/alerts/frontend/components/,它是"表现层基础设施",不是前端产品注册表:
AlertEditor、AlertEditorFormDetails、AlertEditorSection:与容器无关的表单外壳(对应 AlertEditor.tsx);AlertDefinition*系列组件:定义、调度、下次评估与时区的可组合展示原语;AlertAdvancedOptions:共享的折叠与启用计数行为(见 AlertAdvancedOptions.tsx);QuietHoursFields:从归一化的限制、节奏与项目时区渲染静默时段输入(见 QuietHoursFields.tsx);AlertNotificationDestinationEditor:渲染归一化的已保存/待定目的地(见 AlertNotificationDestinationEditor.tsx);AlertEvaluationHistoryChart:渲染归一化的评估点与当前阈值(见 AlertEvaluationHistoryChart.tsx)。
产品自持的部分包括:keyed kea logic、API 调用、表单 schema、源/过滤控件、支持的目的地类型、HogFunction payload、阈值转换、启用计数计算与历史表格;modal、场景宽度与内嵌节尺寸也留在产品侧。
AlertWizard保留为共享的 HogFunction 创建流程。接入方通过带 key 的alertWizardLogicprops 提供支持的子模板 ID、WizardTrigger[]、WizardDestination[]及可选的 URL/preset 行为。后端支持某目的地,并不代表向导里就有这个选项——向导兼容性由 HogFunction 子模板决定。
前端规则还包括:业务逻辑放 keyed kea logic 而非 React hooks;持久化表单用kea-forms;网络操作必须有 loading 与防重复提交;AlertEvaluationHistoryChart的AlertEvaluationHistoryPoint(label/value/firedAtTime)要区分"当时已触发"与"按当前配置会触发",不得仅凭当前阈值推断历史 firing;Storybook 用真实背景 token(bg-bg-primary等,bg-default是遗留文本色别名,在浅色模式下会画出深色背景)。
路径一:给产品添加平台告警(Adopt,九步)
adopting-platform-alerting.md 给出接入清单,核心原则是"先组合现有平台,再考虑新增共享抽象":
1. 定义产品契约。编码前写下产品专属输入:评估什么数据、什么构成 breached/clear/inconclusive/transient error/permanent error;哪些生命周期状态与控制面动作对用户可见;固定节奏还是日历对齐;有哪些通知事件种类(firing/resolved/errored/broken);允许哪些目的地类型;需要 HogFunction、邮件还是两者;检查历史与详情页有哪些字段。文档警告:不要因为"产品将来可能需要"就新增AlertPolicy选项——先确认现有策略确实表达不了,且该差异是有意的。
2. 添加产品持久化与隔离。产品拥有自己的告警配置与检查历史模型:所有租户数据模型需要team_id与 fail-closed 作用域;存储构造AlertSnapshot所需字段加上产品评估与调度字段;必须保持一致的写入放进窄transaction.atomic()块;不要在事务内做通知投递。
3. 构建生命周期适配器。建一个产品状态机模块(参照products/logs/backend/alert_state_machine.py):选定或定义产品的AlertPolicy→ 把模型与近期历史转成共享快照 → 把领域评估转成CheckInput→ 调用evaluate_alert_check(...)或evaluate_alert_failure(...)→ 所有共享结果经由唯一一个产品自持的apply_outcome应用 → 控制面动作走共享 helper 并汇入同一 mutator → 扩展 alert-state semgrep 规则覆盖产品后端。除此之外,任何地方都不得修改state或consecutive_failures。
4. 定义通知内容与目的地。建一个薄产品模块(参照products/logs/backend/alert_destinations.py):每个通知动作定义一个EventKindSpec;事件 ID 与属性保持稳定(HogFunction 靠它们过滤与渲染);内事件 payload 必须包含模板用到的全部属性;显式声明产品允许的DestinationType值;校验/构建/创建/删除一律走products.alerts.backend.facade.api;删除用 team、alert ID 与允许事件 ID 限定。再次强调:共享支持某目的地不等于产品自动接入。
5. 让投递与生命周期状态事务化。HogFunction 目的地的六步序列:评估并保留每个 check 前的快照/结果用于回滚 → 生产内事件并保留每个ProduceResult→ 批内只 flush 一次 → 用alert_internal_event_delivered(...)逐个检查确认 → 持久化之前,为未确认结果恢复依赖投递的结果 → 按产品契约持久化已确认结果、检查历史与调度。邮件路径则显式决定:邮件失败是阻断生命周期迁移,还是单独记录。
6. 添加调度与到期选择。直接从实现模块导入产品面向的调度契约:
from products.alerts.backend.scheduling import ( CalendarInterval, advance_next_check_at, compute_shard_offset_seconds, is_utc_datetime_blocked, is_weekend, next_calendar_check_time, parse_blocked_windows_tuples, scan_next_unblocked_utc, to_calendar_interval, validate_and_normalize_schedule_restriction, )固定分钟检查:复用compute_shard_offset_seconds(...)与advance_next_check_at(...);若产品调度器间隔不同于共享默认值(DEFAULT_SCHEDULE_INTERVAL_SECONDS = 60),就包一层分片函数;创建/更新/评估路径保持同一稳定分片计算;自持实现并测试到期谓词(含 team 作用域、enabled、broken、snooze 与next_check_at行为)。日历对齐检查:to_calendar_interval(...)转换产品间隔值,next_calendar_check_time(...)配合团队 IANA 时区;静默时段用validate_and_normalize_schedule_restriction(...)校验、parse_blocked_windows_tuples(...)解析;绝不重写时区/DST 逻辑。模型访问、API 错误翻译、有界重试日志与next_check_at持久化留在产品适配器。
7. 添加产品编排。评估器、历史、到期查询、批处理、Temporal/Celery 编排、重试与指标都归产品。避免大 Temporal payload——只传 alert ID 与引用,在 activity 内加载数据;通知派发不进数据库事务。
8. 添加前端。按上一条"前端契约"选择AlertWizard或共享产品告警组件,或两者兼用;每个网络动作都要有 loading 与防重复提交保护。
9. 验证边界。补最低层级的测试,覆盖真实回归:共享状态机决策用例、产品快照与单一 mutator 行为、目的地校验/生成配置/归属安全删除/事件属性完整性、投递成功/入队失败/flush 失败/保存前回滚、到期谓词与节奏/分片行为、API schema 与租户隔离、向导逻辑与产品入口。
路径二:扩展共享告警平台(Extend)
extending-platform-alerting.md 的总原则:只有当"可复用能力、选项或高级行为属于共享基础设施"时才走这条路;若只有一个产品需要、共享契约会变成投机,就保持产品本地。
扩展分类:先找到单一事实来源
| 能力 | 主要事实来源 | 同时检查 |
|---|---|---|
| 生命周期状态、通知动作、控制面迁移、策略选项 | products/alerts/backend/state_machine.py | 共享决策测试、每个接入方策略与适配器、semgrep 规则 |
| 固定节奏、日历、时区或调度限制行为 | products/alerts/backend/scheduling.py | 产品包装、创建/更新路径、到期查询、调度器间隔、DST 边界 |
| 目的地类型或目的地级选项 | products/alerts/backend/destination_configs.py | HogFunction 模板/子模板、facade 导出、产品白名单、AlertWizard |
| HogFunction 持久化或投递语义 | products/alerts/backend/destinations.py | worker 批处理、回滚、投递指标、目的地测试 |
| 邮件传输能力 | products/alerts/backend/email_notifications.py | facade 导出、campaign key 语义、接入方模板与测试 |
| 共享 insight 查询评估 | products/alerts/backend/evaluation/ | 告警配置 schema、API 校验、查询种类门控、生成的 API 类型 |
| 共享告警模型或 API 选项 | products/alerts/backend/models/与products/alerts/backend/api/ | 迁移、OpenAPI、前端逻辑、MCP schema |
| 向导触发器、目的地或高级创建选项 | frontend/src/lib/components/Alerting/AlertWizard/ | HogFunction 子模板兼容性、接入方 props、kea 测试 |
变更若跨多行,必须逐行有意更新,不能把跨层契约藏在一个产品适配器里。
生命周期扩展规则
保持state_machine.py无 Django 与产品模型导入;只"针对观测到的语义差异"添加策略字段,并给出保留所有现有接入方行为的默认值;优先新增纯迁移 helper 而非直接改模型;更新受影响的 firing/resolving/snoozing/erroring/breaking/cooldown 决策表;审计投递回滚,确保新的通知动作不能在投递成功前消耗边缘。
调度扩展规则
辅助函数保持纯 Python;固定网格与日历契约保持显式,不要按产品名分支;调度器间隔假设必须显式化;保持确定性 UUID 分片与稳定稳态节奏;DST 下保持本地墙钟锚点,静默时段在团队时区中评估;测试覆盖错过间隔、漂移自愈、节奏变更、DST 转换、跨夜窗口与边界时刻;到期资格留在产品侧,除非存在真实的跨产品模型契约。
添加一个新目的地类型的七步
- 在
products/alerts/backend/destination_configs.py添加枚举值、HogFunction 模板 ID 与必填字段; - 扩展校验与
build_alert_destination_config(...),且不改变既有 payload; - 添加或更新
posthog/cdp/templates/<destination>/template_<destination>.py传输模板及其相邻测试; - 在
frontend/src/scenes/hog-functions/sub-templates/sub-templates.ts添加告警专属的模板兼容性; - 决定哪些产品显式允许该目的地,并更新其目的地编辑器或展示逻辑;
- 仅在相关子模板支持时把选项加入
AlertWizard; - 覆盖校验、生成 payload、归属安全删除、CDP 模板行为、产品渲染与向导兼容性的测试。
对已有目的地的高级选项,需先判断它是"传输级(transport-wide)"还是"产品专属":传输级进共享的类型化目的地数据与共享 builder;产品专属的措辞、事件属性与事件种类行为留在EventKindSpec与产品适配器。
其他扩展领域
- 投递:把事件创建、生产者 flush、生产者确认当三个阶段;绝不把"生产者确认"描述为"最终目的地投递";批 flush 保持高效有界;helper 只在调用方能凭返回值做显式回滚决策时才可非抛出;为新的派发阶段加指标与结构化失败上下文;验证部分批次生产失败只回滚受影响的告警。
- 邮件:
send_alert_email(...)保持传输 helper 定位,不做产品策略引擎;调用方拥有收件人、模板、上下文与 campaign key;只有多种告警类型需要同一传输行为时才加共享选项。 - 共享 insight 评估:
evaluation/包在 insight 告警内部是种类无关的。扩展契约的顺序是:定义告警配置 schema 与校验 → 添加产出ExtractionResult的 extractor → 比较与越限格式尽量独立于查询执行 → 按查询种类注册 dispatch 且不在无关 extractor 里分支 → create/update/simulate 路径一致地门控不支持或 feature-flag 的种类 → serializer/schema 变更后重新生成 OpenAPI 与前端类型。不要因为包名叫evaluation就把无关产品的评估器路由进来。 - 共享产品告警前端:只有当第二个接入方有相同的表现契约时才扩展
products/alerts/frontend/components/。组件保持与容器无关、无产品名分支;接受归一化的定义行、目的地视图模型、评估点、阈值、调度状态与启用计数;API 调用、表单 schema、kea logic、payload 构建、产品过滤器、检测器配置、仿真与历史表格留在产品适配器;优先小而可组合的定义原语,而不是"一个带满可选 props 的大组件";改动共享契约时要同时验证至少一条 insight 路径与一条 logs 路径。 - AlertWizard:业务逻辑进 keyed kea logic;触发器/目的地兼容性从 HogFunction 子模板推导;只有后端输入契约共享时才加可复用 UI 选项;保留 URL 恢复、已有告警检测、测试、loading 与防重复提交行为。
Facade 导出即兼容性承诺
产品面向的 Django helper 通过products.alerts.backend.facade.api导出;worker 专属的投递原语(需要详细结果类型时)留在products.alerts.backend.destinations。要更新类型标注与 help text,让 OpenAPI 与 MCP schema 保持有用;当归属边界、公开 helper 集合或接入流程变化时更新技能文档本身。不要仅为方便而暴露内部 helper——一个 facade 导出就是一份兼容性承诺(对照 facade/api.py 中snooze_alert_from_slack的注释可见该承诺如何体现:Slack webhook 无 team 上下文,授权必须全部从告警行重新推导,且用select_for_update()行锁做权威复查)。
拒绝错误的抽象
出现以下任一情况就停下,把变更留在产品侧:
- 不存在第二个用例;
- 选项会把产品模型字段泄漏进共享状态机;
- 通用 helper 需要产品名分支;
- 某目的地选项只改变一种事件种类的内容;
- 共享到期查询需要某个产品的状态名或租户模型;
- 向导选项没有共享的 HogFunction 输入契约。
平台应该从已验证的接入方需求中生长,而不是从猜测的未来灵活性中生长。
把告警当作端到端系统对待
原文档"Current limits"之外的另一核心章节指出:告警跨越多条边界——目的地可能在 UI 里合法、被 API 拒绝、保存后不可见、或保存后永不被调用;单条边界上的单元测试不能证明下一条边界工作。
对每个受支持的告警来源与目的地类型,必须经由公共接口测通这条路径:
- 通过每种受支持的管理 API 创建或更新目的地;
- 读回告警,确认目的地可见;
- 触发告警,确认路由选中了该目的地;
- 确认投递 worker 接受了通知,或记录了清晰的失败。
最后一步用测试传输或 mock 外部端点完成,不依赖真实客户目的地。每当共享过滤器、白名单、事件 ID、模板 ID 或归属规则变化时,添加聚焦测试。
配套要求还包括:维护一份显式的兼容性矩阵(受支持的来源、事件、目的地与管理路径组合),尽量让单一事实来源驱动相关白名单;若必须用多个白名单,就要命名受支持组合并测试——永远不要把"空匹配"当作成功而不记录原因。在每个边界埋点:配置接受/拒绝、目的地选中、事件产生、worker 匹配、投递尝试、投递结果,带关联 ID 与稳定的来源/目的地维度,对相邻阶段的持续失配告警——这样才能发现各组件都不报错的静默丢件。部署后与定期运行隔离的合成检查,且合成检查必须证明整条路径,而不只是生产者接受了事件。
把共享变更当作"一个告警系统"来评审
当变更触及共享告警时,评审所有可能消费它的契约,而不只是动机产品。从参考接入方出发,检查受影响维度:生命周期(产品适配器、控制面动作、持久化状态、通知边缘);投递与目的地(事件生产者/消费者、确认与回滚、模板、产品与通用管理 API);调度(创建/更新路径、到期资格、重试、节奏、静默时段、时区行为);授权(目的地创建、管理与派发时的产品/项目/组织边界);前端与 API 契约(生成的客户端、待定目的地重试、已有目的地可见性、所有使用共享数据的 UI);分类与资格(所有对事件/模板/目的地/告警类型做分类的生产者与消费者——一个新限制可能在一条路径上阻断创建,在另一条路径上阻断投递)。
对事件、目的地、查询或授权变更,要按精确事件 ID、模板 ID、模型类型、通用 API、UI 以及正则/前缀匹配,映射出所有匹配的产品与客户端;注意"一个新产品可以自持目的地生命周期,而另一个产品仍有意使用通用 HogFunction 路径"这种混合状态。当通用访问违反归属或授权边界时立即收紧;只为显式安全、受支持的路径保留通用访问,并有意识地将其迁移到产品自持 API;为新归属边界与每一条仍受支持的路径添加公共接口测试,并包含证明未授权用户不能跨产品/项目/组织边界列举、读取、创建、更新、删除或派发目的地的负向测试。
当前局限:不要发明平行的框架
文档最后给出明确的现状边界:目前没有通用告警基类模型、产品注册表、push 模式的submit_check(...)、通用调度器 runner,也没有通用 Temporal harness。不要围绕这些缺失的拼图发明平行框架。对非 insight 产品,在共享契约落地之前,评估、持久化、到期查询、历史与编排都保留在产品内。
这既是约束也是路线图:PostHog 的告警平台由 logs 与 insight 两个参考接入方反推生长——共享层只沉淀被两个以上产品真实验证过的契约(状态机、调度数学、目的地注册表、投递确认、邮件传输、归一化前端原语),而模型、到期谓词、编排与领域评估始终留在产品侧。理解这一点,是读完本文后最重要的收获。
【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考