Files
vf_react/src/lib/posthog.js
zdl e70999afa5 feat:1️⃣ 增强 performanceMonitor.ts
-  新增 measure(name, startMark, endMark) 方法(支持命名测量)
  -  新增 getMarks() - 获取所有性能标记
  -  新增 getMeasures() - 获取所有测量结果
  -  新增 getReport() - 返回完整 JSON 报告
  -  新增 exportJSON() - 导出 JSON 文件
  -  新增 reportToPostHog() - 上报到 PostHog
  -  新增全局 API window.__PERFORMANCE__(仅开发环境)
  -  彩色控制台使用说明

  2️⃣ 添加 PostHog 性能上报

  -  在 posthog.js 中新增 reportPerformanceMetrics() 函数
  -  上报所有关键性能指标(网络、渲染、React)
  -  自动计算性能评分(0-100)
  -  包含浏览器和设备信息
2025-11-25 17:04:10 +08:00

398 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/lib/posthog.js
import posthog from 'posthog-js';
// 初始化状态管理(防止重复初始化)
let isInitializing = false;
let isInitialized = false;
/**
* Initialize PostHog SDK
* Should be called once when the app starts
*/
export const initPostHog = () => {
// 开发环境禁用 PostHog减少日志噪音仅生产环境启用
if (process.env.NODE_ENV === 'development') {
return;
}
// 防止重复初始化
if (isInitializing || isInitialized) {
console.log('📊 PostHog 已初始化或正在初始化中,跳过重复调用');
return;
}
// Only run in browser environment
if (typeof window === 'undefined') return;
const apiKey = process.env.REACT_APP_POSTHOG_KEY;
const apiHost = process.env.REACT_APP_POSTHOG_HOST || 'https://app.posthog.com';
if (!apiKey) {
console.warn('⚠️ PostHog API key not found. Analytics will be disabled.');
return;
}
isInitializing = true;
try {
posthog.init(apiKey, {
api_host: apiHost,
// 📄 页面浏览追踪
capture_pageview: true, // 自动捕获页面浏览事件
capture_pageleave: true, // 自动捕获用户离开页面事件
// 📹 会话录制配置Session Recording
session_recording: {
enabled: process.env.REACT_APP_ENABLE_SESSION_RECORDING === 'true',
// 🔒 隐私保护:遮蔽敏感输入字段(录制时会自动打码)
maskInputOptions: {
password: true, // 遮蔽密码输入框
email: true, // 遮蔽邮箱输入框
phone: true, // 遮蔽手机号输入框
'data-sensitive': true, // 遮蔽带有 data-sensitive 属性的字段(可在 HTML 中自定义)
},
// 📊 录制 Canvas 画布内容(用于记录图表、图形等可视化内容)
recordCanvas: true,
// 🌐 网络请求数据捕获(用于调试 API 问题)
networkPayloadCapture: {
recordHeaders: true, // 捕获请求头
recordBody: true, // 捕获请求体
// 🚫 敏感接口黑名单(不记录以下接口的数据)
urlBlocklist: [
'/api/auth/session', // 会话接口
'/api/auth/login', // 登录接口
'/api/auth/register', // 注册接口
'/api/payment', // 支付接口
],
},
},
// ⚡ 性能优化:批量发送事件
batch_size: 10, // 每 10 个事件发送一次
batch_interval_ms: 3000, // 或每 3 秒发送一次(两个条件满足其一即发送)
// 🔐 隐私设置
respect_dnt: true, // 尊重浏览器的"禁止追踪"Do Not Track设置
persistence: 'localStorage+cookie', // 同时使用 localStorage 和 Cookie 存储(提高可靠性)
// 🚩 功能开关Feature Flags- 用于 A/B 测试和灰度发布
bootstrap: {
featureFlags: {}, // 初始功能开关配置(可从服务端动态加载)
},
// 🖱️ 自动捕获设置Autocapture
autocapture: {
// 自动捕获用户交互事件(点击、提交、修改等)
dom_event_allowlist: ['click', 'submit', 'change'],
// 捕获额外的元素属性
capture_copied_text: false, // 不捕获用户复制的文本(隐私保护)
},
});
isInitialized = true;
} catch (error) {
// 忽略 AbortError通常由热重载或快速导航引起
if (error.name === 'AbortError') {
return;
}
} finally {
isInitializing = false;
}
};
/**
* Get PostHog instance
* @returns {object} PostHog instance
*/
export const getPostHog = () => {
return posthog;
};
/**
* Identify user with PostHog
* Call this after successful login/registration
*
* @param {string} userId - Unique user identifier
* @param {object} userProperties - User properties (email, name, subscription_tier, etc.)
*/
export const identifyUser = (userId, userProperties = {}) => {
if (!userId) {
console.warn('⚠️ Cannot identify user: userId is required');
return;
}
try {
posthog.identify(userId, {
email: userProperties.email,
username: userProperties.username,
subscription_tier: userProperties.subscription_tier || 'free',
role: userProperties.role,
registration_date: userProperties.registration_date,
last_login: new Date().toISOString(),
...userProperties,
});
} catch (error) {
console.error('❌ User identification failed:', error);
}
};
/**
* Update user properties
* Use this to update user attributes without re-identifying
*
* @param {object} properties - Properties to update
*/
export const setUserProperties = (properties) => {
try {
posthog.people.set(properties);
} catch (error) {
console.error('❌ Failed to update user properties:', error);
}
};
/**
* Track custom event
*
* @param {string} eventName - Name of the event
* @param {object} properties - Event properties
*/
export const trackEvent = (eventName, properties = {}) => {
try {
posthog.capture(eventName, {
...properties,
timestamp: new Date().toISOString(),
});
} catch (error) {
console.error('❌ Event tracking failed:', error);
}
};
/**
* 异步追踪事件(不阻塞主线程)
* 使用 requestIdleCallback 在浏览器空闲时发送事件
*
* @param {string} eventName - 事件名称
* @param {object} properties - 事件属性
*/
export const trackEventAsync = (eventName, properties = {}) => {
// 浏览器支持 requestIdleCallback 时使用(推荐)
if (typeof requestIdleCallback !== 'undefined') {
requestIdleCallback(
() => {
trackEvent(eventName, properties);
},
{ timeout: 2000 } // 最多延迟 2 秒(防止永远不执行)
);
} else {
// 降级方案:使用 setTimeout兼容性更好
setTimeout(() => {
trackEvent(eventName, properties);
}, 0);
}
};
/**
* Track page view
*
* @param {string} pagePath - Current page path
* @param {object} properties - Additional properties
*/
export const trackPageView = (pagePath, properties = {}) => {
try {
posthog.capture('$pageview', {
$current_url: window.location.href,
page_path: pagePath,
page_title: document.title,
referrer: document.referrer,
...properties,
});
} catch (error) {
console.error('❌ Page view tracking failed:', error);
}
};
/**
* Reset user session
* Call this on logout
*/
export const resetUser = () => {
try {
posthog.reset();
} catch (error) {
console.error('❌ Session reset failed:', error);
}
};
/**
* User opt-out from tracking
*/
export const optOut = () => {
try {
posthog.opt_out_capturing();
} catch (error) {
console.error('❌ Opt-out failed:', error);
}
};
/**
* User opt-in to tracking
*/
export const optIn = () => {
try {
posthog.opt_in_capturing();
} catch (error) {
console.error('❌ Opt-in failed:', error);
}
};
/**
* Check if user has opted out
* @returns {boolean}
*/
export const hasOptedOut = () => {
try {
return posthog.has_opted_out_capturing();
} catch (error) {
console.error('❌ Failed to check opt-out status:', error);
return false;
}
};
/**
* Get feature flag value
* @param {string} flagKey - Feature flag key
* @param {any} defaultValue - Default value if flag not found
* @returns {any} Feature flag value
*/
export const getFeatureFlag = (flagKey, defaultValue = false) => {
try {
return posthog.getFeatureFlag(flagKey) || defaultValue;
} catch (error) {
console.error('❌ Failed to get feature flag:', error);
return defaultValue;
}
};
/**
* Check if feature flag is enabled
* @param {string} flagKey - Feature flag key
* @returns {boolean}
*/
export const isFeatureEnabled = (flagKey) => {
try {
return posthog.isFeatureEnabled(flagKey);
} catch (error) {
console.error('❌ Failed to check feature flag:', error);
return false;
}
};
/**
* Report performance metrics to PostHog
* @param {object} metrics - Performance metrics object
*/
export const reportPerformanceMetrics = (metrics) => {
// 仅在生产环境上报
if (process.env.NODE_ENV !== 'production') {
console.log('📊 [开发环境] 性能指标(未上报到 PostHog:', metrics);
return;
}
try {
// 获取浏览器和设备信息
const browserInfo = {
userAgent: navigator.userAgent,
viewport: `${window.innerWidth}x${window.innerHeight}`,
connection: navigator.connection?.effectiveType || 'unknown',
deviceMemory: navigator.deviceMemory || 'unknown',
hardwareConcurrency: navigator.hardwareConcurrency || 'unknown',
};
// 上报性能指标
posthog.capture('Performance Metrics', {
// 网络指标
dns_ms: metrics.dns,
tcp_ms: metrics.tcp,
ttfb_ms: metrics.ttfb,
dom_load_ms: metrics.domLoad,
resource_load_ms: metrics.resourceLoad,
// 渲染指标
fp_ms: metrics.fp,
fcp_ms: metrics.fcp,
lcp_ms: metrics.lcp,
// React 指标
react_init_ms: metrics.reactInit,
auth_check_ms: metrics.authCheck,
homepage_render_ms: metrics.homepageRender,
// 总计
total_white_screen_ms: metrics.totalWhiteScreen,
// 性能评分
performance_score: calculatePerformanceScore(metrics),
// 浏览器和设备信息
...browserInfo,
// 时间戳
timestamp: new Date().toISOString(),
});
console.log('✅ 性能指标已上报到 PostHog');
} catch (error) {
console.error('❌ PostHog 性能指标上报失败:', error);
}
};
/**
* Calculate overall performance score (0-100)
* @param {object} metrics - Performance metrics
* @returns {number} Score from 0 to 100
*/
const calculatePerformanceScore = (metrics) => {
let score = 100;
// 白屏时间评分(权重 40%
if (metrics.totalWhiteScreen) {
if (metrics.totalWhiteScreen > 3000) score -= 40;
else if (metrics.totalWhiteScreen > 2000) score -= 20;
else if (metrics.totalWhiteScreen > 1500) score -= 10;
}
// TTFB 评分(权重 20%
if (metrics.ttfb) {
if (metrics.ttfb > 1000) score -= 20;
else if (metrics.ttfb > 500) score -= 10;
}
// LCP 评分(权重 20%
if (metrics.lcp) {
if (metrics.lcp > 4000) score -= 20;
else if (metrics.lcp > 2500) score -= 10;
}
// FCP 评分(权重 10%
if (metrics.fcp) {
if (metrics.fcp > 3000) score -= 10;
else if (metrics.fcp > 1800) score -= 5;
}
// 认证检查评分(权重 10%
if (metrics.authCheck) {
if (metrics.authCheck > 500) score -= 10;
else if (metrics.authCheck > 300) score -= 5;
}
return Math.max(0, Math.min(100, score));
};
export default posthog;