可观测性
配置分析、崩溃上报、日志和 tracking 权限。
可观测性由 PostHog analytics、Sentry crash reporting、一层轻量 logger,以及 iOS App Tracking Transparency 权限 helper 组成。每个服务在缺少 key 时都会 no-op。
提供什么
| Surface | 状态 | 说明 |
|---|---|---|
AnalyticsProvider | Integrated | 封装 PostHog;缺少 key 时禁用 capture;手动跟踪 screen;识别已登录用户 |
track() / useTrack() | Integrated | 带类型的 product analytics events |
| Feature flags | Integrated | useFlag() 和 useFlagPayload() 包装 PostHog feature flag hooks |
lib/sentry.ts | Integrated | 初始化 Sentry、navigation tracing、Expo update tags、sample rates,并处理 Expo Go 降级 |
logger | Integrated | 开发环境打印 console;生产环境写 Sentry breadcrumbs 并捕获错误 |
| ATT helper | Native-only | iOS 使用 expo-tracking-transparency;web 返回 granted 的 no-op fallback |
PostHog
配置公开 PostHog keys:
EXPO_PUBLIC_POSTHOG_API_KEY=phc_xxxxxxxxxxxxx
EXPO_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
EXPO_PUBLIC_POSTHOG_DISABLED=false
EXPO_PUBLIC_POSTHOG_DEBUG=false只有存在 key 且 EXPO_PUBLIC_POSTHOG_DISABLED !== "true" 时,
isAnalyticsEnabled() 才返回 true。禁用时,track() 只记录 debug skip,不会 capture。
Provider 选项比较克制:
autocapture screens: false
autocapture touches: false
session replay: false
lifecycle events: true
feature flags: preloadedScreenTracker 通过 posthog.screen(pathname, { path, params }) 发送路由变化。AnalyticsIdentity 用 Supabase UUID identify 已登录用户,并在退出登录时 reset PostHog。
类型化 events
AnalyticsEvents 当前定义了:
| Event | Properties |
|---|---|
screen viewed | path,可选 params |
onboarding completed | slideCount |
paywall viewed | 可选 source |
referral invite shared | 可选 channel |
purchase restored | 可选 source |
purchase completed | tier、period |
React 组件外使用 track(),组件内使用 useTrack()。新增 event 时先扩展
AnalyticsEvents,这样 payload 会保持类型安全。
Consent 与 ATT
Legal ConsentGate 会阻止用户继续使用 app,直到接受当前 legal version。但当前 analytics 实现不会等这个 consent 再挂载 PostHog。如果你需要严格的 analytics consent,请在发布前让 PostHog enablement 依赖 consent 值。
ATT 是另一件事。app/permissions.tsx 通过 lib/tracking-permissions.ts 请求 tracking
permission;web 端的 tracking-permissions.web.ts 返回 granted 的 no-op 结果。ATT 权限不能替代你的 analytics consent policy 或隐私披露。
Sentry
配置公开 DSN 和 sample rates:
EXPO_PUBLIC_SENTRY_DSN=https://example@sentry.io/123
EXPO_PUBLIC_SENTRY_DEBUG=false
EXPO_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=0.2
EXPO_PUBLIC_SENTRY_PROFILES_SAMPLE_RATE=0服务端/CI 值要保持私密:
SENTRY_ORG=
SENTRY_PROJECT=
SENTRY_URL=https://sentry.io/
SENTRY_AUTH_TOKEN=lib/sentry.ts 在 module load 时调用 Sentry.init()。runtime Sentry environment
来自 Constants.expoConfig.extra.appVariant:
development -> development
preview -> preview
production -> production它注册 reactNavigationIntegration,在 Expo Go 中禁用 native frame tracking,在生产环境默认 trace sample rate 为 0.2、非生产为 1,并在可用时标记 Expo update ID、是否 embedded update、update group 和 debug URL。
app/_layout.tsx 注册 navigation container,并导出:
export default Sentry.wrap(RootLayout);Sourcemaps
脚本是:
pnpm sentry:upload-sourcemaps它运行 sentry-expo-upload-sourcemaps dist。把 SENTRY_AUTH_TOKEN 放在 EAS secret
或 CI secret 中,不要放进 EXPO_PUBLIC_*。eas.json 会按 profile 设置
SENTRY_ENVIRONMENT,但当前 runtime 代码使用 Expo config 中的 variant 作为
Sentry.init({ environment })。
Logger
生产代码中优先使用 logger,而不是直接写 console:
logger.info("rating.prompt_requested", { reason });
logger.warn("notifications.channels.register_failed", { error });
logger.error("profile.save_failed", error, { userId });开发环境中它会打印到 console。生产环境中它会添加 Sentry breadcrumb,并捕获传给
logger.error() 的 Error 实例。
隐私表单
商店提交前,要按你的实际使用情况填写:
| 服务 | 常见披露范围 |
|---|---|
| PostHog | Analytics、diagnostics、产品交互事件、identify 后的账号标识 |
| Sentry | 崩溃诊断、性能 traces、breadcrumbs、设备和 app metadata |
| RevenueCat | 购买历史和订阅状态 |
| Supabase | 账号、profile、app 数据、上传文件 |
不使用的服务可以留空 key,或设置明确的 kill switch。App Privacy 和 Data Safety 表单要和你实际发布的 build 保持一致。
