会话回放类工具在前端监控体系里的地位越来越重要,它能把用户在页面上的每一次点击、输入、滚动还原成一段可播放的视频,配合控制台日志和网络请求记录,定位问题效率远高于纯日志方案。我们团队最初在React项目中使用LogRocket,后来因为检索维度和数据保留策略的限制,决定迁移到FullStory。这篇文章记录了整个迁移过程的关键决策点和具体实现,供有同样需求的开发者参考。

一、为什么从LogRocket换到FullStory
LogRocket和FullStory的核心思路一致:通过 MutationObserver 捕获DOM变化,配合输入事件的序列化,在服务端重建页面快照流。但两者在实现细节上差别不小。LogRocket采用按会话切片的采集模型,每个会话有明确的生命周期边界;FullStory则采用更细粒度的事件流模型,支持跨会话的用户行为串联,这意味着你可以看到一个用户在多次访问中的完整行为路径。
检索能力是促使我们迁移的直接原因。FullStory的原生搜索语法支持按点击文本、页面元素、错误信息等维度组合过滤,比如查找“所有在结算按钮上点击后出现报错的会话”,这种查询在LogRocket里需要自己拼装日志条件,而在FullStory中是一条内置的搜索语句。另外,FullStory的“rage click”(愤怒点击)和“dead click”(无效点击)自动检测能力,对我们分析交互死角帮助很大,这些在LogRocket中需要额外配置才能近似实现。
性能开销方面,两者相差不大,但FullStory的采样配置更灵活。它支持按用户属性动态调整采样率,比如付费用户全量录制、普通用户10%采样,这种按价值分层采样的能力对控制成本很实用。LogRocket当时的采样粒度只能到项目级别,对我们这种用户量分层明显的业务不够友好。
二、FullStory在React中的接入步骤
FullStory提供了官方的浏览器SDK,接入React项目本身不复杂,但有几个配置项值得仔细处理。先安装依赖:
npm install @fullstory/browser --save
然后在应用入口初始化。这里要注意 orgId 必须从环境变量注入,不要硬编码到代码仓库里:
import * as FullStory from '@fullstory/browser';
FullStory.init({ orgId: import.meta.env.VITE_FS_ORG_ID });初始化的时机有讲究。如果放在React组件的 useEffect 里,首次渲染的内容可能不会被完整捕获;建议直接放在应用入口文件顶层,保证在React挂载之前SDK就已经开始监听DOM变化。如果项目用了StrictMode,开发环境下会遇到双重初始化警告,FullStory的 init 方法内部有幂等保护,但为了日志干净,可以加一个全局标记位判断。
接着处理路由变化的记录。FullStory默认通过URL变更识别页面切换,但React项目大量使用前端路由,纯SPA场景下URL变化事件能被正常捕获,但如果你的项目用了HashRouter,需要确认路径格式是否符合预期。可以在路由钩子里主动记录页面名:
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import * as FullStory from '@fullstory/browser';
function RouteTracker() {
const location = useLocation();
useEffect(() => {
FullStory.event('Page Viewed', {
path_str: location.pathname,
search_str: location.search,
});
}, [location]);
return null;
}把 <RouteTracker /> 挂在路由容器内,每次路由切换都会产生一条结构化事件,后续在FullStory后台可以按 path_str 维度直接检索,比单纯依赖URL灵活得多。
三、状态捕获与隐私脱敏配置
迁移过程中最容易被忽视的是Redux状态的捕获。LogRocket提供了官方的Redux中间件,能自动把每次dispatch的action录制下来;FullStory没有等价的官方中间件,需要自己封装。做法很简单,写一个自定义中间件,把关键action转换成FullStory事件:
const fsReduxMiddleware = (store) => (next) => (action) => {
// 过滤掉高频action,避免录制数据爆炸
if (!action.type.startsWith('@@')) {
FullStory.event('Redux Action', {
type_str: action.type,
});
}
return next(action);
};注意这里只上报action类型,不上报payload。payload里经常包含用户输入的敏感信息,直接录制会带来合规风险。这也引出隐私脱敏的话题:FullStory默认会脱敏 input 和 textarea 的输入值,但密码框之外的字段如果包含手机号、身份证号,需要额外处理。推荐的方式是给敏感元素加上 data-fullstory-consent 相关属性,或者使用 FS.consent API在用户层面控制录制权限:
// 针对特定元素屏蔽录制
// 在JSX中给敏感容器加属性:
// <div data-fullstory-recording="false">...</div>
// 用户拒绝授权后停止录制
if (!userConsent) {
FullStory.consent(false);
}另外别忘了CSS层面。FullStory会通过class匹配做元素屏蔽,如果你的项目用了CSS Modules,生成的hash类名在每次构建后都会变化,导致基于类名的脱敏规则失效。解决办法是给敏感元素保留一个稳定的原生属性或自定义data属性作为脱敏锚点,这也是迁移后排查“为什么脱敏没生效”时最常见的答案。
四、迁移切换与数据连续性保障
切换监控工具最大的顾虑是数据断层。我们采取的策略是双轨运行两周:新的FullStory全量接入,旧的LogRocket通过灰度开关保留在部分流量上,对比两边的会话数量和错误捕获率,确认FullStory没有漏录关键会话后,再将LogRocket的采样率逐步降到零。双轨期间要留意两者同时工作时的性能影响,虽然实测叠加开销在3毫秒以内的主线程占用,但低端机型上的表现建议单独验证一轮。
回滚预案也要提前准备好。把SDK初始化封装在一个统一的监控入口模块里,通过远程配置决定加载哪套SDK,一旦线上出现异常,改一个配置项就能切回LogRocket,不需要发版。监控工具本身也需要被监控,这一点经常被团队忽略——我们为两套SDK都加了加载失败的上报,避免出现“监控挂了但没人知道”的尴尬局面。
最后是成本核算。FullStory按会话量计费,迁移前务必评估真实的月度会话数,并结合采样策略压一压量级。按用户价值分层采样是个好起点:核心用户全录、长尾用户低采样,既保证关键问题的可追溯性,又能把账单控制在预算内。整体来看,这次迁移的工程量大概三天,其中一半时间花在隐私合规和状态捕获适配上,如果你们的业务对这些能力要求不高,接入本身可能一天就能完成。
React会话回放LogRocket迁移FullStory集成修改时间:2026-09-04 11:33:24