PostHog CDP 的 Hog Function 模板:定义、编写规范与模板同步机制
【免费下载链接】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
Hog Function 模板是 PostHog 数据管道(CDP V2)中用户快速接入外部服务(CRM、营销平台、Webhook 等)的标准入口。本篇基于仓库中的模板说明文档 README,结合模板数据类、同步命令与数据库模型的源码,完整讲清模板“为什么是临时代码配置”“如何写一个合格模板”“过滤应该放在哪里”以及模板如何从代码同步进数据库并支撑“模板与函数失步”机制。
模板是什么:HogFunctionTemplate 的数据结构
模板本质上是HogFunction的临时(ephemeral)配置:模板只是一份代码化的定义,用户基于它创建出独立的、属于用户自己的 HogFunction。模板的核心数据结构是一个不可变 dataclassHogFunctionTemplateDC,定义在 hog_function_template.py,所有内置模板都是它的实例:
| 字段 | 类型 | 说明 |
|---|---|---|
status | alpha / beta / stable / deprecated / coming_soon / hidden | 模板生命周期状态;deprecated模板仍会被加载(存量客户可能还在用),只是不在 UI 中列出 |
free | bool | 是否免费可用 |
type | 见下方类型表 | 模板所属的函数类型 |
id | str | 模板唯一标识,如template-discord,同步进数据库后作为template_id |
name/description | str | UI 展示的名称与描述 |
code | str | 模板主体:Hog 或 JavaScript 代码 |
code_language | javascript / hog | 代码语言 |
inputs_schema | list[dict] | 用户可配置输入项的声明式 schema(key、type、label、description、default、secret、required 等) |
category | list[str] | 分类标签,如["Customer Success"] |
filters | dict(可选) | 建议的源过滤条件 |
mapping_templates | list[HogFunctionMappingTemplate](可选) | 事件字段到输入项的映射模板,定义默认映射 |
masking | dict(可选) | 数据脱敏配置 |
icon_url | str(可选) | 模板图标 |
type的取值定义在同文件 HogFunctionTemplateType 中,与 HogFunction 的类型保持一致,包括destination(服务端目的地,Hog 代码)、site_destination/site_app(浏览器端,JavaScript 代码)、source_webhook、warehouse_source_webhook、transformation、transformation_log、internal_destination等。
仓库当前内置了约 50 个模板,全部注册在 templates/init.py 的HOG_FUNCTION_TEMPLATES列表中,覆盖 ActiveCampaign、Airtable、Attio、Avo、AWS Kinesis、Braze、Brevo、Clearbit、Customer.io、Discord、Google Cloud Storage、Google Pub/Sub、HubSpot、Intercom、Klaviyo、Loops、Mailchimp、Mailgun、Mailjet、Make、Microsoft Teams、OneSignal、PostHog、Reddit Pixel、RudderStack、Salesforce、SendGrid、Snapchat/TikTok Pixel、Userlist、Zapier、Zendesk 等目的地,以及_siteapps下的站点应用(通知栏、HogDesk、Pineapple Mode 等)和_internal下的空白模板(blank_site_destination、blank_site_app)。
为什么模板是“临时代码”而不是数据库记录
模板说明文档给出了模板刻意保持 ephemeral 的三条设计理由,每一条都在源码中有对应支撑:
- 管理成本低——模板只是 posthog 仓库里的代码。模板不需要独立维护一份数据库状态,版本管理直接走 Git。真正把模板落库的是同步命令,而不是模板的“出生地”。
- 更新冲突交给用户处理——改模板不会改用户的函数。修改模板只会在 UI 上提示该函数“与模板失步(out of sync)”,由用户自己选择是否拉取模板的更新并解决冲突。数据库模型 HogFunctionTemplate 的 docstring 明确说明了它“替代内存中的模板存储,并支持基于 sha 的版本化”:模型在
save()时会对template_id、code、code_language、inputs_schema、status、mapping_templates、filters、icon_url、masking做 SHA256 并取前 8 位作为内容sha(见 内容哈希生成),且unique_together = ("template_id", "sha")——同一模板的每个内容版本都是一条独立记录。而用户侧的HogFunction只通过template_id关联模板(HogFunction.template 属性 总是取该template_id下最新一条记录)。从源码结构看,正是“函数引用 template_id、最新模板 sha 可能已变化”这一机制,支撑了 UI 上的失步提示:模板变了,函数不变,差异留给用户裁决。 - 分享极其简单——一切可以通过 URL 完成。模板即代码加 URL 参数,无需数据库层面的共享流程。
一个真实模板的完整解剖:Discord 目的地
以 Discord 模板 为例,可以看清一个模板由哪几部分组成。它向一个 Discord 频道的 Webhook 发送消息,status="stable"、type="destination"、code_language="hog"。
inputs_schema(用户可配置项):
webhookUrl(string,必填,非 secret):Discord Webhook 地址;content(string,必填):消息正文,默认值**{person.name}** triggered event: '{event.event}'——注意这里的{person.name}、{event.event}就是 Hog 模板变量,用户在 UI 中可以把任何源数据(Event、ActivityLog 等)注入进来;allowedMentions(choice,必填,默认none):控制@role/@user/@everyone是否真正产生提醒,默认全部以纯文本展示,避免事件数据误触 ping。
Hog 主体代码:
code=""" if (not match(inputs.webhookUrl, '^https://discord.com/api/webhooks/.*')) { throw Error('Invalid URL. The URL should match the format: https://discord.com/api/webhooks/...') } let allowedParse := []; if (inputs.allowedMentions == 'roles_users') { allowedParse := ['roles', 'users']; } else if (inputs.allowedMentions == 'everyone') { allowedParse := ['everyone', 'roles', 'users']; } let res := fetch(inputs.webhookUrl, { 'body': { 'content': inputs.content, 'allowed_mentions': { 'parse': allowedParse } }, 'method': 'POST', 'headers': { 'Content-Type': 'application/json' } }); if (res.status >= 400) { throw Error(f'Failed to post message to Discord: {res.status}: {res.body}'); } """.strip()可以看到代码只做了三件事:校验输入、按选择构造请求体、fetch调目的地并检查状态码。这就是文档所要求的“只保留与目的地通信所必需的最小逻辑”。另外,部分模板(如 HubSpot、Mailgun、Salesforce、Loops 等)会同时导出多个模板变体(如hubspot与hubspot_event、klaviyo_event与klaviyo_user),以覆盖同一服务不同粒度的接入场景(见 templates/init.py 的导入清单)。
对于来自旧 Plugin 体系的目的地,模板目录下还保留了HogFunctionTemplateMigrator(基类定义):HOG_FUNCTION_MIGRATORS字典把旧插件的plugin_url映射到各 Migrator(Customer.io、SendGrid、Google Pub/Sub、Google Cloud Storage、Engage、PostHog、HubSpot、RudderStack、Loops、Avo),用于把存量插件配置迁移为基于模板的 HogFunction。
编写好模板的两条准则:泛化输入与正确放置过滤
说明文档提出了两条对模板作者最重要的纪律,值得逐条展开。
准则一:模板要尽可能通用,一切可变数据走 inputs_schema
模板的 Hog 代码只应该做与目的地通信、触发工作流所必需的事,其余全部交给inputs_schema声明的输入项。这样做的好处是:用户在创建函数时可以借助 Hog 模板化把任意源字段(Event 属性、ActivityLog 等)注入到输入里,模板本身因此与任何特定业务解耦。文档特别指出:如果你发现输入模板化(input templating)已经无法表达需求,应先和 CDP 团队(#team-cdp)协商扩展能力,而不是写一个过度耦合的 Hog 函数——这是团队级的架构约束,防止模板库退化成一次性脚本的集合。
这一准则在测试基建中也有呼应:posthog/cdp/templates/helpers.py中的 BaseHogFunctionTemplateTest 为每个模板测试提供统一的执行沙箱——compile_hog编译模板代码为字节码后,用execute_bytecode在注入event、person、source、inputs的 globals 中运行,并把fetch、print、postHogCapture、produceToWarehouseWebhooks全部替换为可断言的 mock(mock_fetch_response还允许按 URL 预置返回体)。模板的“通用性”因此是可被单测验证的属性,而不是口号。
准则二:过滤尽量不写在 Hog 代码里
来源数据的过滤几乎不应该在模板的 Hog 代码内完成。PostHog 在配置数据源时提供了强大的、与源无关的过滤 UI,保证函数“只在需要时才运行”,这比在函数内部if掉不匹配的事件更省资源,也更符合模板的复用目标。文档同时说明这不是硬性规则——确有需要时在 Hog 里过滤也可以,但要意识到这会限制函数的可复用性。模板数据结构里预留的filters字段(HogFunctionTemplateDC)正是用来在模板层面给出建议性的源过滤条件,而不是把判断逻辑塞进代码。
模板同步:从代码到数据库的完整链路
模板虽然是“仓库里的代码”,但运行时需要落库。这一过程由 Django 管理命令 sync_hog_function_templates 完成,命令 help 即:“Sync HogFunction templates from in-memory and node.js to database”。其处理流程为:
- Python 模板:遍历
HOG_FUNCTION_TEMPLATES,逐一把HogFunctionTemplateDC转为 dict 加入待同步列表; - 仓库源 Webhook 模板:遍历
SourceRegistry.get_all_sources(),取每个WebhookSource的webhook_template(来自products/warehouse_sources的源注册表),并始终包含默认的 fallback 仓库 Webhook 模板; - Node.js 模板:通过
get_hog_function_templates()调用 plugin server 端点获取 JS 侧模板(site_destination/site_app类型的 JavaScript 代码在此通道进入系统)。任何非 200 响应都会告警并抛出异常,防止 Python 侧与 JS 侧模板清单不一致; - 逐个落库:对每个模板调用 sync_template_to_db——按
id找到既有模板则走更新序列化器,否则走创建;序列化成功后由模型save()触发字节码编译(code_language == "hog"时经compile_hog编译并缓存到bytecode字段,失败则置空并记日志)与 sha 重算(compile_bytecode 与 save); - 清理:删除所有以
coming-soon-开头且已不在当前清单中的模板; - 汇总输出:统计总数、创建/更新数、删除数与错误数。
值得注意的是命令内的测试模式过滤:当settings.TEST为真时,只同步site_destination/site_app类型的 Python 模板、白名单中的仓库源模板(Stripe、Customer.io、Slack、GitHub、default 等 id)和少量 Node.js 模板(template-slack、template-webhook、template-geoip等),避免测试数据库被全量模板灌满。
模板的正确性验证
模板质量由两层测试保障:
- 全量校验:test_cdp_templates.py 中的
test_templates_are_valid遍历HOG_FUNCTION_TEMPLATES,用InputsSchemaItemSerializer校验每个模板的inputs_schema合法,并对非编译型过滤模板执行compile_hog,断言产出的字节码以"_H"开头——即每个内置模板的 schema 与代码在 CI 中都是可编译、可校验的; - 单模板行为测试:每个目的地子目录都配有
test_template_*.py(如discord/test_template_discord.py),继承BaseHogFunctionTemplateTest,用 mock 的fetch/print/postHogCapture断言 Hog 代码对给定inputs与event/person的行为;浏览器端模板则使用 BaseSiteDestinationFunctionTest,它会把模板同步进库、通过 API 创建 HogFunction、把 JavaScript 转译成 IIFE 后在 STPyV8 的 JS 上下文里注入posthog对象执行processEvent,最终断言捕获到的track调用。
小结
PostHog CDP 的 Hog Function 模板体系可以概括为三个关键点:模板是 Git 中管理的 ephemeral 代码定义(HogFunctionTemplateDC实例,按id注册、按type/status分类),模板与用户函数解耦(数据库按template_id + sha做内容版本化,模板更新只提示失步、不覆写用户配置),模板编写遵循“最小代码 + inputs_schema 承载一切可变数据 + 过滤交给源过滤 UI”的纪律(由 说明文档 明确提出并由全量校验测试强制兜底)。新增一个目的地接入时,按posthog/cdp/templates/discord/这样的目录结构放置template_<name>.py与test_template_<name>.py,并在HOG_FUNCTION_TEMPLATES中注册,即可进入sync_hog_function_templates同步链路与现有测试体系。
【免费下载链接】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),仅供参考