一、先来解决一些知识障碍
1. 什么是 SDK
SDK 全称 Software Development Kit (软件开发工具包),就是一个可被引入、用来 “ 增强功能 ” 的工具包。
在前端埋点监控场景下,SDK 就是一个前端脚本(通常是一个 JS 文件),它可以被放到任何前端项目里,用来自动收集和上报运行数据。
2. 什么是埋点和监控
○ 埋点是一种通过在代码中插入监控代码(埋点代码)来收集用户在应用程序中的行为数据的一种数据采集技术,核心关注点是用户行为和业务数据
○ 监控是一种实时收集应用的性能表现、加载速度、错误日志等运行状态数据的技术
○ 埋点和监控可以「 获取用户行为以及跟踪产品在用户端的使用情况」,并以监控数据为基础,指明产品优化的方向,核心关注点是系统Bug、性能优化
3. 关于埋点监控平台的“通用性设计”与“需求来源”问题
埋点监控平台就相当于给一个已有的比如说电商管理系统做监控,那开发这个埋点监控平台需要事先有一个这种管理系统吗?如果不需要,那我们怎么知道这个埋点监控平台需要监控哪些东西呢?
不需要。
首先明确 : 埋点监控系统 ≠ 为某个系统定制,埋点监控平台本质上是一种 通用的数据采集上报平台
它的目标是: 提供一种可以 " 接入任何前端项目 "的能力,用来采集行为、性能、错误等数据
那我们怎么知道要采集什么??
关键在于: 明确监控目标和数据类型
我们可以把监控目标分为几大类:
| 监控类型 | 采集内容示例 | 目的 |
|---|---|---|
| 性能监控 | 页面加载时间、白屏时间、资源加载情况 | 优化前端性能体验 |
| 行为监控(埋点) | 点击按钮、浏览页面、表单提交,搜索操作 | 分析用户行为路径 |
| 错误监控 | 接口错误、资源加载错误、Promise拒绝 | 提升系统稳定性 |
| 用户体验监控 | 收集白屏、卡顿等影响用户体验的问题 | 提高用户体验 |
| 环境信息 | 浏览器、系统设备、网络类型 | 维度分析辅助 |
我们在设计 SDK时,可以让它支持这些类型,等到将来接入某个系统的时候(比如电商管理系统)时,开发者只需要调用你提供的接口:
monitor.trackEvent('add_to_cart', { productId: 'A123', price: 99 });
a. monitor 是这个 SDK 里定义的数据监控分析工具的实例对象
b. .trackEvent是该对象的方法,专门用于追踪用户事件,记录用户的具体操作行为
c. 第一个参数'add\_to\_cart'是事件类型,表示 “ 加入购物车 “动作
d. 第二个参数{ productId: 'A123', price: 99 }是事件属性,提供事件的详细信息
▪ productId: 'A123'是被加入购物车的商品ID
▪ price: 99是商品价格(单位通常是元)
实际应用起来的场景:
当用户在电商网站点击"加入购物车"按钮时,这段代码会执行,向数据分析服务器发送类似这样的信息:
用户执行了"加入购物车"操作 商品ID: A123 商品价格: 99元
4. 什么是架构
架构(Architecture)就是系统整体的结构设计,可以理解为“系统的骨架或蓝图”。它告诉你:
○ 系统有哪些模块(功能块)
○ 模块之间怎么协作(数据流、调用关系)
○ 数据在系统里怎么传递和处理
5. 埋点监控 SDK 怎么集成到前端项目里
a. 通过<script>标签引入
▪ 适合普通 HTML 页面或者快速试用SDK
▪ 做法:SDK打包成一个JS文件放到服务器上然后在页面引入
b. 通过包管理工具引入(现代前端框架)
▪ 做法:包管理工具安装,在项目里 import 并初始化
c. 动态加载 SDK
▪ 适合希望按需加载 SDK,减少首屏压力的场景,例如:
const script = document.createElement('script');
script.src = '<https://cdn.example.com/sdk.v1.0.0.js>';
script.onload = () => { SDK.init({ appId: '123' }); };
document.head.appendChild(script); |
二、从 0 到 1 开发一个埋点监控平台的过程
1. 需求分析 & 功能规划
a. 明确要监控的内容:
▪ 性能指标:FCP、LCP、CLS、TTFB、资源加载时间
▪ 错误监控:JS 错误、Promise 未捕获异常、资源加载失败
▪ 用户行为:点击、PV、UV、停留时间、页面跳转
b. 确定数据优先级:
▪ 高优先级:错误、关键行为(影响功能)
▪ 中优先级:性能指标(影响体验)
▪ 低优先级:普通行为数据(影响业务决策)
c. 明确项目目标:
▪ 通用性强:SDK 可嵌入任何前端项目
▪ 低侵入、低损耗:不影响页面正常运行
▪ 后端可实时处理与可视化分析
d. 输出:
▪ 功能清单
▪ 数据采集指标列表
▪ 数据优先级表
2. 技术调研与架构设计
a. SDK 架构设计
i. 采集模块:决定要捕获哪些数据(性能/错误/行为)
ii. 处理模块:如何处理采集到的数据(数据格式化、去重、队列缓存、优先级调度、重试机制)
iii. 上报模块:数据怎么、什么时候、以什么方式发送到后端(立即上报、批量上报、延迟上报、页面关闭上报)
b.上报策略
▪ 高优先级 → fetch/sendBeacon 立即上报
▪ 中优先级 → 页面加载完成或定时批量上报
▪ 低优先级 → 定时/批量上报
c. 后端初步设计
▪ 数据接收接口
▪ 数据存储(性能、错误、行为表)
▪ 聚合分析逻辑:对数据库里的数据做统计、汇总、计算指标,生成可用信息
d. 输出:
▪ SDK 模块图:把SDK内部的各功能模块的组成和关系可视化
▪ 上报流程设计:展示数据从采集到上报后端的完整流程
▪ 后端接口与数据表结构草图
3️. SDK 核心开发
a. 采集模块
○ 性能监控:Performance API + 页面 load/DOMContentLoaded
○ 错误监控:
▪ window.onerror 捕获 JS 错误
▪ window.onunhandledrejection 捕获 Promise 异常
▪ 资源加载失败事件监听
b. 行为监控:
▪ 点击事件监听 (document.addEventListener)
▪ 路由跳转监听 History API (pushState, replaceState) 或路由变化
▪ 表单提交、页面停留时长等记录用户交互数据
| 浏览器原生支持的 API 比如 Performance API + 错误事件 + 行为事件 就支持绝大部分的基础监控数据的采集了,所以掌握已有的 API 是实现这部分的基础 |
|---|
c. 数据处理模块
○ 数据去重、格式化
○ 队列缓存,支持高/中/低优先级调度
○ 网络异常重试机制
| 数据处理模块就是要用 JS 实现一些复杂数据结构和算法来保证高效有序合理,技术其实就是前端开发基础的技术 |
|---|
d. 上报模块
○ 高优先级:立即 fetch/sendBeacon
○ 中优先级:页面 unload 或定时批量
○ 低优先级:定时批量上报(如 5 条或 5 秒)
○ 页面关闭时确保使用 navigator.sendBeacon 发送未上报的数据
本质上就是用 JS 里面的fetch/ XHR / navigator.sendBeacon这些网络请求工具把数据高效合理地发给后端 |
|---|
4️. 后端接口与数据存储开发
a. 接口设计:
▪ 定义好请求方法(POST)和数据格式(JSON)
▪ 支持高并发请求,保证数据不会丢失
▪ 对不同优先级数据可能做不同处理策略
b. 数据存储:
▪ 数据分表存储,便于聚合统计和查询
▪ 可以设计索引和分区,保证高性能查询
c. 数据处理:
▪ 聚合统计(PV、点击量、错误次数)
▪ 异常告警触发(高优先级错误)
| 核心目标是可靠接收、存储、处理前端采集的数据,为后面可视化和分析提供基础 |
|---|
5️. SDK 与后端联调
○ 测试数据是否准确到达后端
○ 检查高优先级数据是否即时上报
○ 检查低优先级数据是否批量、延迟上报
○ 测试网络异常、页面关闭、SPA 路由切换等特殊场景下是否可靠
6️. 可视化与分析模块开发
a. 展示内容:
▪ 性能趋势图(FCP、LCP、CLS)
▪ 错误分布图(类型、频次、页面)
▪ 用户行为统计(PV、UV、点击热区)
b. 数据刷新:
▪ 实时数据 → WebSocket 或定时拉取
▪ 历史数据 → 后端聚合接口
▪ 可选:告警系统(如错误次数超过阈值触发邮件或钉钉)
| 用 Echarts 这种前端图表库把得到的数据直观地展示出来,辅之用 Websocket 实现实时传输数据 ,用 AJAX/fetch 来定时请求数据 ,后端还可以用 REST API 把数据进行聚合来展示数据的趋势 |
|---|
7️. 测试与性能优化
a.功能测试:
▪ 数据采集是否完整
▪ 数据上报是否正确
b. 性能测试:
▪ SDK 体积小、对页面渲染无影响
▪ 高并发行为上报不会丢数据
c. 优化:
▪ 队列调度优化
▪ 数据压缩
▪ 上报策略调整
| 基本上就是用抓包工具,浏览器开发者工具这些获取一些模拟的数据,用 JS和浏览器提供的api实现数据结构和算法来对这个系统上线后的结果进行模拟和预测 |
|---|
8️. 上线与迭代
○ 先小范围上线,收集真实数据
○ 调整指标采集和上报策略
○ 迭代优化 SDK 性能和功能
| 这部分就是用 CDN 内容分发网络实现版本化,实现一些数据结构和算法,利用 JS 的网络请求工具实现根据实际情况调整采集和上报策略,然后用一些打包工具来减小SDK的体积等实现优化 |
|---|
三、监控价值与学习目标
1. 为什么做这个埋点监控平台 ?
| 角度 | 目的 |
|---|---|
| 业务侧 | 分析用户行为,优化产品转化路径 |
| 技术侧 | 定位前端 Bug、性能瓶颈、白屏等问题 |
| 团队侧 | 搭建统一的监控体系,减少埋点重复工作 |
| 学习侧 | 理解浏览器运行机制、事件系统、数据传输、日志采集与分析等全链路知识 |
2. 做完了要掌握哪些知识 ?
| 学习方向 | 涉及知识点 |
|---|---|
| 前端采集层 | 浏览器事件机制、错误捕获、性能指标采集(Performance API) |
| SDK 设计 | 模块化封装、插件机制、数据缓存与上报策略 |
| 网络层 | Fetch / Beacon / XHR 上报方案、跨域与可靠性 |
| 后端接收层 | 日志存储结构设计、接口处理、数据分析管道 |
| 数据展示层 | 可视化平台(如 ECharts、Grafana) |
| 工程化能力 | TypeScript、打包(Rollup / Vite)、npm 发布、监控 SDK 接入方式 |
四、原生 JS 实现最简单的监控
| JS 有一些原生 API 可以实现对一些事件的监听和跟踪,下面是原生JS实现的对一些事件和数据的监控 |
|---|
1. 跟踪用户事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>跟踪用户事件</title>
<style>
body {
height: 2000px;
background-color: antiquewhite;
}
</style>
</head>
<body>
<!-- 1.捕获按钮的点击 -->
<!-- 2.捕获页面的滚动行为 -->
<div>
<button id="myButton">捕获按钮的点击行为</button>
</div>
<script>
//定义通用的跟踪函数
function trackEvent(eventType, details) {
console.log(`Event:${eventType}`, details)
// 上报
//fetch('/测试接口的地址',{
// method:'POST',
// body: JSON.stringfy({eventType,details})
// })
}
//获取按钮对象
const button = document.getElementById('myButton')
//给按钮添加监听,传入参数
button.addEventListener('click', function () {
trackEvent('button_click', {
buttonId: 'myButton',
timeStamp: Date.now()
})
})
window.addEventListener('scroll', function () {
trackEvent('page_scroll', {
scrollY: window.scrollY,
timeStamp: Date.now()
})
})
</script>
</body>
</html>
a. 监控按钮点击
b. 监控页面滚动
2. 完成性能监控指标
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>性能监控指标</title>
<style>
</style>
</head>
<body>
<!-- 1.捕获按钮的点击 -->
<!-- 2.捕获页面的滚动行为 -->
<div>页面加载时间的监控</div>
<div>API调用耗时监控</div>
<script>
//定义通用的跟踪函数
function trackEvent(eventType, details) {
console.log(`Event:${eventType}`, details)
// 上报
//fetch('/测试接口的地址',{
// method:'POST',
// body: JSON.stringfy({eventType,details})
// })
}
//API 调用耗时
//1.请求发送前
//2.请求接收后
function apiPerformance() {
const start = performance.now()
fetch('https://api.imooc-web.lgdsunday.club//api/data-lazy/01')
.then((res) => res.json())
.then((data) => {
const duration = performance.now() - start
trackEvent('apicall', {
duration,
endpoint: 'https://api.imooc-web.lgdsunday.club//api/data-lazy/01'
}
)
})
}
apiPerformance()
</script>
</body>
</html>
a. 页面加载时间的监控
b. API调用耗时监控
3. 进行错误追踪监听
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>性能监控指标</title>
<style>
</style>
</head>
<body>
<!-- 1.捕获按钮的点击 -->
<!-- 2.捕获页面的滚动行为 -->
<div>进行错误追踪监听</div>
<script>
//定义通用的跟踪函数
function trackEvent(eventType, details) {
console.log(`Event:${eventType}`, details)
// 上报
//fetch('/测试接口的地址',{
// method:'POST',
// body: JSON.stringfy({eventType,details})
// })
}
window.onerror = function (message, source, lineno, colno, error) {
trackEvent('js_error', {
message,
source,
lineno,
colno,
error
})
return true
}
''.push('123')
</script>
</body>
</html>
4. 自定义的埋点上报
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>用户注册表单</h1>
<form id="register">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>
</br>
</br>
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
</br>
</br>
<label for="password">密码:</label>
<input type="password" id="password" name="password" required>
</br>
</br>
<button type="button" id="submitButton">注册</button>
</form>
<script>
function trackEvent(eventType, details) {
console.log(`Event:${eventType}`, details)
// 上报
//fetch('/测试接口的地址',{
// method:'POST',
// body: JSON.stringfy({eventType,details})
// })
}
//1.监控页面浏览量
window.addEventListener('load', function () {
trackEvent('page_view', {
url: window.location.href,
timestamp: Date.now()
})
})
//2.监控字段的聚焦事件
const inputFields = document.querySelectorAll('#register')
inputFields.forEach(field => {
field.addEventListener('focus', function () {
trackEvent('input_focus', {
fieldName: field.name,
timestamp: Date.now()
})
})
})
//监控提交按钮点击
submitButton.addEventListener('click', () => {
trackEvent('button_click', {
buttonId: 'submitButton',
timestamp: Date.now()
})
handleSubmit()
})
//4.监控表单的提交事件
function handleSubmit() {
if (register.checkValidity()) {
trackEvent('form_submit', {
formId: 'register',
formData: {
username: rigister.username.value,
email: rigister.email.value,
password: register.password.value
},
timestamp: Date.now()
})
} else {
alert('请填写完整表单')
}
}
</script>
<style>
#sectionId {
width: 200px;
height: 200px;
background-color: antiquewhite
}
</style>
</body>
</html>
5. 一个简易表单的事件监控
JS实现监控方案总结: 核心就是获取到对应的数据,然后在特定的地点或者特定的回调时间把获取的数据上报给服务端, 想要完成监控,关键就是要更加深入地去了解各种关键的事件节点
五、现有埋点监控平台和上报方案对比
1. 一些现有的「前端埋点监控 SDK / 平台类开源项目」的对比
| 类别 | 对比方面 | WebSee | MonitorJS | Funny-Monitor |
|---|---|---|---|---|
| 功能点 | 错误监控 | JS 异常、Promise、资源加载、接口错误 | JS 异常、Promise、资源加载(window.onerror 等实现) | JS 异常、Promise、接口错误 |
| 性能监控 | FP / FCP / LCP / CLS、接口耗时 | 页面加载性能、资源性能 | ||
| 行为监控 | 点击、路由跳转、页面停留 | 用户路径记录(出错前后的操作上下文) | 点击、路由跳转 | |
| 环境信息采集 | 浏览器、设备、网络类型 | 浏览器 UA、URL、时间戳 | ||
| 数据上报 | Beacon / Fetch | XMLHttpRequest | Fetch / XHR | |
| 架构/扩展能力 | 插件化架构设计 | 前端 SDK + 后端 + 可视化平台 | ||
| 亮点 ✅ | 错误监控 | 错误捕获覆盖全面,支持多类型错误 | 错误捕获逻辑清晰直观 | 错误捕获完整,便于学习整体流程 |
| 性能监控 | 支持 FP/FCP/LCP/CLS,接口耗时上报 | 支持页面加载性能和资源性能分析 | ||
| 行为监控 | 覆盖点击、路由跳转、页面停留 | 用户行为路径记录 | 覆盖点击、路由跳转 | |
| 环境信息采集 | 浏览器、设备、网络类型信息完整 | 浏览器 UA、URL、时间戳 | — | |
| 数据上报 | 支持 Beacon / Fetch,多种上报方式 | XMLHttpRequest | 支持 Fetch / XHR | |
| 架构/扩展能力 | 插件机制完善,可自由扩展,TypeScript 编写,代码结构清晰 | 轻量、实现简单、易于嵌入 | 架构完整(前端 SDK + 后端 + 可视化) | |
| 不足 ⚠️ | 错误监控 | — | 仅支持错误监控,无性能/行为数据 | 文档不完善,部署复杂 |
| 性能监控 | — | — | 后端能力较弱,难支撑大规模日志 | |
| 行为监控 | 自动化埋点能力一般 | — | — | |
| 环境信息采集 | — | — | — | |
| 数据上报 | 依赖后端数据接收服务 | 无插件机制与扩展能力无可视化展示层更新不活跃 | — | |
| 架构/扩展能力 | SDK 较重,学习曲线陡峭 | — | 代码更新少、维护较慢 | |
| 总结推荐 |
| 项目名称 | 适合人群 | 学习价值 |
|---|---|---|
| WebSee | 想深入了解前端监控 SDK 原理、设计架构的人 | ⭐⭐⭐⭐(进阶级) |
| MonitorJS | 想快速理解错误监控机制、入门级学习者 | ⭐⭐(入门级) |
| Funny-Monitor | 想学习从 SDK 到后端数据展示全流程的开发者 | ⭐⭐⭐(综合实践型) |
2. 上报方案对比
一个埋点监控系统已经包含了上报方案,我们如果用已有的埋点监控系统,选定了系统,上报方案一般改不了,那为什么还要拿出来说??因为对于开发者来说,要设计系统 ,就需要选择最佳上报方案,因此需要理解原理 ,得知道各种方案的优劣
| 上报方式 | 工作原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Beacon | 利用浏览器提供的异步、可靠的数据上报接口,将少量数据在页面卸载时发送到服务器,不阻塞页面卸载过程。 | ✅ 异步非阻塞,页面关闭时也能成功上报 ✅ 不影响性能,不需手动管理连接 ✅ 浏览器原生支持较好(现代浏览器) | ⚠️ 仅支持 POST 请求 ⚠️ 数据量有限(~64KB) ⚠️ 旧浏览器(IE)不兼容 | 页面关闭、跳转、卸载等临时事件上报;前端埋点 SDK 推荐使用的默认方式 |
| XHR | 传统 AJAX 接口,通过构造异步请求向服务器上报数据,可灵活设置请求头、同步/异步模式。 | ✅ 兼容性好,几乎所有浏览器支持 ✅ 可精确控制请求(header、method等) ✅ 可与后端交互逻辑复用 | ⚠️ 页面卸载时容易被中断(数据丢失) ⚠️ 若同步请求会阻塞页面卸载 ⚠️ 需自行封装上报逻辑 | 页面加载阶段、周期性上报、错误日志上报 |
| Fetch API | 基于 Promise 的现代异步请求接口,语义更简洁,可用于发送 JSON、FormData 等类型数据。 | ✅ 语法简洁、支持 async/await ✅ 支持跨域、灵活配置请求头 ✅ 支持 streaming 等特性 | ⚠️ 同样存在卸载中断问题(除非配合 Keepalive) ⚠️ 旧浏览器需 polyfill | 常规埋点、性能数据上报、接口日志上传 |
| Image Beacon | 通过创建 new Image(),将数据拼接在 URL 中发送 GET 请求。 | ✅ 实现极其简单、兼容性极好 ✅ 不受跨域限制 | ⚠️ 数据长度受 URL 限制(约 2KB) ⚠️ 无法获取响应结果 ⚠️ 不适合复杂数据结构 | 兜底兼容方案 |