
一、如何设计一个前端监控架构我会把前端监控分成数据采集、数据上报、服务端存储聚合、告警分析四部分覆盖错误、性能和用户行为重点解决数据采得全、上报不丢、海量数据可控以及出了问题能快速定位。二、面试官继续追问后的展开1. 先明确到底监控什么前端监控不能只盯着 JS Error。我一般会分成三类前端监控 ├── 错误 │ ├── JS 运行时错误 │ ├── Promise 异常 │ ├── 网络请求错误 │ ├── 资源加载错误 │ └── 框架错误 │ ├── 性能 │ ├── 页面加载 │ ├── Core Web Vitals │ ├── 接口耗时 │ ├── 静态资源耗时 │ └── 长任务 │ └── 用户行为 ├── 点击 ├── 路由变化 ├── 页面访问 └── 关键业务流程因为真实线上问题经常不是“代码抛异常了。”而是“代码没报错但用户就是用不了。”比如用户点击「加入购物车」 ↓ 接口请求 ↓ 弱网环境下请求超时 ↓ 业务代码 catch ↓ 没有继续抛异常 ↓ 全局 error 监控完全不知道 ↓ 用户反馈按钮没反应所以错误监控 ≠ 前端监控。三、错误怎么采集2. JS 运行时错误怎么捕获同步运行时异常主要通过window.onerror捕获Promise 异常通过unhandledrejection捕获。例如window.addEventListener(error,event{report({type:js_error,message:event.message,filename:event.filename,lineno:event.lineno,colno:event.colno,stack:event.error?.stack});});window.addEventListener(unhandledrejection,event{report({type:promise_error,reason:serializeError(event.reason)});});这里有一个容易被问的点try{awaitrequest();}catch(err){// 已经被业务代码处理}这种错误不会自动出现在unhandledrejection里。所以只靠全局异常监听一定会漏。四、那接口超时怎么办这是这道题非常重要的追问。核心回答网络错误不能只靠全局异常捕获要在请求层统一采集请求失败、超时、状态码异常和业务失败。例如业务代码 ↓ request() ↓ 请求拦截层 ├── 请求耗时 ├── HTTP 状态码 ├── 超时 ├── 网络错误 └── 业务错误码 ↓ 监控 SDK ↓ 上报例如asyncfunctionrequest(config){conststartperformance.now();try{constresponseawaitfetch(config.url,config);constdurationperformance.now()-start;reportRequest({url:config.url,duration,status:response.status});returnresponse;}catch(error){reportRequest({url:config.url,duration:performance.now()-start,error:serializeError(error)});throwerror;}}这里还要继续区分HTTP 500 网络失败 请求超时 HTTP 200 业务 code 失败这些在监控上不是一回事。五、资源加载错误怎么监控比如JS 加载失败 CSS 加载失败 图片加载失败 字体加载失败可以通过资源错误事件采集window.addEventListener(error,event{consttargetevent.target;if(targetinstanceofHTMLScriptElement||targetinstanceofHTMLLinkElement||targetinstanceofHTMLImageElement){report({type:resource_error,url:target.src||target.href});}},true);这里需要注意资源加载错误和 JS 运行时错误不是一类东西。所以监控 SDK 通常要区分事件类型。六、框架错误怎么办React 可以利用ErrorBoundary App / /ErrorBoundary捕获组件树中的渲染错误。例如class ErrorBoundary extends React.Component { componentDidCatch(error, info) { report({ type: react_error, message: error.message, stack: error.stack, componentStack: info.componentStack }); } render() { return this.props.children; } }但它也不是万能的。例如事件处理函数中的异常、异步回调中的异常不一定能由 Error Boundary 捕获。所以最终还是需要全局异常 框架异常 请求异常 资源异常一起采集。七、跨域脚本为什么拿不到完整错误这是一个比较好的深挖点。如果页面加载scriptsrchttps://cdn.xxx.com/app.js/script发生跨域 JS 错误时浏览器出于安全限制可能只能给出Script error.而拿不到完整message filename line column stack解决通常需要scriptsrchttps://cdn.xxx.com/app.jscrossoriginanonymous/script同时 CDN 响应Access-Control-Allow-Origin: *这样浏览器才允许页面获得更完整的错误信息。八、性能监控采什么核心回答性能监控重点不是把所有 Performance API 数据都采下来而是围绕页面加载、用户体验和关键资源找真正影响体验的指标。常见包括页面加载 ├── FP ├── FCP ├── LCP ├── TTFB └── 页面整体加载耗时 用户体验 ├── INP └── CLS 资源 ├── JS ├── CSS ├── 图片 └── 字体 运行时 └── Long Task另外还可以采接口耗时 接口错误率 路由切换耗时 白屏 资源加载失败九、白屏怎么监控白屏监控本质上就是页面加载后判断“用户真正看到的内容有没有出来”如果在规定时间内没有检测到有效内容就上报一次白屏。例如JS 没报错 接口也返回 200 但是核心内容没有渲染出来 ↓ 用户看到白屏所以白屏需要单独做检测。常见思路页面加载 ↓ 等待一段时间 ↓ 检查关键 DOM / 页面有效像素 ↓ 判断是否出现有效内容 ↓ 异常则上报生产环境还可以结合白屏率 路由 设备 浏览器 版本 接口 用户行为一起定位。真正落地不会只选择一种检测方式而是白屏监控 │ ┌───────────┼───────────┐ ↓ ↓ ↓ 业务就绪标志 DOM检测 多点采样 │ │ │ └───────────┼───────────┘ ↓ 综合判断 ↓ 二次确认 / 去重 ↓ 上报 ↓ ┌───────────┼───────────┐ ↓ ↓ ↓ 页面 版本 环境 ↓ ↓ ↓ 接口 用户行为 错误 ↓ 聚合分析核心原则是业务就绪标志负责准确性通用 DOM/多点检测负责兜底二次确认降低误报错误和网络数据负责定位原因。十、用户行为为什么也要采集因为错误日志告诉你“哪里坏了”用户行为告诉你“用户是怎么走到这里的”。例如用户反馈“点击支付之后页面没反应。”单独看错误日志可能只有POST /api/pay timeout但是如果有行为链进入商品页 ↓ 点击立即购买 ↓ 进入订单页 ↓ 点击支付 ↓ POST /api/pay ↓ timeout定位速度会完全不同。所以可以记录PV 点击 路由 关键业务事件 接口调用 错误并通过一个traceId / sessionId把它们关联起来。十一、监控数据怎么上报这就是第二层传输层。最简单的fetch(/monitor,{method:POST,body:JSON.stringify(data)});但生产环境不能这么简单。12. 为什么要批量上报假设一个用户产生100 条监控数据如果1 条数据 1 个 HTTP 请求请求数量会非常夸张。所以通常事件 事件 事件 事件 ↓ 本地缓冲队列 ↓ 达到数量 / 时间阈值 ↓ 批量上报例如10 条 或者 5 秒满足一个条件就发送。十三、页面关闭时数据还没上报怎么办这是一个非常典型的追问。普通fetch()在页面关闭、刷新时存在请求来不及完成的问题。可以使用navigator.sendBeacon()例如navigator.sendBeacon(/monitor,JSON.stringify(events));也可以使用fetch(url,{method:POST,body:data,keepalive:true});具体选哪个要看数据大小、兼容性和服务端接口设计。十四、一天几十万甚至几百万条日志怎么办这里就开始从“前端开发”进入“架构设计”。核心回答核心思路是采样降量、批量传输、服务端聚合和数据分层存储而不是让所有数据无脑全量上报。15. 采样怎么做例如100 万用户 ↓ 普通性能数据只采 10% ↓ 约 10 万用户但不能简单Math.random()0.1更合理的是使用稳定的用户标识做哈希userId ↓ hash ↓ hash % 100 ↓ 10 ↓ 进入采样这样同一个用户不会一会儿被采、一会儿不被采。十六、是不是所有错误都可以采样不是。这是非常重要的边界。例如普通性能数据 → 可以采样 普通用户行为 → 可以采样 高频重复错误 → 可以采样 / 聚合 支付失败 → 尽量全量 白屏 → 尽量全量 核心接口失败 → 尽量全量也就是说采样不是简单地“10% 全部数据”而应该按数据价值做采样。十七、为什么需要错误指纹假设线上出现TypeError xxx app.js:100一天来了10 万次实际上可能只是同一个问题被 10 万个用户触发。所以服务端需要把相同错误聚合。例如错误类型 错误 message 文件 行号 关键 stack生成fingerprint例如a8f9237c然后100000 条错误事件 ↓ fingerprint ↓ 同一个错误 ↓ 1 个问题 ↓ 影响 32000 个用户这样监控平台关注的就不是“今天发生了多少条错误。”而是“今天出现了多少类问题以及每类问题影响了多少用户。”这个区别非常重要。十八、存储怎么设计可以按照数据特点分开。监控数据 ├── 错误数据 ├── 性能数据 ├── 行为数据 └── 聚合指标同时做冷热分层最近数据 ↓ 热存储 ↓ 快速查询 历史数据 ↓ 冷存储 ↓ 低成本归档例如最近 30 天 → 热数据 更早的数据 → 冷存储具体保存多久要根据业务价值、成本和合规要求决定。十九、告警怎么设计这是这道题最容易拉开差距的地方。不要回答“错误超过 100 条就报警。”因为这种告警非常容易把人淹没。应该从错误数量升级到错误率 影响用户数 业务成功率 增长趋势 严重等级二十、怎么避免告警疲劳比如TypeError 一天 10 万次不应该报警 10 万次而应该同一个 fingerprint ↓ 聚合 ↓ 统计 ├── 影响用户数 ├── 错误次数 ├── 错误增长率 └── 影响版本 ↓ 达到阈值 ↓ 触发一次告警还可以做告警去重 告警合并 告警抑制 冷却时间 升级策略二十一、P0 / P1 / P2 怎么划不要死背某几个数字。面试时更好的回答是严重等级应该根据业务影响定义而不是单纯根据错误数量定义。例如P0 核心业务不可用 支付失败率大幅上升 大面积白屏 核心接口整体故障 P1 重要功能明显异常 某个版本错误率突然上升 某地区接口大面积失败 P2 局部异常 少量用户报错 低优先级功能异常然后对应不同通知渠道P0 → 电话 / 即时通讯 P1 → 即时通讯 P2 → 邮件 / 日报重点是告警应该服务于故障响应而不是把监控系统变成报警器。二十二、如果监控上报接口自己挂了呢这就是监控系统的自监控与容灾。例如业务页面 ↓ 监控 SDK ↓ 主上报接口 × ↓ 备用通道可以根据数据类型设计不同策略普通性能数据 → 丢弃部分数据也可以接受 普通行为数据 → 可以降级 核心错误 → 优先保证上报另外还应该避免一个很危险的问题业务错误 ↓ 监控 SDK 上报 ↓ 监控 SDK 自己报错 ↓ 再次上报 ↓ 无限循环所以 SDK 本身必须异常隔离 上报失败保护 重试限制 队列大小限制二十三、完整架构可以怎么画面试时可以直接画这个用户浏览器 │ ┌───────────────┼────────────────┐ ↓ ↓ ↓ 错误采集 性能采集 行为采集 │ │ │ └───────────────┼────────────────┘ ↓ Monitor SDK │ ┌─────────┴─────────┐ ↓ ↓ 采样 本地队列 │ │ └─────────┬─────────┘ ↓ 批量上报 │ ┌───────┴───────┐ ↓ ↓ 主通道 备用通道 │ │ └───────┬───────┘ ↓ 接入 / 消息队列 ↓ ┌─────────┴─────────┐ ↓ ↓ 数据清洗 错误聚合 │ │ └─────────┬─────────┘ ↓ 存储 / 数据仓库 │ ┌──────────┼──────────┐ ↓ ↓ ↓ 查询分析 聚合指标 用户行为 │ │ │ └──────────┼──────────┘ ↓ 告警系统 ↓ P0 / P1 / P2 分级通知二十四、这道题真正的“主要矛盾”这里我建议你面试时抓住一个核心前端监控最大的难点不是“怎么捕获错误”而是“怎么保证真正影响用户的问题能被及时发现而且海量数据不会把系统和人都拖垮”。所以整个设计其实是在解决两个矛盾数据越多 ↓ 发现问题越全面 ↑ │ VS │ │ 成本越高 │ 查询越慢 / 告警越多最终要做到采得全 ↓ 传得稳 ↓ 存得住 ↓ 聚得起来 ↓ 告警不泛滥 ↓ 问题能定位二十五、面试官高频追问链追问 1window.onerror能捕获所有 JS 错误吗不能。它主要处理全局运行时异常Promise 异常、已经被业务catch的异常、某些框架内部异常等都可能需要额外处理。追问 2为什么unhandledrejection捕获不到接口超时因为只要业务代码把 Promise 异常catch住了它就已经不是“未处理的 Promise 异常”了。所以请求监控必须进入请求层。追问 3为什么不能所有日志都全量上报因为数据量、网络开销、存储成本和查询成本都会快速上升。所以要采样 聚合 批量上报追问 4为什么同一个错误要做 fingerprint因为 10 万条错误日志可能只是 1 个真实问题。我们真正关心的是问题数量 影响用户数 发生趋势而不是日志条数。追问 5为什么错误已经有 Stack 了还需要用户行为因为 Stack 告诉你“代码哪里出错”行为链告诉你“用户怎么走到这里”。两者结合才能快速复现问题。追问 6页面切后台和页面崩溃怎么区分这里不能简单通过“页面没响应”判断。可以结合visibilitychange pagehide pageshow 路由状态 心跳 错误事件 性能数据进行判断。例如用户切后台 ↓ visibilityState hidden ↓ 没有业务事件不能直接判断成页面崩溃。真正的“页面失活/白屏/崩溃”需要结合多种信号判断。追问 7监控 SDK 自己出 bug 怎么办SDK 必须和业务代码隔离任何采集、序列化、上报逻辑都不能反过来影响业务页面。例如try{collect();report();}catch{// SDK 自己失败不能影响业务}同时限制重试次数 队列长度 单条日志大小 上报频率二十六、最终背诵版如果面试官只给你30 秒我建议你直接说我会把前端监控拆成四部分采集、上报、存储聚合、告警分析。采集层覆盖 JS、Promise、网络、资源、框架错误同时采性能和关键用户行为上报层通过批量、采样和主备通道控制成本并保证可靠性服务端通过错误指纹和聚合把海量日志归并成真正的问题最后按照影响范围和业务重要性做分级告警并结合用户行为、版本、设备和错误堆栈快速定位。核心目标不是收集最多日志而是让真正影响用户的问题能够及时发现、准确定位。