字
字节笔记本
2026年2月22日
miracle90/monitor:完整的前端监控体系开源项目
本文介绍 miracle90/monitor,一个完整的前端监控体系开源项目。该项目提供了错误监控、白屏检测、性能监控、卡顿检测、PV 统计等核心功能,帮助开发者构建完整的前端监控解决方案。
项目简介
miracle90/monitor 是一个开源的前端监控 SDK 项目,由开发者 miracle90 维护。截至目前,该项目在 GitHub 上已获得 860+ stars,163 forks,是一个值得关注的前端监控方案。
该项目涵盖了前端监控的完整链路,从数据采集、上报到可视化展示,为前端工程师提供了技术深度和广度的实践案例。
为什么要做前端监控
- 更快地发现和解决问题 - 实时捕获线上异常,缩短故障响应时间
- 做产品的决策依据 - 基于真实用户数据优化产品体验
- 为业务扩展提供更多可能性 - 数据驱动业务增长
- 提升前端工程师的技术深度和广度 - 打造简历亮点
前端监控目标
稳定性 (Stability)
- JS 错误:JS 执行错误、Promise 异常
- 资源错误:JS、CSS 资源加载异常
- 接口错误:Ajax、Fetch 请求接口异常
- 白屏:页面空白
用户体验 (Experience)
- 页面加载性能
- 交互响应速度
- 卡顿检测
业务 (Business)
- PV:页面浏览量和点击量
- UV:访问站点的不同 IP 人数
- 用户在每个页面的停留时间
前端监控流程
- 前端埋点 - 采集用户行为和性能数据
- 数据上报 - 将采集数据发送到服务端
- 加工汇总 - 服务端处理和存储数据
- 可视化展示 - 数据报表和图表展示
- 监控报警 - 异常实时告警通知
核心功能实现
1. 错误监控
JS 执行错误 + 资源加载错误
javascript
window.addEventListener(
"error",
function (event) {
// 资源加载错误
if (event.target && (event.target.src || event.target.href)) {
tracker.send({
kind: "stability",
type: "error",
errorType: "resourceError",
filename: event.target.src || event.target.href,
tagName: event.target.tagName,
timeStamp: formatTime(event.timeStamp),
selector: getSelector(event.path || event.target),
});
} else {
// JS 执行错误
tracker.send({
kind: "stability",
type: "error",
errorType: "jsError",
message: event.message,
filename: event.filename,
position: (event.lineNo || 0) + ":" + (event.columnNo || 0),
stack: getLines(event.error.stack),
selector: lastEvent ? getSelector(lastEvent.path || lastEvent.target) : "",
});
}
},
true
);Promise 异常
javascript
window.addEventListener(
"unhandledrejection",
function (event) {
let message = "";
let file = "";
let line = 0;
let column = 0;
let stack = "";
if (typeof event.reason === "string") {
message = event.reason;
} else if (typeof event.reason === "object") {
message = event.reason.message;
}
let reason = event.reason;
if (typeof reason === "object" && reason.stack) {
var matchResult = reason.stack.match(/at\s+(.+):(\d+):(\d+)/);
if (matchResult) {
file = matchResult[1];
line = matchResult[2];
column = matchResult[3];
}
stack = getLines(reason.stack);
}
tracker.send({
kind: "stability",
type: "error",
errorType: "promiseError",
message: message,
filename: file,
position: line + ":" + column,
stack,
selector: lastEvent ? getSelector(lastEvent.path || lastEvent.target) : "",
});
},
true
);2. 接口异常监控
XMLHttpRequest 拦截
javascript
export function injectXHR() {
let XMLHttpRequest = window.XMLHttpRequest;
let oldOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (
method,
url,
async,
username,
password
) {
if (!url.match(/logstores/) && !url.match(/sockjs/)) {
this.logData = { method, url, async, username, password };
}
return oldOpen.apply(this, arguments);
};
let oldSend = XMLHttpRequest.prototype.send;
let start;
XMLHttpRequest.prototype.send = function (body) {
if (this.logData) {
start = Date.now();
let handler = (type) => (event) => {
let duration = Date.now() - start;
let status = this.status;
let statusText = this.statusText;
tracker.send({
kind: "stability",
type: "xhr",
eventType: type,
pathname: this.logData.url,
status: status + "-" + statusText,
duration: "" + duration,
response: this.response ? JSON.stringify(this.response) : "",
params: body || "",
});
};
this.addEventListener("load", handler("load"), false);
this.addEventListener("error", handler("error"), false);
this.addEventListener("abort", handler("abort"), false);
}
oldSend.apply(this, arguments);
};
}Fetch API 拦截
javascript
export function injectFetch() {
let oldFetch = window.fetch;
function hijackFetch(url, options) {
let startTime = Date.now();
return new Promise((resolve, reject) => {
oldFetch.apply(this, [url, options]).then(async (response) => {
const oldResponseJson = response.__proto__.json;
response.__proto__.json = function (...responseRest) {
return new Promise((responseResolve, responseReject) => {
oldResponseJson.apply(this, responseRest).then(
(result) => {
responseResolve(result);
},
(responseRejection) => {
sendLogData({
url,
startTime,
statusText: response.statusText,
status: response.status,
eventType: "error",
response: responseRejection.stack,
options,
});
responseReject(responseRejection);
}
);
});
};
resolve(response);
}, rejection => {
sendLogData({
url,
startTime,
eventType: "load",
response: rejection.stack,
options,
});
reject(rejection);
});
});
}
window.fetch = hijackFetch;
}3. 白屏检测
使用 elementsFromPoint API 检测页面是否白屏:
javascript
export function blankScreen() {
let NUM = 20; // 控制检测密集度
let wrapperElements = ["html", "body", "#container"];
let emptyPoints = 0;
function isWrapper(element) {
if (!element) {
emptyPoints++;
return;
}
let selector = getSelector(element);
if (wrapperElements.indexOf(selector) !== -1) {
emptyPoints++;
}
}
onload(function () {
const portion = NUM + 1;
const xPortion = window.innerWidth / portion;
const yPortion = window.innerHeight / portion;
for (let i = 0; i < NUM; i++) {
let xElements = document.elementFromPoint(xPortion * i, window.innerHeight / 2);
let yElements = document.elementFromPoint(window.innerWidth / 2, yPortion * i);
let xyDownElements = document.elementFromPoint(xPortion * i, yPortion * i);
let xyUpElements = document.elementFromPoint(xPortion * i, yPortion * (NUM - i));
isWrapper(xElements);
isWrapper(yElements);
isWrapper(xyDownElements);
isWrapper(xyUpElements);
}
// 检测到白屏
if (emptyPoints == 4 * NUM) {
const centerElements = document.elementsFromPoint(
window.innerWidth / 2,
window.innerHeight / 2
);
tracker.send({
kind: "stability",
type: "blank",
emptyPoints: emptyPoints + "",
screen: window.screen.width + "X" + window.screen.height,
viewPoint: window.innerWidth + "X" + window.innerHeight,
selector: getSelector(centerElements[0]),
});
}
});
}4. 性能指标监控
javascript
export function timing() {
onload(function () {
setTimeout(() => {
const {
fetchStart,
connectStart,
connectEnd,
requestStart,
responseStart,
responseEnd,
domLoading,
domInteractive,
domContentLoadedEventStart,
domContentLoadedEventEnd,
loadEventStart,
} = performance.timing;
tracker.send({
kind: "experience",
type: "timing",
connectTime: connectEnd - connectStart, // TCP连接耗时
ttfbTime: responseStart - requestStart, // 首字节到达时间
responseTime: responseEnd - responseStart, // Response响应耗时
parseDOMTime: loadEventStart - domLoading, // DOM解析耗时
domContentLoadedTime: domContentLoadedEventEnd - domContentLoadedEventStart, // DOMContentLoaded事件耗时
timeToInteractive: domInteractive - fetchStart, // 首次可交互时间
loadTime: loadEventStart - fetchStart, // 完整的加载时间
});
}, 3000);
});
}5. 卡顿检测 (FPS)
javascript
export function fps() {
let frame = 0;
let lastSecond = performance.now();
let lastFrameTime = performance.now();
let longTaskDuration = 0;
function calculateFPS(currentTime) {
frame++;
const offset = currentTime - lastSecond;
if (offset >= 1000) {
const fps = Math.round((frame * 1000) / offset);
if (fps < 20) {
tracker.send({
kind: "experience",
type: "fps",
fps: fps,
longTaskDuration: longTaskDuration,
});
}
frame = 0;
longTaskDuration = 0;
lastSecond = currentTime;
}
// 检测长任务 (>50ms)
const frameDuration = currentTime - lastFrameTime;
if (frameDuration > 50) {
longTaskDuration += frameDuration;
}
lastFrameTime = currentTime;
requestAnimationFrame(calculateFPS);
}
requestAnimationFrame(calculateFPS);
}数据上报格式
JS 错误数据
json
{
"title": "前端监控系统",
"url": "http://localhost:8080/",
"timestamp": "1590815288710",
"userAgent": "Chrome",
"kind": "stability",
"type": "error",
"errorType": "jsError",
"message": "Uncaught TypeError: Cannot set property 'error' of undefined",
"filename": "http://localhost:8080/",
"position": "0:0",
"stack": "btnClick (http://localhost:8080/:20:39)^HTMLInputElement.onclick...",
"selector": "HTML BODY #container .content INPUT"
}接口监控数据
json
{
"title": "前端监控系统",
"url": "http://localhost:8080/",
"timestamp": "1590817024490",
"userAgent": "Chrome",
"kind": "stability",
"type": "xhr",
"eventType": "load",
"pathname": "/api/user",
"status": "200-OK",
"duration": "7",
"response": "{\"id\":1}",
"params": "name=zhufeng"
}埋点方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 代码埋点 | 精确,任意时刻,数据量全面 | 代码工作量大 |
| 可视化埋点 | 业务代码和埋点代码分离,系统化管理 | 需要额外系统支持 |
| 无痕埋点 | 采集全量数据,不会漏埋和误埋 | 数据传输和服务器压力大,无法灵活定制数据结构 |
项目结构
text
monitor/
├── public/ # 前端监控 SDK
├── src/ # 源码目录
│ ├── index.js # 入口文件
│ ├── utils/ # 工具函数
│ └── monitor/ # 监控模块
├── server.js # 服务端接收脚本
├── package.json
└── README.md快速开始
安装依赖
bash
# 克隆项目
git clone https://github.com/miracle90/monitor.git
cd monitor
# 安装依赖
npm install
# 或
yarn启动服务
bash
node server.js接入监控 SDK
html
<script src="./monitor/index.js"></script>
<script>
// 初始化监控
monitor.init({
url: 'http://localhost:3000/log',
appId: 'your-app-id'
});
</script>适用场景
- 大型 Web 应用线上监控
- 微前端架构下的统一监控
- 需要自定义监控指标的项目
- 学习前端监控体系实现原理
项目链接
- GitHub 仓库: https://github.com/miracle90/monitor
- Star 数: 860+
- Fork 数: 163
- 主要语言: JavaScript
总结
miracle90/monitor 是一个功能完整、实现清晰的前端监控开源项目,涵盖了错误监控、性能监控、白屏检测、卡顿检测等核心功能。项目代码结构清晰,注释详细,非常适合作为学习前端监控体系实现的参考资料,也可作为实际项目的监控方案基础。
分享: