前端错误监听与上报框架工作原理,如:Sentry
文章目录
- 前言
- 🔧 一、核心监听方式(浏览器层)
- 1. 拦截同步错误:`window.onerror`
- 2. 拦截异步错误:`window.addEventListener('unhandledrejection')`
- 3. 覆写全局 API:如 `console.error`、`XMLHttpRequest.prototype.send`
- ⚙️ 二、框架适配层(Vue/React/Angular)
- Vue 示例(Vue 2 / Vue 3)
- React 示例(ErrorBoundary)
- 🧪 三、错误信息序列化 & 上报
- 错误标准格式:
- 上报方式:
- 🔄 四、额外能力(增强稳定性与分析能力)
- 1. Source Map 支持
- 2. Breadcrumbs(用户行为轨迹)
- 3. 采样与过滤
- 📦 五、Sentry SDK 封装架构简要流程
- ✅ 总结一句话:
- 🧩 一、前端 SDK 最小实现
- 📁 文件结构
- 🔧 `error-sdk.js`
- 🧪 二、模拟错误触发(`index.html`)
- 🧰 三、后端错误日志接收(Node.js + Express)
- 📁 文件结构
- 🔧 `index.js`
- 📈 四、运行方式(测试用)
- 🚀 五、进阶扩展建议
- ✅ 总结
- Vue 3 错误查看平台
- 🧱 一、项目结构概览(Vue 3 + Vite)
- 🚀 二、主要技术点实现
- 1. 获取错误日志列表(支持时间过滤)
- 2. 日志聚类(前端聚类示例)
- 3. 时间过滤 UI 示例
- 4. 日志卡片组件(堆栈、行为轨迹回放)
- 5. 主视图:分页+聚类展示
- 📦 三、运行环境和后端配合
- 后端 `/api/logs` 响应格式:
- ✅ 四、进一步可扩展能力
前言
像 Sentry 这样的前端错误监听与上报框架,其核心是通过劫持浏览器原生错误处理机制,并拦截框架生命周期中的异常,最终将错误信息序列化后发送到后端进行归档与聚合。
以下是它的底层工作原理剖析:
🔧 一、核心监听方式(浏览器层)
1. 拦截同步错误:window.onerror
window.onerror = function (message, source, lineno, colno, error) {// 捕获运行时错误(语法/引用错误等)sendToServer({ message, source, lineno, colno, stack: error?.stack });
};
作用:
- 捕获未被 try/catch 包裹的错误
- 包含错误信息、脚本来源、行列号、错误堆栈
2. 拦截异步错误:window.addEventListener('unhandledrejection')
了解更多 :unhandledrejection | Web api MDN
window.addEventListener("unhandledrejection", event => {const reason = event.reason;sendToServer({ type: 'PromiseError', reason });
});
作用:
- 捕获未处理的 Promise 错误
- 包括
fetch(...).then().then()
链中报错但未.catch()
的情况
3. 覆写全局 API:如 console.error
、XMLHttpRequest.prototype.send
部分监控库还会:
- 重写
console.error
,记录开发者手动抛出的信息 - Hook
fetch
/XMLHttpRequest
以监听请求失败
⚙️ 二、框架适配层(Vue/React/Angular)
Vue 示例(Vue 2 / Vue 3)
Vue.config.errorHandler = (err, vm, info) => {sendToServer({message: err.message,stack: err.stack,component: vm.$options.name || '(anonymous)',info});
};
- Vue 把组件渲染和事件处理等错误暴露给
errorHandler
- Sentry 会自动注册这个钩子进行错误接管
React 示例(ErrorBoundary)
class ErrorBoundary extends React.Component {componentDidCatch(error, info) {sendToServer({ error, info });}...
}
- React 17+ 推荐使用
ErrorBoundary
组件包裹所有 UI 节点 - Sentry 提供开箱即用的
withErrorBoundary
高阶组件
🧪 三、错误信息序列化 & 上报
错误标准格式:
{"message": "Cannot read property 'x' of undefined","type": "TypeError","stack": "at Object.<anonymous> (main.js:10:15)","timestamp": 1684453410000,"browser": "Chrome 113","userAgent": "...","url": "https://xxx.com/page","breadcrumbs": [...], // 用户行为轨迹"extra": {...}
}
上报方式:
- Sentry 使用beacon / fetch / xhr等方式发送错误日志到后端
- 示例:
navigator.sendBeacon('/api/log', JSON.stringify(error));
- Sentry SDK 支持异步缓冲、重试、采样率控制等机制
🔄 四、额外能力(增强稳定性与分析能力)
1. Source Map 支持
- Sentry 可上传构建时生成的
.map
文件 - 将压缩代码中的错误还原为源码中的具体文件、行号、方法名等
2. Breadcrumbs(用户行为轨迹)
- 自动记录用户行为链,如点击、输入、页面跳转、XHR 请求等
- 每条错误日志都会带一份"历史行为快照"
3. 采样与过滤
- 默认并不记录所有错误,防止高频错误占满带宽
- 可配置采样比例(如 10% 上报)、忽略特定错误类型或路径
📦 五、Sentry SDK 封装架构简要流程
浏览器/框架异常↓
捕获器(window.onerror / unhandledrejection / Vue.errorHandler 等)↓
中间层(格式标准化 + 过滤 + 日志构造)↓
缓冲池(采样 + 批量 + 异步)↓
HTTP 上报(beacon/fetch/XHR)↓
Sentry 服务端聚合处理
✅ 总结一句话:
Sentry 的底层是基于浏览器和框架提供的全局异常钩子,结合源码映射、用户行为追踪和智能采样,完成完整的“采集→转化→上报→可视化”流程。
下面是一个最小可用的“类 Sentry 前端错误上报系统”示例,包括:
- ✅ 前端 SDK(监听、采集、上报)
- ✅ 后端服务(接收、保存、可视化展示)
- ✅ 进阶扩展建议(sourceMap、用户行为轨迹、异常采样)
🧩 一、前端 SDK 最小实现
📁 文件结构
/client/├── index.html├── error-sdk.js ← 错误监听与上报逻辑
🔧 error-sdk.js
(function () {const endpoint = 'https://your-server.com/api/log';function report(error) {const payload = {message: error.message || error,type: error.name || 'UnknownError',stack: error.stack || '',url: location.href,userAgent: navigator.userAgent,time: Date.now()};navigator.sendBeacon(endpoint, JSON.stringify(payload));}// 捕获同步错误window.onerror = function (msg, src, line, col, err) {report(err || msg);};// 捕获 Promise 错误window.addEventListener('unhandledrejection', event => {report(event.reason);});// 封装为全局对象供外部手动调用window.$ErrorSDK = {capture: report};
})();
🧪 二、模拟错误触发(index.html
)
<!DOCTYPE html>
<html>
<head><title>Test Error</title><script src="./error-sdk.js"></script>
</head>
<body><h1>点击按钮触发错误</h1><button onclick="throw new Error('测试错误!')">抛错</button>
</body>
</html>
🧰 三、后端错误日志接收(Node.js + Express)
📁 文件结构
/server/├── index.js ← 主服务├── db.json ← 模拟数据库
🔧 index.js
const express = require('express');
const fs = require('fs');
const app = express();
const logs = [];app.use(express.json({ limit: '1mb' }));
app.use(express.static(__dirname + '/public'));app.post('/api/log', (req, res) => {let raw = '';req.on('data', chunk => (raw += chunk));req.on('end', () => {try {const log = JSON.parse(raw);logs.push(log);fs.writeFileSync('db.json', JSON.stringify(logs, null, 2));res.sendStatus(204);} catch {res.sendStatus(400);}});
});// 简单页面查看日志
app.get('/logs', (req, res) => {const data = fs.readFileSync('db.json', 'utf-8');res.send(`<pre>${data}</pre>`);
});app.listen(3000, () => {console.log('🚀 Error log server running on http://localhost:3000');
});
📈 四、运行方式(测试用)
-
安装依赖:
npm install express
-
启动后端服务:
node index.js
-
打开
client/index.html
,点击按钮模拟异常 -
浏览器访问
http://localhost:3000/logs
查看错误信息
🚀 五、进阶扩展建议
功能 | 建议实现方式 |
---|---|
source map 解析 | 使用 Sentry CLI 或 source-map npm 包 |
用户行为 Breadcrumb | 记录点击、跳转、输入事件至数组 window.$SDK.track(event) 并附加上报 |
请求异常采集 | Hook XMLHttpRequest 与 fetch ,记录失败请求 |
错误去重与采样 | 哈希 message+stack 内容并设置 TTL;使用采样率避免刷爆日志 |
多端接入支持 | 提供 init({ appKey }) 和上传接口文档,兼容 React/Vue |
数据持久化 | 使用 SQLite / MongoDB / Elasticsearch 替代 db.json |
面板可视化 | 使用 Vue3/React + ECharts 可视化报错趋势、影响用户数等指标 |
✅ 总结
你现在已经拥有了一个:
- ✨ 监听错误、捕获堆栈、上报后端的前端 SDK
- 🧾 可接收并持久化日志的 Node 后端
- 📊 简单的错误列表 UI 页面
这就是最小版 Sentry 的工作模型。
Vue 3 错误查看平台
功能包括:
- ✅ 错误列表展示(分页 / 时间过滤)
- ✅ 错误聚类(按 message+stack 聚类)
- ✅ 堆栈信息展开查看
- ✅ 行为轨迹回放(breadcrumbs)
- ✅ 基于 Element Plus 组件库 + Pinia 状态管理(可选)
🧱 一、项目结构概览(Vue 3 + Vite)
error-dashboard/
├── public/
├── src/
│ ├── api/ ← 接口调用
│ ├── components/ ← UI 组件(日志卡片、时间过滤等)
│ ├── views/ ← 页面(ErrorList.vue)
│ ├── App.vue
│ └── main.ts
├── index.html
├── vite.config.ts
🚀 二、主要技术点实现
1. 获取错误日志列表(支持时间过滤)
// src/api/error.ts
import axios from 'axios';export interface ErrorLog {id: string;message: string;stack: string;type: string;time: number;url: string;userAgent: string;breadcrumbs: string[];
}export const getErrorLogs = (params: { startTime?: number; endTime?: number }) =>axios.get<ErrorLog[]>('/api/logs', { params });
2. 日志聚类(前端聚类示例)
// 简化版聚类函数:根据 message + stack 做分组
export function clusterLogs(logs: ErrorLog[]) {const map = new Map<string, ErrorLog[]>();logs.forEach(log => {const key = log.message + log.stack?.split('\n')[0]; // 可加 hashif (!map.has(key)) map.set(key, []);map.get(key)!.push(log);});return [...map.entries()].map(([key, group]) => ({key,count: group.length,latest: group.sort((a, b) => b.time - a.time)[0],samples: group}));
}
3. 时间过滤 UI 示例
<!-- src/components/TimeFilter.vue -->
<template><el-date-pickerv-model="range"type="daterange"unlink-panelsstart-placeholder="开始时间"end-placeholder="结束时间"@change="emitFilter"/>
</template><script setup>
import { ref } from 'vue';
const emit = defineEmits(['filter']);
const range = ref([]);
function emitFilter() {if (range.value.length === 2) {emit('filter', {startTime: new Date(range.value[0]).getTime(),endTime: new Date(range.value[1]).getTime()});}
}
</script>
4. 日志卡片组件(堆栈、行为轨迹回放)
<!-- src/components/ErrorCard.vue -->
<template><el-card class="mb-4"><div><strong>{{ log.message }}</strong><el-tag class="ml-2">{{ log.type }}</el-tag></div><div class="text-sm text-gray-500">{{ new Date(log.time).toLocaleString() }}</div><el-button text @click="showStack = !showStack">查看堆栈</el-button><el-button text @click="showBreadcrumb = !showBreadcrumb">行为轨迹</el-button><el-collapse v-if="showStack"><pre>{{ log.stack }}</pre></el-collapse><el-collapse v-if="showBreadcrumb"><ul><li v-for="(b, i) in log.breadcrumbs" :key="i">{{ b }}</li></ul></el-collapse></el-card>
</template><script setup>
import { ref } from 'vue';
defineProps(['log']);
const showStack = ref(false);
const showBreadcrumb = ref(false);
</script>
5. 主视图:分页+聚类展示
<!-- src/views/ErrorList.vue -->
<template><TimeFilter @filter="onFilter" /><div v-for="group in clusters" :key="group.key"><ErrorCard :log="group.latest" /><span class="text-sm text-gray-500">共 {{ group.count }} 次</span></div>
</template><script setup>
import { ref, onMounted } from 'vue';
import { getErrorLogs } from '@/api/error';
import { clusterLogs } from '@/utils/cluster';
import ErrorCard from '@/components/ErrorCard.vue';
import TimeFilter from '@/components/TimeFilter.vue';const logs = ref([]);
const clusters = ref([]);async function fetchLogs(params = {}) {const res = await getErrorLogs(params);logs.value = res.data;clusters.value = clusterLogs(logs.value);
}function onFilter(params) {fetchLogs(params);
}onMounted(() => fetchLogs());
</script>
📦 三、运行环境和后端配合
后端 /api/logs
响应格式:
[{"id": "uuid","message": "Uncaught TypeError: Cannot read property 'x'","stack": "at main.js:1:123","type": "TypeError","time": 1684928000000,"url": "https://xxx.com/page","userAgent": "...","breadcrumbs": ["click button", "fetch /api/user fail", ...]}
]
✅ 四、进一步可扩展能力
功能 | 实现方式建议 |
---|---|
搜索 / 排序 | 加入输入框 + 下拉框(Element UI Table 支持) |
权限访问 | 登录鉴权 + token 验证 |
图表展示错误趋势 | Vue + ECharts 绘制 bar/line 图 |
map 映射源码 | 后端集成 source-map npm 包,转堆栈回原文件 |
行为轨迹可视化 | 类似 DevTools 的 Timeline,做 step-by-step 回放 |