Ever Gauzy 中集成 PostHog:使用 @gauzy/plugin-posthog-ui 实现产品分析与埋点追踪
Ever Gauzy 中集成 PostHog:使用 @gauzy/plugin-posthog-ui 实现产品分析与埋点追踪
导读
本文以 packages/plugins/posthog-ui/README.md 为核心骨架,系统讲解 Ever Gauzy 中面向 Angular 应用的产品分析插件 @gauzy/plugin-posthog-ui:如何完成安装与初始化配置、如何实现事件/页面浏览/表单/错误/HTTP 请求等多维度追踪、如何使用指令在模板中零成本埋点,并深入源码剖析其底层实现原理。读完本文,你将掌握在 Ever Gauzy(或任意 Angular 21+ 应用)中接入 PostHog 的完整实战方案,包括配置项的含义、服务 API 的选用策略,以及测试与发布该插件库的方法。
插件概述:把 PostHog 的开放产品分析能力装进 Angular
Ever Gauzy 是一套开源的企业经营管理平台(ERP/CRM/HRM/ATS/PM),前端基于 Angular 构建。PostHog 则是开源的产品分析平台,提供事件追踪、会话录制、功能开关(Feature Flags)、漏斗分析、错误监控等能力。@gauzy/plugin-posthog-ui 正是将两者桥接的 UI 插件——它封装了 posthog-js SDK,以 Angular 惯用的模块、服务、指令、拦截器形式,让 Gauzy 应用能够实时采集用户行为、功能使用情况与应用事件。
从仓库实际使用情况看,该插件已经在 Gauzy 主应用中被启用:apps/gauzy/src/app/app.module.ts 中通过 PostHogModule.forRoot(...) 注入,且 apps/gauzy/src/app/pages/settings/monitoring/monitoring.component.ts 提供了 posthogEnabled、posthogKey、posthogHost、posthogFlushInterval 等全局/租户级设置项,说明它在 Gauzy 中既可用于开发环境调试,也可在生产环境按租户配置启用。
插件核心能力(源自其 package.json 与 posthog.interface.ts)包括:
- 事件追踪:采集自定义事件与用户操作;
- 页面浏览分析:基于 Angular Router 的自动页面浏览追踪;
- 用户识别:跨会话识别与追踪个体用户;
- 会话录制:录制用户会话供回放与分析;
- 功能开关:在 Angular 应用中实现 Feature Flags 与 A/B 测试;
- 错误监控:追踪前端错误与异常;
- HTTP 请求追踪:自动采集 API 调用及耗时指标;
- 表单追踪:追踪表单提交与字段交互;
- 分组分析(Group Analytics):将用户归属到公司、团队或组织;
- 超级属性(Super Properties):设置跨事件持久化的属性;
- SSR 兼容:可在服务端渲染场景下安全使用。
安装
使用你偏好的包管理器安装插件及其核心依赖:
npm install @gauzy/plugin-posthog-ui posthog-js
# 或
yarn add @gauzy/plugin-posthog-ui posthog-js
根据 package.json 的声明,该插件将 posthog-js(当前锁定 ^1.236.2)作为直接依赖,@angular/common 与 @angular/core(21.0.7)作为 peerDependencies,rxjs 与 tslib 为运行依赖。因此你的项目应运行在 Angular 21 与 Node.js >= 22 环境(engines 声明),并使用 Yarn >= 1.22 或 npm 进行依赖管理。
配置
基础配置:PostHogModule.forRoot
在主应用模块中导入 PostHogModule 并调用 forRoot 传入配置:
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { PostHogModule } from '@gauzy/plugin-posthog-ui';
import { environment } from '../environments/environment';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
PostHogModule.forRoot({
apiKey: environment.posthogApiKey,
apiHost: environment.posthogHost || 'https://app.posthog.com',
// 可选配置
captureHttpEvents: true,
options: {
capture_pageview: false, // 页面浏览由 Router 接管处理
persistence: 'localStorage',
session_recording: {
enabled: environment.production
}
}
})
],
bootstrap: [AppComponent]
})
export class AppModule {}
配置对象对应源码中的 PostHogModuleConfig 接口(见 posthog.interface.ts),除 apiKey 必填外,还支持以下扩展配置:
| 配置项 | 类型 | 说明 |
|---|---|---|
apiKey |
string |
必填。PostHog 项目 API Key |
options |
Partial<PostHogConfig> |
透传给 posthog-js 的原生配置(api_host、persistence、session_recording、bootstrap 等) |
debug |
boolean |
是否开启调试模式,注入到 POSTHOG_DEBUG_MODE token,默认 false |
routeTracking |
{ excluded?, included? } |
路由追踪白/黑名单:提供 excluded 时匹配路由不追踪;提供 included 时仅匹配路由被追踪;两者同时提供时 excluded 优先 |
httpTracking |
{ captureResponseBodies?, excludeUrls?, additionalProperties? } |
HTTP 请求追踪配置,见下文 |
从 posthog.module.ts 的实现可以看到 forRoot 实际完成的初始化工作:
- 将
PostHogService、PostHogServiceManager注册为 provider; - 把配置注入
POSTHOG_CONFIGtoken,把config.debug || false注入POSTHOG_DEBUG_MODEtoken; - 通过
provideAppInitializer在应用启动阶段调用 posthog-init.factory.ts 完成初始化; - 当
options.capture_exceptions !== false时,条件性地注册PostHogInterceptor到HTTP_INTERCEPTORS(多 provider),实现 HTTP 错误/请求自动追踪。
需要注意的是,初始化工厂对“占位 Key”做了防护:当 apiKey 缺失或等于 'DOCKER_POSTHOG_KEY'(Docker 部署未配置环境变量时的占位值)时直接返回,初始化失败时也会捕获异常并 resolve,从而保证应用启动不会被 PostHog 阻塞——这与 apps/gauzy/src/app/app.module.ts 中对 DOCKER_POSTHOG_KEY 的告警逻辑一致。
环境配置
在环境文件中添加以下变量:
// environments/environment.ts
export const environment = {
production: false,
posthogApiKey: 'YOUR_POSTHOG_API_KEY',
posthogHost: 'https://app.posthog.com' // 或你的自托管实例地址
};
Gauzy 主应用中对应的环境变量为 POSTHOG_ENABLED、POSTHOG_KEY、POSTHOG_HOST(见 app.module.ts)。posthog-js 的原生配置里 api_host 默认值为 https://app.posthog.com,若使用自托管 PostHog 实例,请将其替换为你的实例地址(源码见 posthog.service.ts)。
使用指南
服务选择:PostHogServiceManager 与 PostHogService
插件提供两个服务层:
PostHogServiceManager(高层 API,推荐应用代码使用):面向组件封装,自动携带初始化状态检查、路由追踪与配置驱动行为;PostHogService(底层 API):直接封装posthog-js的全部能力(captureEvent、capturePageview、identify、alias、group、Feature Flags、会话录制、超级属性等),并负责 SSR 安全(仅在浏览器平台初始化)与全局错误监听。
两个服务都以 providedIn: 'root' 注册(见 posthog-manager.service.ts 与 posthog.service.ts),可直接构造函数注入。
基础事件追踪
在组件中注入高层服务并调用 trackEvent:
import { Component } from '@angular/core';
import { PostHogServiceManager } from '@gauzy/plugin-posthog-ui';
@Component({
selector: 'app-example',
template: `<button (click)="trackButtonClick()">Click Me</button>`
})
export class ExampleComponent {
constructor(private posthogManager: PostHogServiceManager) {}
trackButtonClick() {
this.posthogManager.trackEvent('button_clicked', {
buttonName: 'example_button',
section: 'homepage'
});
}
}
底层实现上,trackEvent 先做初始化检查(isInitialized()),再转发给 posthogService.captureEvent,后者调用 posthog.capture(eventName, properties, options),可选 sendInstantly 参数控制是否立即发送(源码见 posthog.service.ts)。
指令式事件追踪(模板零成本埋点)
插件提供了 Angular 独立指令(standalone directive),可在模板中直接声明式埋点。[phTrack] 指令支持 click、mouseenter、focus 三种事件类型,也支持在组件初始化时捕获:
<!-- 基础事件追踪 -->
<button [phTrack]="'signup_button_clicked'" [phProperties]="{ location: 'header', variant: 'primary' }">Sign Up</button>
<!-- 高级配置 -->
<button
[phTrack]="'button_clicked'"
[phProperties]="{ buttonName: 'submit' }"
[phOnInit]="true"
[phEventType]="'click'"
[phStopPropagation]="true"
>
Submit
</button>
指令实现见 posthog-track.directive.ts:@HostListener 监听 click/mouseenter/focus,仅当 phEventType 与触发事件匹配时才上报;phOnInit 为 true 时在 ngOnInit 阶段立即上报一次;phStopPropagation 控制是否阻止事件冒泡。由于该指令已由 PostHogModule 统一 export,只要导入模块即可直接使用。
表单追踪
phFormTrack 指令自动采集表单提交与字段交互。以下示例追踪 signup_form 表单,启用字段级交互追踪,并排除敏感字段、开启值脱敏:
<form
phFormTrack
[phFormName]="'signup_form'"
[phTrackFields]="true"
[phExcludeFields]="['password', 'credit-card']"
[phSanitizeValues]="true"
(ngSubmit)="onSubmit()"
>
<input name="email" type="email" />
<input name="password" type="password" />
<button type="submit">Submit</button>
</form>
表单指令的能力远不止 README 示例,源码 posthog-form-track.directive.ts 揭示了完整输入参数与安全机制:
| 输入 | 默认值 | 说明 |
|---|---|---|
phFormName |
'form' |
表单名称标识 |
phTrackFields |
false |
是否追踪单个字段的交互 |
phExcludeFields |
['password', 'secret', 'token', 'credit', 'card'] |
排除字段(支持字符串包含匹配与 RegExp) |
phSanitizeValues |
true |
是否对字段值脱敏(截断超过 100 字符的值) |
phTrackFocus / phTrackBlur / phTrackChange |
false / false / true |
是否追踪焦点、失焦、变更事件 |
phIncludeMetadata |
true |
是否附带 field_id、field_required、field_max_length 等元数据 |
phFieldPrefix |
'' |
字段交互事件名的前缀,如 myform_field_change |
该指令自动上报三类事件:组件初始化时的 form_viewed(携带表单名、id、action 与字段清单)、提交时的 form_submitted(携带 form_valid 校验状态与全部字段数据)、以及 field_focus/field_blur/field_change 字段交互事件。安全性方面做到了三层防护:shouldExcludeField 排除敏感字段名、shouldRedactFieldValue 对 password 类型及 password/secret/token/auth/key/credit/card/cvv/ssn/social 等敏感模式强制脱敏为 [REDACTED]、sanitizeFieldValue 截断超长值。指令通过 Renderer2.listen 动态绑定字段监听器,并在 ngOnDestroy 中统一清理,避免内存泄漏。
用户识别
用户登录成功后调用 identifyUser:
login(user) {
// 认证成功后
this.posthogManager.identifyUser(user.id, {
email: user.email,
name: user.name,
plan: user.subscriptionPlan,
signupDate: user.createdAt
});
}
identifyUser 底层转发到 posthog.identify(distinctId, userProperties)(见 posthog-manager.service.ts)。若应用启动配置了 options.bootstrap.distinctID,初始化阶段会自动调用 identifyUser 完成身份绑定;注销或切换账号时可调用 resetUser() 清空当前身份。
功能开关(Feature Flags)
import { Component, OnInit } from '@angular/core';
import { PostHogServiceManager } from '@gauzy/plugin-posthog-ui';
@Component({
selector: 'app-feature',
template: `
@if (newFeatureEnabled) {
<h2>New Feature</h2>
<!-- 功能内容 -->
}
`
})
export class FeatureComponent implements OnInit {
newFeatureEnabled = false;
constructor(private posthogManager: PostHogServiceManager) {}
ngOnInit() {
this.newFeatureEnabled = this.posthogManager.isFeatureEnabled('new-feature');
}
}
高层服务还提供 getFeatureFlagValue<T>(flagKey, defaultValue) 获取带类型的开关值、reloadFeatureFlags() 手动重载。值得注意的细节:在 initialize 的 setupConfigBasedBehaviors 中(见 posthog-manager.service.ts),当 options.advanced_disable_feature_flags !== true 且未禁用首载时,插件会自动调用 reloadFeatureFlags() 预拉取开关,因此组件中调用 isFeatureEnabled 时开关通常已就绪。底层 PostHogService 还额外暴露了 getFeatureFlags(keys: string[])(批量取值)、setFeatureFlagOverride(key, value)(本地覆盖,常用于测试,对应 posthog.featureFlags.override)。
分组分析(Group Analytics)
将用户与组织/团队关联:
// 当用户加入或切换团队时
this.posthogManager.setGroup('company', 'company-id-123', {
name: 'Acme Corp',
industry: 'Technology',
tier: 'Enterprise'
});
底层对应 posthog.group(groupType, groupKey, groupProperties),适合 Gauzy 这类多租户企业管理平台按公司、团队维度做聚合分析。
错误追踪
插件在浏览器环境下自动为 window 注册 error 与 unhandledrejection 监听器(见 posthog.service.ts),自动捕获全局 JS 异常(附带 source/lineno/colno 定位信息)与未处理的 Promise rejection,统一以 $exception 事件上报。HTTP 错误则由 PostHogInterceptor 自动捕获。也可以手动上报业务错误:
try {
// 某些有风险的操作
} catch (error) {
this.posthogManager.trackError(error, {
context: 'payment_processing',
user_action: 'checkout'
});
}
captureError 会将 error_message、error_type、error_stack 等结构化信息一并上报(见 posthog.service.ts)。若希望完全关闭自动异常捕获,可在 options 中设置 capture_exceptions: false——这同时会阻止拦截器的注册(见 posthog.module.ts)。
HTTP 请求追踪
通过 httpTracking 配置可以控制 API 调用追踪的粒度(posthog.interface.ts):
PostHogModule.forRoot({
apiKey: environment.posthogApiKey,
httpTracking: {
// 是否采集响应体(谨慎!可能含敏感数据),默认 false
captureResponseBodies: false,
// 排除特定路由(支持字符串与 RegExp)
excludeUrls: ['/api/analytics', /\/reports\/\d+/],
// 随 API 事件附加的自定义属性
additionalProperties: { app: 'gauzy', env: 'production' }
}
})
拦截器 posthog.interceptor.ts 的实现要点:
- 对每个被拦截的请求,用
performance.now()记录开始时间,剥离 query 参数后构造事件属性(path、method、url、withCredentials),并合并additionalProperties; - 通过
tap在响应成功时写入status、success(200-399 区间判定)与可选的脱敏responseBody;出错时写入status/success: false/errorMessage/errorType/statusText; - 在
finalize中计算耗时duration(毫秒),统一上报api_request事件; shouldSkipTracking依据excludeUrls决定是否放行(不追踪);sanitizeResponseBody以递归方式(深度上限 10 层)脱敏password/token/secret/key/auth/credit/card等敏感字段,防止响应体泄露隐私。
超级属性(Super Properties)
注册随每个事件一起发送的全局属性:
// 在应用初始化时设置一次
this.posthogManager.register({
app_version: '1.2.3',
theme: 'dark',
locale: 'en-US'
});
注意:README 中的 register 在高层服务 API 表中未单列,但底层 PostHogService.register(properties, days?) 对应 posthog.register(可指定保留天数),另有 registerOnce(仅当属性未设置时写入)、unregister(propertyName)(注销单个超级属性)。此外,在 initialize 阶段,若 options.persistence !== 'memory'(即使用 localStorage 等持久化存储),插件会自动注册 app_version 超级属性(见 posthog-manager.service.ts)。
API 参考
PostHogServiceManager(高层服务)
| 方法 | 描述 |
|---|---|
initialize(config: PostHogModuleConfig) |
以配置初始化 PostHog,返回是否成功 |
trackEvent(eventName: string, properties?: object) |
追踪自定义事件 |
trackPageView(url?: string, properties?: object) |
捕获页面浏览事件 |
identifyUser(userId: string, properties?: object) |
识别用户 |
resetUser() |
重置当前用户身份 |
setGroup(groupType: string, groupKey: string, groupProperties?: object) |
将用户与分组关联 |
isFeatureEnabled(flagKey: string) |
检查功能开关是否开启 |
getFeatureFlagValue<T>(flagKey: string, defaultValue: T) |
获取功能开关值 |
reloadFeatureFlags() |
从 PostHog 重载功能开关 |
setUserProperties(properties: object) |
设置当前用户属性 |
optInTracking() / optOutTracking() |
选择加入/退出追踪 |
trackError(error: Error, properties?: object) |
带上下文追踪错误 |
getPostHogInstance() |
获取原始 PostHog 实例用于高级操作 |
startSessionRecording() |
手动启动会话录制 |
高层服务几乎每个方法都先做 isInitialized() 守卫,未初始化时安全返回,便于在服务尚未就绪或 SSR 环境下调用而不抛错。
PostHogService(底层服务)
| 方法 | 描述 |
|---|---|
initialize(apiKey: string, config?: object) |
初始化 PostHog(仅浏览器平台执行) |
captureEvent(eventName: string, properties?: object, sendInstantly?: boolean) |
追踪自定义事件 |
capturePageview(url?: string, properties?: object) |
捕获页面浏览 |
captureError(error: Error, properties?: object) |
追踪错误或异常 |
identify(distinctId: string, userProperties?: object, callback?: () => void) |
识别用户 |
alias(alias: string, distinctId?: string, callback?: () => void) |
为用户创建别名 |
group(groupType: string, groupKey: string, groupProperties?: object) |
将用户与分组关联 |
reset() |
重置当前用户 |
isFeatureEnabled(key: string, options: { send_event: boolean }) |
检查功能开关 |
getFeatureFlag(key: string, defaultValue?: any) |
获取功能开关值 |
setFeatureFlagOverride(key: string, value: any) |
覆盖功能开关(用于测试) |
reloadFeatureFlags() |
重载功能开关 |
getFeatureFlags(keys: string[]) |
批量获取多个功能开关值 |
startSessionRecording() / stopSessionRecording() |
启动/停止会话录制 |
register(properties: object, days?: number) |
注册超级属性 |
registerOnce(properties: object, defaultValue?: any, days?: number) |
仅当未设置时注册属性 |
unregister(propertyName: string) |
注销超级属性 |
getDistinctId() |
获取当前用户 distinct ID |
optOut() / optIn() / isOptedOut() |
退出/加入追踪及状态查询 |
setPersonProperties(properties: object) |
设置用户(person)属性 |
setPersonPropertiesOnce(properties: object) |
仅当未设置时设置用户属性 |
setPersonPropertiesForFlags(properties: object) |
为功能开关计算设置用户属性 |
setGroupPropertiesForFlags(map: object, reload?: boolean) |
为功能开关计算设置分组属性 |
captureDeadClick(element: Element, properties?: object) |
捕获无效点击($dead_click)事件 |
getInstance() |
获取原始 PostHog 实例 |
指令
| 指令/输入 | 描述 |
|---|---|
[phTrack] |
在用户交互时追踪事件 |
[phProperties] |
随事件发送的属性 |
[phOnInit] |
是否在组件初始化时立即捕获 |
[phEventType] |
监听的事件类型(click、mouseenter、focus),默认 click |
[phStopPropagation] |
是否阻止事件冒泡 |
phFormTrack |
追踪表单提交与字段交互 |
[phFormName] |
被追踪表单的名称标识 |
[phTrackFields] |
是否追踪单个字段交互 |
[phExcludeFields] |
排除的字段 |
[phSanitizeValues] |
是否对字段值脱敏 |
[phTrackFocus] |
是否追踪字段焦点事件 |
[phTrackBlur] |
是否追踪字段失焦事件 |
[phTrackChange] |
是否追踪字段变更事件 |
页面浏览与路由追踪原理
插件默认将 capture_pageview 置为 false(见 posthog.service.ts),页面浏览改由 Router 事件驱动,以获得对 URL 与计时指标的更精细控制。setupRouteTracking 订阅 router.events:在 NavigationStart 记录起始时间,在 NavigationEnd 计算加载耗时并上报 $pageview 事件,附带 load_time_ms(导航耗时)、referrer、route(重定向后的 URL)与 route_id(匹配到的路由 path)等属性(posthog.service.ts)。该逻辑在 initialize 成功后由 PostHogServiceManager 自动调用一次,且通过内部标记 _routeTrackingAttached 保证只绑定一次。若需要通过 routeTracking 配置做路由级过滤(excluded/included),可在模块配置中提供,实现上以 excluded 优先。
测试:forTesting 与单元测试
插件内置测试支持,在测试模块中导入即可获得不执行真实初始化的 mock 环境:
// test.module.ts
import { PostHogModule } from '@gauzy/plugin-posthog-ui';
@NgModule({
imports: [PostHogModule.forTesting()]
})
export class TestModule {}
从 posthog.module.ts 的实现可见,forTesting() 以 apiKey: 'test-key' 与空 loaded 回调提供 POSTHOG_CONFIG,并将 POSTHOG_DEBUG_MODE 置为 true,且不注册 HTTP 拦截器——这样组件测试中注入 PostHogServiceManager 不会触发网络请求。运行插件单元测试(基于 Jest + jest-preset-angular):
yarn nx test plugin-posthog-ui
构建与发布
构建库:
yarn nx build plugin-posthog-ui
构建产物输出到 dist/packages/plugins/posthog-ui。发布到 npm registry:
cd dist/packages/plugins/posthog-ui && npm publish
若需要在开发模式下持续构建,可使用 yarn nx build plugin-posthog-ui --configuration=development --watch(对应 package.json 中的 lib:build、lib:build:prod、lib:watch 脚本)。
在 Ever Gauzy 中的真实落地方式
回到 Gauzy 主应用,PostHog 的启用采用“环境变量 + 运行时设置”双通道(依据 apps/gauzy/src/app/app.module.ts):
- 环境变量
POSTHOG_ENABLED、POSTHOG_KEY、POSTHOG_HOST控制构建期的启用开关与默认连接参数;POSTHOG_KEY为 Docker 占位值DOCKER_POSTHOG_KEY时给出告警且不初始化; - 运行时,在设置页的监控(Monitoring)模块中(见 monitoring.component.ts),管理员可按 Global 与 Tenant 两个层级保存
posthogEnabled、posthogKey、posthogHost、posthogFlushInterval(默认刷新间隔 10000ms),从而实现多租户环境下的按需启停与独立配置。
这一落地模式说明:@gauzy/plugin-posthog-ui 不只是实验性的埋点封装,而是 Gauzy 生产级产品分析链路中的正式组件;你可以依据同样的模式在自己的 Angular 业务应用中接入,把用户行为、漏斗转化、功能开关与前端错误统一收拢到 PostHog 一处分析。
小结
@gauzy/plugin-posthog-ui 将 PostHog 的开放分析能力封装成了 Angular 生态的标准形态:模块级 forRoot 配置、双层级服务 API、声明式追踪指令、表单安全埋点、HTTP 拦截器、SSR 防护与测试支持一应俱全。无论是 Ever Gauzy 平台内按租户启用产品分析,还是在其他 Angular 项目中快速落地事件追踪与 Feature Flags,本文所覆盖的配置项、方法签名与源码实现细节都能直接指导你的实战落地。