
做前端这几年最让我头疼的不是需求写不完也不是组件改不动而是线上出了bug我却像个瞎子一样什么都看不见。用户说页面白屏了我打开自己的浏览器一切正常用户说点了没反应我看代码逻辑八百遍也找不出问题。这种盲人摸象式的排查相信每个前端都经历过。后来我把监控体系从零搭起来从基础的错误上报到会话回放踩了不少坑也攒了不少经验今天就系统聊聊Sentry和LogRocket这套组合拳把看不见变成看得见。这套体系解决的核心问题很简单前端代码跑在用户的浏览器里你无法直接看到现场。无论是报错信息、用户操作路径还是网络请求状态都需要一套工具帮你录下来、传回来、还原现场。Sentry负责把错误收集得干净利落LogRocket负责把用户操作过程回放得纤毫毕现两者结合基本覆盖了日常线上问题排查的绝大多数场景。如果你正在选型、或者已经接入但用不出效果这篇文章应该能帮到你。1. 为什么光有后端日志远远不够1.1 后端的病人和前端的病人不是同一个很多团队一开始的排查思路是这样的前端报错就去看后端日志看接口返回什么。但实际生产环境里绝大多数前端问题恰恰是后端日志里什么都看不出来的。举个例子用户反馈某个页面数据加载不出来后端日志显示接口200且返回正常那问题出在哪可能是前端某个字段的取值场景没覆盖JS抛了TypeError可能是用户网络代理把静态资源拦截了也可能是某个浏览器插件往页面注入了脚本导致样式错乱。这些情况后端一无所知只有用户的浏览器知道发生了什么。这就是前端监控的第一层价值它帮你在用户侧装了一双眼睛。Sentry捕获到的是浏览器运行时抛出的异常、资源加载失败、Promise rejection这些信息带有堆栈、浏览器版本、操作系统、路由地址等上下文远比一句页面打不开管用。1.2 监控体系的三层结构比你想的更必要完整的监控体系我习惯拆成三层错误监控、性能监控、行为回放。错误监控负责知道坏了性能监控负责知道多慢行为回放负责知道怎么坏的。很多人只做了第一层Sentry接上就不管了结果遇到这个bug我复现不了依旧抓瞎就是因为缺了第三层。Sentry本身也有性能监控能力支持自动埋点采集页面加载、接口耗时、前端事务等指标。不过实际用下来性能监控的痛点不在于采集而在于归因——页面慢了两秒你看到耗时数据但不知道用户当时点了什么、页面处于什么状态。LogRocket补充的正是这段过程数据鼠标轨迹、点击事件、控制台输出、网络请求、Redux状态变化像行车记录仪一样把你的应用跑了一遍。两套工具不是替代关系而是互补关系。Sentry告诉你哪里炸了LogRocket告诉你为什么炸。下面我分两章分别讲清楚他们的接入要点和真正的使用姿势。2. Sentry接入比想象中简单但细节决定生死2.1 项目初始化与SDK接入的几个关键参数Sentry的接入流程在官方文档上写得清楚但它默认推荐的配置并不适合所有团队你需要根据自己的场景调整。核心是在初始化时传入dsn、environment、release三个字段import * as Sentry from sentry/react; import { BrowserTracing } from sentry/tracing; Sentry.init({ dsn: https://your-dsnsentry.example.com/1, environment: process.env.NODE_ENV, release: my-app${__APP_VERSION__}, integrations: [new BrowserTracing()], tracesSampleRate: process.env.NODE_ENV production ? 0.1 : 1.0, beforeSend(event) { if (event.request?.url?.includes(localhost)) return null; return event; } });environment用于区分开发、测试、生产release用于把错误关联到具体版本这两项不配Sentry的版本对比、回归发现功能基本废掉。beforeSend是我特别强调的生产环境经常会混入浏览器插件、本地调试等无效错误在这里做一次过滤能有效减少告警噪音。还有一种常用做法是在beforeSend里对event.tags打上业务标记比如用户等级、订单状态排查具体业务问题时能节省大量时间。另外Sentry的React SDK还提供了ErrorBoundary组件可以捕获React组件树中的渲染错误并展示降级UI。不要只依赖全局的window.onerrorReact的渲染错误不会自动冒泡到全局必须单独包一层。不过ErrorBoundary只能捕获渲染阶段和生命周期里的错误事件回调里的错误还是会漏所以两者都要有。2.2 Source Map上传不做等于裸奔线上代码基本都是打包压缩过的报错堆栈全是a.b.c is not a function at e.exports (main.8f3k2d.js:1:12345)这种天书。不配上Source MapSentry给你一个把错误定位到压缩后变量名的堆栈你只能数着冒号后的行列号去猜效率低到让人崩溃。上传Source Map有两种主流方式。一种是使用sentry/webpack-plugin在webpack构建完成后自动上传const SentryWebpackPlugin require(sentry/webpack-plugin); module.exports { devtool: hidden-source-map, plugins: [ new SentryWebpackPlugin({ org: your-org, project: your-project, authToken: process.env.SENTRY_AUTH_TOKEN, release: process.env.RELEASE_VERSION, include: ./dist, ignore: [node_modules] }) ] };这里有两个关键坑。第一devtool必须设置为hidden-source-map不能是source-map或eval-source-map。eval开头的方案会直接内联代码map文件信息不完整不带hidden则会把sourceMappingURL注释留在产物里等于把源码地址暴露给所有用户。第二release必须和SDK初始化时传入的一致否则上传的map文件和错误事件对不上号。我见过很多团队map传了、release没对上排查半天才发现问题出在这里。还有一种兼容性更好的方案是直接用Sentry CLIsentry-cli releases new $RELEASE_VERSION sentry-cli releases set-commits $RELEASE_VERSION --auto sentry-cli releases files $RELEASE_VERSION upload-sourcemaps ./dist --url-prefix ~/static/js注意--url-prefix要跟部署后静态资源的路径前缀对齐比如你的JS文件实际访问地址是https://cdn.example.com/static/js/main.js这里就填~/static/js填错了Sentry照样匹配不上map。2.3 错误分组与告警规则的正确打开方式Sentry默认按异常信息和堆栈指纹对错误进行分组但实际生产环境里经常出现同一个错误被拆成几百个Issue或者完全不相关的错误被合并成一个的情况。我的做法是在beforeSend里用fingerprint显式指定分组规则beforeSend(event, hint) { const error hint.originalException; if (error instanceof TypeError error.message.includes(Cannot read property)) { event.fingerprint [property-error, event.request?.url || unknown]; } return event; }把同一类型的异常归到同一个fingerprint下Issue数量会显著收敛告警列表不再是一堆重复项。告警规则的设置更要谨慎。Sentry默认的首次出现新错误就报警会把你淹没在告警风暴里。我现在用的是三级策略P1级别的错误白屏、支付流程中断、接口大面积5xx立刻邮件企微机器人通知P2级别的错误单接口偶发失败、特定浏览器兼容问题聚合到日报里P3级别的已知噪音直接忽略或扔进低优先级。这里的核心思路是告警要可执行——收到消息的人能立刻采取行动否则这个告警迟早会被无视长此以往团队的告警疲劳会让真正严重的问题被淹掉。3. LogRocket把无法复现变成随时回放3.1 会话回放到底录的是什么很多第一次接触LogRocket的人以为它是个录屏工具靠浏览器截屏来录画面。实际上它走的是DOM快照加增量更新的路线加载页面时记录完整的DOM树之后每次DOM变更只记录差异部分再配合CSS样式快照在播放端重新渲染。好处是数据量小、回放画质清晰、还能查看实时状态而不是死视频。我实测一个普通管理后台页面录制半小时的会话数据大概几MB相比视频录屏动辄几百MB要友好太多。SDK接入也很轻量import LogRocket from logrocket; import setupRedux from logrocket-redux; LogRocket.init(your-app-id, { dom: { textSanitizer: true, inputSanitizer: true }, network: { requestSanitizer: request { if (request.headers?.Authorization) request.headers.Authorization [REDACTED]; return request; }, responseSanitizer: response { if (response.body?.token) response.body.token [REDACTED]; return response; } }, redux: setupRedux() });两个sanitizer我建议必开。生产环境里不可避免会有用户输入手机号、地址、登录token这些数据一旦被录下来就是安全风险。开启textSanitizer和inputSanitizer后页面文本和输入框内容会被脱敏处理网络请求和响应里的敏感字段也要在发送前抹掉。安全红线这一块宁严勿松。3.2 LogRocket的两张王牌状态快照与网络瀑布LogRocket对前端定位问题最有用的功能我觉得是状态快照State和网络请求列表Network。状态快照记录的是应用内存中的数据包括Redux/Vuex store、localStorage、sessionStorage、全局变量你在回放时可以看到任意时间点的完整应用状态。这等于把用户操作时正在处理什么数据这个信息录了下来排查那种只在特定数据组合下才出现的bug简直是直接给了答案。网络请求列表则把每个XHR和Fetch的URL、请求头、请求体、响应体、耗时、状态码全部按时间线列出来配合时间轴上的会话回放一起看一个请求到底是参数带错了、后端拒绝、还是超时被取消一目了然。我之前排查过一次诡异的偶发性白屏Sentry上报的错误是某个组件渲染时访问了undefined的属性但数据明明来自一个看起来正常的接口。打开LogRocket才发现用户是在页面加载完成前迅速切换了路由旧页面的异步请求返回后更新了store中的一个深层对象导致新页面的reducer没处理好这个异常状态。这种bug靠打日志和代码审阅基本不可能发现但回放一遍现场十分钟就能定位。3.3 结合Sentry的Source Tag实现跳转排查LogRocket有个很有用的功能——source标签。我们的做法是给LogRocket和Sentry都加上用户唯一ID作为标签然后在Sentry的错误详情页里把对应的LogRocket回放链接拼出来const sessionURL LogRocket.sessionURL; Sentry.configureScope(scope { scope.setTag(session_url, sessionURL); scope.setUser({ id: user.id }); });这样从Sentry的Issue页面点进去就能直接看到这个错误发生前30秒的用户操作回放。从知道坏了到看到怎么坏的中间只隔一次点击排查效率完全不是一个量级。反过来从LogRocket的会话列表里也能看到这个用户在这段时间内踩过哪些Sentry错误双向打通才是这套体系真正的完全体形态。4. 双工具联动的完整事故排查流程4.1 打通用户身份的关联设计要让Sentry和LogRocket真正联动起来关键是把userId和sessionId两套标识统一。我在项目里做了一个简单的监控工具模块封装了统一的埋点入口export function initMonitoring(user) { Sentry.setUser({ id: user.id, email: user.email }); LogRocket.identify(user.id, { name: user.name, email: user.email, plan: user.plan, signupDate: user.signupAt }); Sentry.configureScope(scope { scope.setTag(logrocket_session, sessionURL); }); }LogRocket的identify接口可以附加自定义属性后续可以在LogRocket的会话列表里按用户ID或计划类型筛选精准定位到特定用户群体的操作行为。Sentry则通过setUser把每个错误关联到具体用户配合之前提到的session_url标签随时可以跳转到对应回放。这里有个实操细节一定要把LogRocket.sessionURL在你自己的用户系统里留一份记录。比如用户通过客服渠道反馈问题时客服可以直接查出该用户最近的session链接不需要再让用户复现。这个体验对客服效率的提升是很明显的也变相降低了用户因为反复被追问当时点了什么而产生的烦躁感。4.2 一次线上支付事故的排查复盘去年我们遇到过一次线上支付接口大面积失败用户反馈微信支付调起后又取消提示未知错误。Sentry后台当时已经被几千条同类型错误淹没告警里相同堆栈的ErrorGroup挤在一起根本分不清影响范围。我们的排查过程是这样的先看Sentry里这个错误按environment、release、browser维度的分布发现只在Chrome 85以下的用户里出现且集中在两个旧版本上。再点开某个错误的session_url标签直接跳进LogRocket播放回放看到用户连续点了三次支付按钮每次支付调起后瞬间就取消了控制台里报了一个跨域相关的警告。继续看网络请求发现支付SDK加载的CDN域名在部分网络环境下被劫持返回了错误脚本。沿着这个链条再往下去应用部署记录确认是我们的静态资源CDN配置在某个时段被误改回滚后错误量立刻恢复正常。这个案例里Sentry负责把问题从一片迷雾缩小到特定版本特定浏览器LogRocket则把为什么支付会取消这最后一步的现场还原了出来。没有这两者我们大概率要在用户社区里反复发问卷收集信息耗时少说以天计而以这个错误的量级来看每多一小时就是几十个用户受影响。4.3 数据脱敏与隐私合规的实践经验接入监控体系后隐私合规一定是绕不开的话题。用户在页面上输入的手机号、身份证、银行卡、聊天内容都不应该被原样记录。LogRocket的sanitizer是前置防线但我们是前后两道防线一起做第一道防线是在LogRocket和Sentry的初始化配置里做脱敏比如所有input[typepassword]和input[typetel]的元素直接替换为占位符。第二道防线是在业务代码层面对需要埋点分析的数据只打脱敏后的值比如用户手机号只保留前三位和后四位。第三方工具的脱敏做的是事发后抹除业务层的脱敏做的是根本不产生后者显然更稳。还要注意一点不要把监控数据的存储期限设成永久。Sentry和LogRocket的付费版都支持数据保留策略建议按团队实际需要设成30到90天。超过保留期的旧数据要么自动删除要么导出到自己的数据仓库再做审计分析不要把用户行为数据无限期地放在第三方平台上这既是合规要求也是对用户负责。5. 生产环境中那些文档没写的坑5.1 Source Map上传失败的连锁反应这个坑我们踩了不止一次。CI里构建用的是npm run buildSource Map上传是构建完之后单独一步偶尔网络抖动或权限配置出错上传脚本会静默失败——Sentry CLI带--ignore参数时有些错误不会报非零退出码CI以为成功了实际map没传上去。结果是线上新的release错误堆栈全部没有源码映射排查效率骤降。我的解决思路是两步第一步在CI脚本里加上传成功后的校验步骤比如调用Sentry API查询该release下是否有关联的source map文件数量不对就fail构建第二步在Sentry后台的Release详情页看Artifacts是否齐全凡是发布过新版本但没有新增artifact的release立刻标红。这套校验跑通后Source Map问题基本杜绝了。还要注意一个点webpack打包时如果启用了css-loader的sourceMap上传的map文件会包含CSS的源码路径如果CSS文件路径里恰好有公司内网域名就被当成信息泄露来处理。所以上传之前我会统一对map文件做一次路径重写把绝对路径改成相对路径。5.2 性能开销与采样率的平衡术加监控不是零成本的。LogRocket本身会监听所有DOM变更在复杂交互页面上这几毫秒的diff计算和网络传输会造成可感知的卡顿。Sentry的BrowserTracing也会给每个XHR请求增加耗时统计的开销。我实测的数据是一个包含复杂表格和频繁setState的管理后台开启LogRocket后首屏渲染时间大约增加3%到5%对多数业务影响不大但如果你的页面本身就是重交互、高频更新的建议做采样。我们生产环境的采样策略是tracesSampleRate设为0.1LogRocket的会话默认采样率为100%但只作用于已登录用户未登录的匿名访客只保存1%的会话。另外LogRocket提供了shouldCapture函数可以按网络状态和页面类型动态过滤比如在弱网环境下自动降低采样率减少上传对用户带宽的占用。LogRocket.init(your-app-id, { shouldCapture: () { if (navigator.connection?.effectiveType slow-2g) return false; return Math.random() 0.85; } });这种方案兼顾了覆盖率和性能尤其适用于移动端H5页面。移动端网络不稳定把用户宝贵的带宽花在监控上传上本身就是一种用户伤害。5.3 告警风暴与分组误判的真实案例还有一个我印象极深的坑某个版本我们改了公共组件的默认导出方式结果全站几十个页面同一天全部抛错。Sentry后台瞬间涌进上万条相同错误按默认告警规则触发了所有P1通知团队所有人同时开始处理结果发现只要刷新一次页面错误就消失了。后续一查是灰度发布时后端接口返回结构和前端新版本不匹配属于预期内的不兼容但我们把错误当成了需要紧急响应的线上故障。经过这次教训我们给告警规则加了两条约束一是对相同release的错误设置新出现才告警的规则版本归属不再触发告警二是设置错误量的阈值衰减比如一个错误在过去24小时内已经出现超过500次就不再触发新增告警。另外利用Sentry的Issue Owners和Team功能把前端监控告警分给前端组、后端告警分给后端组避免所有人被无关告警轰炸。6. 不同团队规模下的选型与部署建议6.1 小团队怎么把成本压到最低如果你所在团队规模不大、日活不高完全没必要一上来就上企业版的付费套餐。Sentry提供了完全开源的self-hosted版本官方提供了一键部署脚本一台2核4G的云服务器就能跑起来。LogRocket目前没有开源版本但免费的Starter套餐有每月1000个会话的额度适合验证阶段使用。小团队我建议先把最基础的链路跑通Sentry负责错误收集和Source MapLogRocket用免费额度做关键用户的会话回放。优先保证任何线上错误都能定位到代码行这一目标性能监控和深度用户行为分析可以后面再加。我见过太多小团队一上来就追求大而全结果监控配置比业务代码还复杂最后没人维护逐渐变成摆设。6.2 中大型团队怎么把数据用起来当错误量和用户量上来了工具层面的接入反而不是重点重点在于数据如何沉淀和流转。我们现在的做法是Sentry和LogRocket作为实时排查的入口通过webhook把错误事件同步到内部的问题工单系统每周自动汇总Top 20的错误列表和对应的用户影响面直接推送到团队周报。前端小组的绩效指标里就有一项是存量问题在Sentry中的平均解决时间这个指标促使大家主动去查监控、消Issue而不是等用户报障。另外LogRocket的会话数据对产品团队的帮助也不小。通过设定错误发生前的会话筛选条件产品经理可以直观看到用户在哪一步流失、哪个交互最让人困惑。这些一手行为数据比任何问卷调研都真实多往这个方向挖掘监控体系的ROI会远超你的预期。我自己在搭建这套体系时最有体会的一点选型不是越贵越好也不是功能越多越好而是要让每一种数据都有人看、有人用、有反馈闭环。Sentry和LogRocket只是两把趁手的工具真正让监控体系发挥价值的是你围绕它们建立的排查流程和团队习惯。先把基础打牢再逐步加码这条路走下去前端团队的线上问题排查会从焦头烂额变成按图索骥那种掌控感值得每个团队认真投入一次。