PostHog Web Analytics 支持工单分级排查实战指南:从队列枚举、诊断 Playbook 到修复产出
【免费下载链接】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/triaging-web-analytics-support/技能文档为主体,完整呈现一套端到端的 Web Analytics 支持工单分级(Triage)方法论:先在数据仓库中枚举工单队列,再把工单归入六大诊断形态并执行对应 Playbook,最后产出有证据支撑的回复草稿与修复 PR。读完你将掌握 PostHog 内部支持团队处理“指标对不上”“流量下降”“Tracker 不加载”等高频问题的完整排查链路,并了解底层源码(HogQL 通道分类、会话入口属性聚合、ClickHouse 字典)如何支撑每一个诊断结论。
说明:该技能定位为内部工具(Internal-only)——它需要跨客户查询支持与用量数据,涉及客户名称、流量数字的内容严禁进入公开产物(PR、Issue、Commit)。文中所有 SQL 均针对 PostHog 内部 MCP 数据项目(US,project 2)编写。
一、分级工作的核心原则
技能文档开篇给出两条贯穿始终的准则:
- 先诊断、后写码:绝大多数被上报的“Bug”实际上是可解释的语义问题;而真正的 Bug 往往先出现在错误追踪(Error Tracking)或原始数据里,而不是代码里。
- 先定层、再下结论:任何症状都必须先判定它存在于哪一层——Capture(采集)→ Ingestion(摄入)→ 存储事件(Stored Events)→ 查询期分类(Query-time Classification)→ UI。例如原始
count()的下降不可能由查询期的机器人排除逻辑引起;分类规则的变更也不可能改变已存储的计数。在提出修复方案前,必须先明确指出证据指向哪一层。
这两条原则分别对应了工单分类阶段的“层拆分”与“先查既有先例”两个横切规则。
二、第一步:枚举工单队列
工单存放在 PostHog 的 Conversations 产品中,可以通过 PostHog MCP 的execute-sql工具对内部项目的system.support_tickets表(project 2,US)进行查询;Zendesk 镜像则承载了完整评论历史。
该表在仓库中确有实现:posthog/hogql/database/schema/system.py 中以PostgresTable注册了support_tickets(见support_tickets: PostgresTable = PostgresTable(name="support_tickets", ...)),并配套tags、assignee等内部联结表,访问隔离通过ticket_id IN (SELECT id FROM system.support_tickets)形式的谓词作用域实现。技能文档提醒:查询system.*表前,务必先用system.information_schema.columns确认列结构。
2.1 拉取未处理的 Web Analytics 工单
references/ticket-queries.md 提供了开箱即用的队列扫描 SQL:
SELECT ticket_number, id, status, priority, channel_source, substring(last_message_text, 1, 400) AS last_msg, created_at, message_count FROM system.support_tickets WHERE status IN ('new', 'open', 'pending') AND created_at >= now() - INTERVAL 21 DAY AND (last_message_text ILIKE '%web analytics%' OR last_message_text ILIKE '%bounce%' OR last_message_text ILIKE '%utm%' OR last_message_text ILIKE '%pageview%' OR email_subject ILIKE '%web analytics%') ORDER BY created_at DESC使用该查询需要注意几个已知陷阱:
last_message_text只有最新一条消息,且可能被截断(这里截取前 400 字符);message_count > 1意味着还有你没看过的历史对话。- 关键词过滤会漏掉措辞不同的工单——因此还要同时阅读
#support-web-analyticsSlack 频道中镜像过来的 Zendesk 工单流;每条消息里的应用内工单链接携带 Conversations UUID。
2.2 通过 Zendesk 仓库镜像取完整评论历史
system.support_tickets没有消息表,完整评论历史在 Zendesk 镜像表的一个 JSON 数组列中。核心技巧是使用arrayJoin展开child_eventsJSON 数组,再用JSONExtractString提取正文:
SELECT created_at, body FROM ( SELECT created_at, JSONExtractString( arrayJoin(JSONExtractArrayRaw(assumeNotNull(toString(child_events)))), 'body') AS body FROM zendesk.ticket_events WHERE ticket_id = {zendesk_ticket_id} ) WHERE body != '' ORDER BY created_at ASC三个关键细节:
assumeNotNull必不可少——arrayJoin不能直接作用于Nullable列;- 应在子查询之外应用
LIMIT,否则arrayJoin会先行展开导致 LIMIT 失效; - MCP 展示会截断长单元格,应显式提取字段而不是直接倾倒原始 JSON。
2.3 将请求者邮箱解析到组织/团队(US 与 EU)
PostHog 内部数据项目区分 US 与 EU 区域,且EU 客户数据无法从 US MCP 项目查询。跨区域解析请求者邮箱时使用UNION ALL:
SELECT 'us' AS region, u.email, u.current_team_id FROM postgres.posthog_user u WHERE u.email = '{email}' UNION ALL SELECT 'eu', u.email, u.current_team_id FROM eu_postgres_posthog_user u WHERE u.email = '{email}'补充可用的辅助表:postgres.posthog_organizationdomain(仅已验证域名,小组织常为空)、按名称查询的postgres.posthog_organization、以及all_posthog_team。任一区域的单团队事件数据,则改用querying-production-databases-via-metabase技能(覆盖 prod-us 与 prod-eu 访问)。
三、第二步:按诊断形态分类并执行 Playbook
技能文档将 Web Analytics 工单归纳为六种诊断形态,每种形态都有明确的触发词与第一步动作:
| 形态 | 触发词 | 第一步动作 |
|---|---|---|
| 前端崩溃(Frontend crash) | “everything crashes”、异常 ID、堆栈 | 错误追踪查询;sourcemap 后的帧可定位文件。US 与 EU 项目都要查 |
| 两个数字对不上(Two numbers don't match) | “两个不同的跳出率”、“insight X 与 tile Y 不一致” | 先查语义而非代码:事件级 vs 会话入口级作用域、“landing vs containing”、any-event vs entry-event 过滤器解释大多数情况 |
| 流量随时间下降(Count drop over time) | “pageviews 下降”、“追踪丢失” | 层拆分:原始存储计数 vs 查询期排除。看$pageview与$pageleave比值、UA 分段、SDK 版本固定;机器人形态流量消失很常见,不是 PostHog 的 Bug |
| Tracker 不加载 / 计数低于竞品 | “数字低于其他工具”、GTM、consent、广告拦截器 | 用 Playwright 对客户线上站点做运行时加载审计:加载方式、首请求时机、黑名单模拟。见 references/loading-audit.md |
| 广告平台集成错误 | “无法重新添加 source”、OAuth 报错、“没有转化” | Source 重建路径、OAuth 失败模式(如 Microsoft AADSTS650052)、归因连接键(精确 campaign 名 + 归一化 source,兜底需两个 UTM 同时存在) |
| 渠道类型误分类 | “显示为 Direct”、“渠道不对” | 查 channel_definitions.json + channel_type.py 中的决策树;未知 source + 被剥离的 referrer 会落到 Direct |
两条横切规则:
- 先定层再修复:明确证据指向 Capture → Ingestion → 存储事件 → 查询期分类 → UI 中的哪一层。
- 先查先例再动手:检索既有 Issue/PR 与频道历史——技能文档明确提到自引用排除(self-referral exclusion)、AI 渠道类型、OAuth 错误提示等反复出现的诉求已有开放 Issue,其上下文会改变正确的回应方式。
3.1 Playbook:前端崩溃
- 拿到异常:工单通常带异常 ID 或压缩后的堆栈(
chunk-XXXX.js帧)。 - 在内部项目的错误追踪中检索(
query-error-tracking-issues-list配合searchQuery和/webURL 过滤)。Issue 行的source字段给出 sourcemap 后的文件;用verbosity: stack查看事件可获得解析后的函数名。 - 阅读命中的代码路径。文档记录的 Web Analytics 高频崩溃类:kea-router 会 JSON 解析 query 参数——
?someParam=123会变成数字、?p=a&p=b会变成数组;任何由searchParams喂入的持久化 reducer 都可能永久持有非字符串值,进而在每次 selector 重算(日期变更、过滤变更)时崩溃,包括那些只是connect了该 logic 的场景。 - 两端同时修复:在 router 边界强制类型转换,并且在读路径也做处理(仅修边界无法覆盖已持久化的坏值);补一个廉价的纯函数回归测试,断言非字符串输入不会抛异常。
- 区分 CI 噪音:门禁 Job(如 “X Tests Pass”)会在依赖 Job 被重复运行的 superseded 版本取消时失败——先读门禁日志再追查幻影测试失败;一个执行了零步骤就失败的 runner 属于基础设施问题,重跑即可。
3.2 Playbook:两个数字对不上
几乎总是作用域语义问题,主要有三种形态:
- Landing vs Containing(落地 vs 包含):总览图块按“任意事件匹配过滤器”圈定会话;而按路径的跳出率/下钻行按会话入口值圈定。过滤器相同、分母不同,两者都正确。
- 事件级过滤 vs 会话入口级下钻:按事件
utm_campaign过滤会选中“包含任一匹配事件”的会话;而 UTM 下钻图块按会话$entry_utm_campaign分组。一个在会话中途才带上 UTM 的会话能匹配过滤器,却显示为 “(not set)”。 - 算子漂移:URL 的
containsvsequals、路径清洗开/关、同名的事件属性 vs 会话属性。
分辨率结论:精确定义两套口径,然后给客户对齐的一对——与会话级下钻比较时,就用会话入口属性过滤。只有当两个表面声称同一口径却仍然不一致时,才升级为 Bug。
源码佐证:该语义差异直接对应仓库中的会话入口属性聚合实现。posthog/hogql/database/schema/sessions_v2.py 中$entry_utm_campaign被定义为null_if_empty(arg_min_merge_field("initial_utm_campaign"))——即会话入口属性由initial_utm_campaign经过argMin合并聚合而来,这与事件级utm_campaign天然属于不同口径。相关查询构建器集中在 products/web_analytics/backend/hogql_queries/(overview 与 stats_table 两套 query builder,如 web_overview.py、stats_table.py)。
3.3 Playbook:流量随时间下降
决定性问题是:哪一层掉了?
- 拉原始日序列:通过
querying-production-databases-via-metabase按天对事件做count()。如果原始计数下降,那么任何查询期变更(机器人分类、排除规则、物化)都不可能是原因——它们永远不会改变已存储的计数。 - 快速排除假线索的健全性检查:
$lib+ SDK 版本分段(版本固定 = 无 SDK 回归)、重复 UUID 计数(去重)、按星期几配对比较(季节性)、按小时分布(区域/故障)。 - 机器人指纹:只有
$pageview下降而$pageleave持平;损失集中在少数几个 UA 字符串,且周环比萎缩 10~50 倍;每个会话约 2 次 pageview 却没有 pageleave。这说明非人类流量停止执行 JS SDK——通常是客户侧边缘设施(WAF、bot-fight 模式、JS challenge)发生变化,或爬虫停止。他们的服务器日志仍会计数这些请求,这正是“我们的日志看起来没变”的原因。 - 回复框架:PostHog 存储的是它实际收到的内容;识别消失的流量段;请客户确认在具体日期他们的边缘设施改了什么;并说明新的更低水平更接近真实人类流量。
3.4 Playbook:Tracker 不加载 / 计数低于竞品
对客户线上页面执行运行时加载审计(见 references/loading-audit.md)。反复出现的高频结论:
- 投递链比端点代理更重要:反向代理的
api_host本身不可被拦截,但如果 SDK经由 GTM加载,拦截googletagmanager.com照样会杀死它。要么第一方脚本 + 第一方端点,否则都不算数。 - Consent 延迟吃掉快速跳出:即使在无横幅区域,标签管理器与 consent 平台也是异步解析的;每个在该窗口前离开的访客都不会发送任何数据。需要测量首请求时机与竞品脚本的差距。
- 对等比较:竞品工具在访问定义、机器人过滤、无 Cookie 计数上都有差异;先量化加载链差距,再谈口径差距。
3.5 Playbook:广告平台集成错误
- 软删除的 Source 不会阻止重建:前缀检查会排除已删除行,所以出现 “Prefix already exists” 说明旧 Source 仍然活跃。
- OAuth 重连失败是重建的头号障碍:Microsoft 的 AADSTS650052(缺少租户管理员同意 / service principal)会以裸
invalid_clienttoast 形式出现。一定要问清楚确切的错误文本和它出现的位置(登录弹窗 vs 表单字段 vs 创建 toast)——每个位置对应不同的代码路径。 - 账户选择器可能受项目管理员门控:成员看到权限错误,而管理员能看到账户。
- Marketing Analytics 归因逻辑:转化事件优先用自己的 UTM 归因;否则回退到窗口内最近一次同时携带
utm_campaign和utm_source的 pageview,然后按大小写敏感的精确 campaign 名+ 归一化 source 做 LEFT JOIN。任何缺失都会落入 “organic”。如果付费转化为 0 而 organic 行很胖,说明归因连接键或“双 UTM 要求”失败,而不是目标(goals)配置失败。 - 原生广告源只需同步其统计表即可获得花费指标;PostHog 事件只用于转化目标。
3.6 Playbook:渠道类型误分类
这是最能体现“查询期分类”层概念的一类:
- 分类发生在查询期(HogQL),由 channel_definitions.json 驱动,通过
channel_definition_dict这个 ClickHouse 字典参与计算;修改定义会自动重分类历史数据。 - 默认决策树以 Direct 兜底:
unknown source+$direct引用域最终映射到 Direct——因此在 referrer 被剥离的流量上,一个无法识别的utm_source就会显示为 Direct。正确修法是添加定义行,而不是改兜底逻辑——该兜底行为已被测试钉死为对垃圾 UTM 的刻意处理。 - 定义变更需要 ClickHouse 迁移:
add_missing_channel_types只会 INSERT 缺失的 (domain, kind) 组合;类型变更需要重建(truncate → re-insert →SYSTEM RELOAD DICTIONARY)。同时要更新create_channel_definitions_file.py,否则下一次重新生成会回退 JSON。 - **同源插页(bot challenge)**会破坏
document.referrer却保留 query 字符串——自引用且 UTM 完好的流量是其签名。缓解手段:用before_send重写自引用、在客户自己的域名上加自定义渠道规则、缩小 challenge 范围。
3.6.1 源码级佐证:默认决策树与字典实现
默认分类逻辑完整实现在 posthog/hogql/database/schema/channel_type.py 的_initial_default_channel_rules_expr()中,其注释明确说明“该逻辑同时被官方文档引用,改动需同步两边”。核心是一段multiIf决策树,节选关键分支:
multiIf( match({campaign}, 'cross-network'), 'Cross Network', ({medium} IN ('cpc','cpm','cpv','cpa','ppc','retargeting') OR startsWith({medium},'paid') OR {has_gclid} OR {gad_source} IS NOT NULL), coalesce(lookupPaidSourceType({source}), ..., 'Paid Unknown'), ({referring_domain} = '$direct' AND {medium} IS NULL AND ({source} IS NULL OR {source} IN ('(direct)','direct','$direct')) AND NOT {has_fbclid}), 'Direct', coalesce(lookupOrganicSourceType({source}), ..., 'Unknown') )要点:付费判定优先(gclid、fbclid、gad_source、paid medium 前缀),Direct 判定要求 referrer 为$direct且 source/medium 为空,最后兜底到 Referral/Unknown。自定义渠道规则(CustomChannelRule)支持 EXACT、IS_NOT、IS_SET、IS_NOT_SET、ICONTAINS、NOT_ICONTAINS、REGEX、NOT_REGEX 等算子,通过multiIf+coalesce叠加在默认规则之上(custom_rule_expr优先,未命中则落到内置规则)。
底层字典在 posthog/models/channel_type/sql.py 中构建:channel_definition是ORDER BY (domain, kind)的 MergeTree 表,channel_definition_dict采用COMPLEX_KEY_HASHED()布局(复合主键 domain, kind),LIFETIME 为 3000~3600 秒,通过 DICT_READER 只读账号从表加载。数据来自 channel_definitions.json(仓库中约 1500+ 行,每条记录形如["domain/medium", "kind", "domain_type", "type_if_paid", "type_if_organic", "is_app"],例如["email", "medium", null, null, "Email", false]、["andisearch.com", "source", "AI", null, "AI", false])。相关的两个迁移文件为 posthog/clickhouse/migrations/0069_add_channel_definitions.py 与 posthog/clickhouse/migrations/0073_add_missing_channel_types.py。
四、第三步:产出工件(Artifacts)
每个工单最终要产出三类工件:
- 回复草稿:每一条论断都要落到
file:line、查询结果或文档链接上。与其只解释客户“为什么错”,不如直接给出对齐的过滤器/属性——例如用会话$entry_utm_campaign替代事件utm_campaign。 - 修复 PR:每个修复使用一个 worktree + 分支,conventional commit 规范提交,用仓库模板创建 Draft PR。公开仓库安全红线:泛化描述 Bug,绝不包含客户名称、Zendesk 编号或客户流量数字;Slack/工单链接(需认证访问)可作为来源上下文。
- 会话笔记:在
.notes/维护一份持续更新的分级笔记,每个工单一个章节,且每个工单必须有明确的 “action left” 标记,便于人类接手队列。
五、第四步:验证工具
- 运行时加载审计与流量模拟:references/loading-audit.md。
- 生产查询侧检查(按团队事件序列、UA 分段、摄入告警):
querying-production-databases-via-metabase技能覆盖 prod-us 与 prod-eu 访问。 - 错误追踪:MCP 的
query-error-tracking-issues-list/query-error-tracking-issue-events配合verbosity: stack可获得 sourcemap 后的帧。
5.1 运行时加载审计实操模式
Playwright(Chromium)双轮扫描 2~4 个代表性 URL(落地页、一个深层页面、一个带 UTM 参数的页面):
- 正常轮(Normal pass):真实用户 UA、US 时区/语言环境。记录对 tracker 域名的每个请求及相对导航开始的时间戳。加载完成后再等约 10 秒,评估页内状态:
window.posthog(__loaded、config.api_host、config.token前缀、person_profiles)、标签管理器容器与dataLayer长度、consent 平台对象及其解析后的状态、竞品 tracker 全局变量与脚本标签。 - 黑名单轮(Blocklist pass):同上,但中断匹配 EasyPrivacy 风格域名列表的请求(
googletagmanager.com、google-analytics.com、各分析厂商域名),对比哪些 tracker 存活。
结果解读要点:
- 加载方式:
<head>内联片段 vs 标签管理器 vs 打包引入。代理了api_host但走 GTM 投递的,GTM 被拦截照样失效——投递链是最薄弱环节。 - 首请求时差:tracker 相对导航开始的第一条网络活动毫秒数,与竞品的差距就是快速跳出的盲区。
- Consent 地理门控:consent 平台在 GDPR 区域外常常根本不加载;那些区域里的延迟来自标签管理器启动,而不是横幅。
两个重要注意事项:
- 主流 SDK 都会抑制自动化:posthog-js 与多数竞品能检测
navigator.webdriver,不会从无头浏览器发送事件。审计中 0 条采集请求是预期现象,与真实用户无关——断言应基于脚本/运行时存在性与时机,而不是事件 POST。 - 带
?utm_...测试参数的访问无害,但严禁向客户项目注入伪造的转化事件;另外数字型主机名在new URL()中会被解析为 IPv4,不要在合成输入上断言精确解析后的主机。
5.2 既有先例与演进方向
文档记录的既有资产:每次审计现场编写约 120 行的会话级审计脚本;一个更完整的 CLI(check-loading、new-user、returning-user场景)曾存在于内部沙箱仓库,并有将其发布为 tools/traffic-sim/ 的开放计划(该目录当前已存在于仓库中,包含流量模拟相关脚本与文档)。此外,逐页对比表(加载方式、片段位置、config key/host、与基线匹配度)能让部分迁移状态一目了然——缺失 tracker 的页面会立刻显现。
六、附录:错误追踪交叉引用
前端崩溃类工单中,EU 客户报告的崩溃通常在 US 也有出现(PostHog 员工与 US 用户执行的是同一份代码)。在内部项目的错误追踪中用 MCP 工具检索即可;Issue 行的source字段指出 sourcemap 后的文件,verbosity: stack给出解析后的帧名——这样无需本地复现,就能从压缩的客户堆栈定位到file:line。
参考资源索引
- 技能主文档:.agents/skills/triaging-web-analytics-support/SKILL.md
- 工单查询 SQL 集:.agents/skills/triaging-web-analytics-support/references/ticket-queries.md
- 六大形态诊断 Playbook:.agents/skills/triaging-web-analytics-support/references/diagnostic-playbooks.md
- 运行时加载审计指南:.agents/skills/triaging-web-analytics-support/references/loading-audit.md
- 支持工单数据表定义:posthog/hogql/database/schema/system.py
- 会话入口属性聚合:posthog/hogql/database/schema/sessions_v2.py
- 渠道分类决策树:posthog/hogql/database/schema/channel_type.py
- 渠道定义数据与字典:posthog/models/channel_type/channel_definitions.json、posthog/models/channel_type/sql.py
- 渠道定义迁移:posthog/clickhouse/migrations/0069_add_channel_definitions.py、posthog/clickhouse/migrations/0073_add_missing_channel_types.py
- Web Analytics 查询构建器:products/web_analytics/backend/hogql_queries/
【免费下载链接】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),仅供参考