🎯 学习目标:掌握前端错误监控的完整体系,从错误捕获到预警通知的全流程实现
📊 难度等级 :中级-高级
🏷️ 技术标签 :
#错误监控
#前端调试
#异常处理
#Sentry
#日志分析
⏱️ 阅读时间:约7分钟
🌟 引言
在日常的前端开发中,你是否遇到过这样的困扰:
- 🌙 半夜被电话吵醒:"网站崩了,用户投诉一大堆!"
- 🔍 错误难以复现:用户说有问题,但你本地怎么都测不出来
- 📱 移动端黑盒:iOS Safari的错误信息少得可怜
- 🐛 Bug定位困难:错误堆栈信息不完整,找问题像大海捞针
今天分享5个前端错误监控的核心技巧,让你的错误监控体系更加完善!
💡 核心技巧详解
1. 全局错误捕获:让任何异常都逃不掉
🔍 应用场景
- 生产环境的JavaScript运行时错误监控
- Promise未捕获异常的统一处理
- 静态资源加载失败的监控
- 第三方库错误的捕获和上报
- React/Vue组件错误边界处理
❌ 常见问题
javascript
// ❌ 只监听了部分错误类型
window.onerror = (message, source, lineno, colno, error) => {
console.log('捕获到错误:', message);
};
// ❌ 忽略了Promise错误
fetch('/api/data').then(res => res.json()); // 可能抛出未捕获的Promise错误
// ❌ 错误信息不完整
// 缺少用户环境、页面状态等关键上下文信息
// ❌ 重复上报问题
// 同一个错误可能被多次捕获和上报
✅ 推荐方案
javascript
/**
* 全局错误监控初始化
* @description 设置全方位的错误捕获机制
*/
const initErrorMonitor = () => {
// 1. JavaScript运行时错误
window.addEventListener('error', (event) => {
const errorInfo = {
type: 'javascript',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack,
timestamp: Date.now(),
userAgent: navigator.userAgent,
url: window.location.href
};
reportError(errorInfo);
});
// 2. Promise未处理异常
window.addEventListener('unhandledrejection', (event) => {
const errorInfo = {
type: 'promise',
message: event.reason?.message || event.reason,
stack: event.reason?.stack,
timestamp: Date.now(),
url: window.location.href
};
reportError(errorInfo);
event.preventDefault();
});
// 3. 资源加载错误
window.addEventListener('error', (event) => {
if (event.target !== window) {
const errorInfo = {
type: 'resource',
message: `Resource failed: ${event.target.src}`,
element: event.target.tagName,
source: event.target.src,
timestamp: Date.now()
};
reportError(errorInfo);
}
}, true);
};
/**
* 错误上报函数
* @param {Object} errorInfo - 错误信息
*/
const reportError = (errorInfo) => {
// 过滤重复错误
if (isDuplicateError(errorInfo)) {
return;
}
// 发送错误报告
sendErrorReport(errorInfo);
};
/**
* 检查重复错误
*/
const errorCache = {};
const isDuplicateError = (errorInfo) => {
const errorKey = `${errorInfo.message}_${errorInfo.filename}_${errorInfo.lineno}`;
const now = Date.now();
if (errorCache[errorKey] && now - errorCache[errorKey] < 5000) {
return true; // 5秒内重复错误
}
errorCache[errorKey] = now;
return false;
};
/**
* 发送错误报告
*/
const sendErrorReport = (errorInfo) => {
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/errors', JSON.stringify(errorInfo));
} else {
fetch('/api/errors', {
method: 'POST',
body: JSON.stringify(errorInfo),
headers: { 'Content-Type': 'application/json' }
}).catch(() => {});
}
};
🔑 核心要点
- 全面覆盖:JavaScript错误、Promise异常、资源错误三大类型
- 信息完整:收集错误堆栈、文件位置、时间戳等关键信息
- 性能优化:使用事件委托,避免重复监听
- 跨域处理:为第三方脚本添加crossorigin属性
🚀 实际应用
javascript
// Vue3项目中的应用
const app = createApp(App);
initErrorMonitor();
app.mount('#app');
// 配置跨域脚本
// <script src="third-party.js" crossorigin="anonymous"></script>
// React错误边界
class ErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
reportError({
type: 'react',
message: error.message,
stack: error.stack,
componentStack: errorInfo.componentStack,
timestamp: Date.now()
});
}
render() {
if (this.state.hasError) {
return <div>Something went wrong.</div>;
}
return this.props.children;
}
}
2. 智能错误上报:数据质量决定监控效果
🔍 应用场景
- 错误信息的结构化处理
- 用户行为轨迹记录
- 设备环境信息收集
- 错误优先级分类
❌ 常见问题
javascript
// ❌ 上报信息不完整
fetch('/api/error', {
method: 'POST',
body: JSON.stringify({
message: error.message // 信息太少,无法定位问题
})
});
// ❌ 没有用户行为轨迹
// 无法知道用户做了什么操作导致错误
// ❌ 重复上报问题
// 同一个错误可能被多次上报,造成噪音
// ❌ 缺乏智能过滤
// 所有错误都上报,包括无关紧要的错误
✅ 推荐方案
javascript
/**
* 智能错误上报系统
* @description 收集完整的错误上下文信息,实现智能过滤和批量上报
*/
class ErrorReporter {
constructor() {
this.breadcrumbs = [];
this.errorQueue = [];
this.reportTimer = null;
this.initTracking();
}
/**
* 初始化行为追踪
*/
initTracking = () => {
// 追踪用户点击
document.addEventListener('click', (e) => {
this.addBreadcrumb({
type: 'click',
target: e.target.tagName,
timestamp: Date.now()
});
});
}
/**
* 添加错误到队列
*/
addError = (errorInfo) => {
if (!this.shouldReport(errorInfo)) return;
const report = this.generateReport(errorInfo);
this.errorQueue.push(report);
if (this.errorQueue.length >= 10) {
this.flushErrors();
}
}
/**
* 添加行为轨迹
*/
addBreadcrumb = (breadcrumb) => {
this.breadcrumbs.push({ ...breadcrumb, timestamp: Date.now() });
if (this.breadcrumbs.length > 20) this.breadcrumbs.shift();
}
/**
* 智能错误过滤
*/
shouldReport = (errorInfo) => {
if (errorInfo.message === 'Script error.') return false;
if (errorInfo.filename?.includes('extension://')) return false;
return true;
}
/**
* 生成错误报告
*/
generateReport = (errorInfo) => ({
...errorInfo,
errorId: btoa(`${errorInfo.message}_${errorInfo.filename}`).slice(0, 16),
user: { sessionId: Date.now().toString(36) },
page: { url: location.href, title: document.title },
breadcrumbs: [...this.breadcrumbs],
timestamp: Date.now()
})
/**
* 批量上报错误
*/
flushErrors = () => {
if (this.errorQueue.length === 0) return;
fetch('/api/errors/batch', {
method: 'POST',
body: JSON.stringify({ errors: this.errorQueue }),
headers: { 'Content-Type': 'application/json' }
});
this.errorQueue = [];
}
/**
* 获取会话ID
* @returns {string} 会话ID
*/
getSessionId = () => {
let sessionId = sessionStorage.getItem('sessionId');
if (!sessionId) {
sessionId = Date.now().toString(36) + Math.random().toString(36).substr(2);
sessionStorage.setItem('sessionId', sessionId);
}
return sessionId;
}
}
const errorReporter = new ErrorReporter();
🔑 核心要点
- 智能过滤:自动过滤无关紧要的错误,减少噪音干扰
- 批量上报:避免频繁网络请求,提升应用性能
- 完整上下文:错误信息、用户行为、设备环境一应俱全
- 行为轨迹:记录用户操作路径,便于错误复现
- 优先级处理:严重错误立即上报,普通错误批量处理
- 去重标识:生成唯一错误ID,便于统计和去重
🚀 实际应用
javascript
// 初始化智能错误上报器
const errorReporter = new ErrorReporter();
// 在全局错误监控中集成
window.addEventListener('error', (event) => {
const errorInfo = {
type: 'javascript',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack,
timestamp: Date.now()
};
// 使用智能上报
errorReporter.addError(errorInfo);
});
// 在业务代码中手动上报
try {
// 业务逻辑
await processUserData(userData);
} catch (error) {
errorReporter.addError({
type: 'business',
message: error.message,
stack: error.stack,
context: {
action: 'processUserData',
userId: userData.id
}
});
}
// 页面卸载时确保数据发送
window.addEventListener('beforeunload', () => {
errorReporter.flushErrors();
});
3. 错误分析与分类:从海量数据中找到关键问题
🔍 应用场景
- 海量错误数据的智能分析和分类
- 错误趋势的监控和预警
- 关键错误的自动识别和优先级排序
- 错误影响范围的评估和统计
- 错误根因分析和问题定位
❌ 常见问题
javascript
// ❌ 简单粗暴的错误统计
const errorStats = {
total: 1000,
today: 50
}; // 无法知道哪些错误最重要
// ❌ 没有错误分组
// 相同根因的错误被当作不同问题处理
// ❌ 缺乏智能分析
// 无法自动识别错误模式和趋势
// ❌ 优先级不明确
// 不知道应该优先处理哪些错误
✅ 推荐方案
javascript
/**
* 错误分析引擎
* @description 智能分析和分类错误数据
*/
class ErrorAnalyzer {
constructor() {
this.errorGroups = new Map();
this.errorTrends = [];
this.analysisRules = this.initAnalysisRules();
}
/**
* 初始化分析规则
* @returns {Array} 分析规则数组
*/
initAnalysisRules = () => [
{
name: 'critical_js_error',
pattern: /TypeError|ReferenceError|SyntaxError/,
priority: 'critical',
category: 'javascript'
},
{
name: 'network_error',
pattern: /fetch|XMLHttpRequest|Network/,
priority: 'high',
category: 'network'
},
{
name: 'resource_error',
pattern: /Resource.*failed/,
priority: 'medium',
category: 'resource'
},
{
name: 'third_party_error',
pattern: /Script error/,
priority: 'low',
category: 'third_party'
}
]
/**
* 分析错误报告
* @param {Object} errorReport - 错误报告
*/
analyzeError = (errorReport) => {
// 错误分组
const groupKey = this.generateGroupKey(errorReport);
this.addToGroup(groupKey, errorReport);
// 趋势分析
this.updateTrends(errorReport);
// 影响分析
this.analyzeImpact(errorReport);
// 应用分析规则
return this.applyAnalysisRules(errorReport);
}
/**
* 应用分析规则
* @param {Object} errorReport - 错误报告
* @returns {Object} 分析结果
*/
applyAnalysisRules = (errorReport) => {
const analysis = {
...errorReport,
category: 'unknown',
priority: 'low',
fingerprint: this.generateGroupKey(errorReport)
};
// 应用分析规则
for (const rule of this.analysisRules) {
if (rule.pattern.test(errorReport.message || '')) {
analysis.category = rule.category;
analysis.priority = rule.priority;
analysis.ruleName = rule.name;
break;
}
}
return analysis;
}
/**
* 生成错误分组键
* @param {Object} errorReport - 错误报告
* @returns {string} 分组键
*/
generateGroupKey = (errorReport) => {
// 基于错误消息和堆栈的关键部分生成分组键
const message = this.normalizeErrorMessage(errorReport.message);
const stackKey = this.extractStackKey(errorReport.stack);
return `${errorReport.type}_${message}_${stackKey}`;
}
/**
* 标准化错误消息
* @param {string} message - 原始错误消息
* @returns {string} 标准化后的消息
*/
normalizeErrorMessage = (message) => {
if (!message) return 'unknown';
// 移除动态内容(如ID、时间戳等)
return message
.replace(/\d+/g, 'N') // 数字替换为N
.replace(/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/gi, 'UUID') // UUID
.replace(/https?:\/\/[^\s]+/g, 'URL') // URL
.toLowerCase()
.slice(0, 100);
}
/**
* 提取堆栈关键信息
* @param {string} stack - 错误堆栈
* @returns {string} 堆栈关键信息
*/
extractStackKey = (stack) => {
if (!stack) return 'no-stack';
// 提取第一个有效的堆栈行(通常是错误发生的位置)
const lines = stack.split('\n');
for (const line of lines) {
if (line.includes('.js:') || line.includes('.ts:')) {
// 提取文件名和行号
const match = line.match(/([^/]+\.(js|ts)):(\d+):(\d+)/);
if (match) {
return `${match[1]}:${match[3]}`;
}
}
}
return 'unknown-stack';
}
/**
* 添加错误到分组
* @param {string} groupKey - 分组键
* @param {Object} errorReport - 错误报告
*/
addToGroup = (groupKey, errorReport) => {
if (!this.errorGroups.has(groupKey)) {
this.errorGroups.set(groupKey, {
key: groupKey,
count: 0,
firstSeen: errorReport.timestamp,
lastSeen: errorReport.timestamp,
affectedUsers: new Set(),
samples: [],
severity: 'low'
});
}
const group = this.errorGroups.get(groupKey);
group.count++;
group.lastSeen = errorReport.timestamp;
group.affectedUsers.add(errorReport.user?.id || 'anonymous');
// 保存错误样本(最多保存10个)
if (group.samples.length < 10) {
group.samples.push(errorReport);
}
// 更新严重程度
group.severity = this.calculateSeverity(group);
}
/**
* 计算错误严重程度
* @param {Object} group - 错误分组
* @returns {string} 严重程度
*/
calculateSeverity = (group) => {
const userCount = group.affectedUsers.size;
const errorCount = group.count;
const timeSpan = group.lastSeen - group.firstSeen;
// 基于影响用户数和错误频率计算严重程度
if (userCount > 100 || errorCount > 1000) {
return 'critical';
} else if (userCount > 10 || errorCount > 100) {
return 'high';
} else if (userCount > 1 || errorCount > 10) {
return 'medium';
} else {
return 'low';
}
}
/**
* 分析错误影响
*/
analyzeImpact = (errorReport) => {
const url = errorReport.page?.url || '';
const isPaymentRelated = url.includes('checkout') || url.includes('payment');
const isCriticalPath = ['/login', '/checkout', '/payment'].some(path => url.includes(path));
return {
severity: isPaymentRelated ? 'critical' : isCriticalPath ? 'high' : 'medium',
isPaymentRelated,
isCriticalPath,
affectedUsers: 1
};
}
/**
* 获取错误统计
*/
getErrorStats = () => {
const groups = Array.from(this.errorGroups.values());
return {
totalGroups: groups.length,
totalErrors: groups.reduce((sum, g) => sum + g.count, 0),
topErrors: groups.sort((a, b) => b.count - a.count).slice(0, 5),
severityDistribution: {
critical: groups.filter(g => g.severity === 'critical').length,
high: groups.filter(g => g.severity === 'high').length,
medium: groups.filter(g => g.severity === 'medium').length
}
};
}
}
/**
* 生成改进建议
* @param {Array} groups - 错误分组
* @returns {Array} 建议列表
*/
generateRecommendations = (groups) => {
const recommendations = [];
// 高严重程度错误建议
const criticalErrors = groups.filter(g => g.severity === 'critical');
if (criticalErrors.length > 0) {
recommendations.push({
type: 'urgent',
message: `发现 ${criticalErrors.length} 个严重错误,建议立即处理`,
errors: criticalErrors.slice(0, 3)
});
}
// 新错误建议
const recentErrors = groups.filter(g =>
Date.now() - g.firstSeen < 24 * 60 * 60 * 1000
);
if (recentErrors.length > 5) {
recommendations.push({
type: 'warning',
message: `24小时内出现 ${recentErrors.length} 个新错误,建议检查代码变更`,
errors: recentErrors.slice(0, 3)
});
}
// 用户影响建议
const highImpactErrors = groups.filter(g => g.affectedUsers.size > 10);
if (highImpactErrors.length > 0) {
recommendations.push({
type: 'info',
message: `${highImpactErrors.length} 个错误影响了大量用户,建议优先修复`,
errors: highImpactErrors.slice(0, 3)
});
}
return recommendations;
}
}
const errorAnalyzer = new ErrorAnalyzer();
🔑 核心要点
- 智能分组:基于错误特征自动分组,避免重复问题
- 严重程度评估:综合考虑影响用户数和错误频率
- 趋势分析:追踪错误变化趋势,及时发现异常
- 影响评估:从页面、用户、业务多个维度评估错误影响
- 规则引擎:基于预定义规则自动分类和优先级排序
- 智能建议:根据错误模式生成针对性的改进建议
🚀 实际应用
javascript
// 错误分析仪表板
class ErrorDashboard {
constructor() {
this.errorAnalyzer = new ErrorAnalyzer();
this.renderStats();
}
/**
* 渲染错误统计
*/
renderStats = () => {
const stats = this.errorAnalyzer.getErrorStats();
this.renderKPIs(stats);
this.renderTopErrors(stats.topErrors);
}
/**
* 渲染关键指标
*/
renderKPIs = (stats) => {
document.getElementById('error-kpis').innerHTML = `
<div class="kpi-card">
<h3>错误分组: ${stats.totalGroups}</h3>
<h3>总错误数: ${stats.totalErrors}</h3>
</div>
`;
}
/**
* 渲染Top错误
*/
renderTopErrors = (topErrors) => {
const errorList = topErrors.map(error =>
`<div class="error-item">${error.message} (${error.count}次)</div>`
).join('');
document.getElementById('top-errors').innerHTML = errorList;
}
}
const dashboard = new ErrorDashboard();
categoryFilter?.addEventListener('change', (e) => {
this.filterByCategory(e.target.value);
});
}
/**
* 按严重程度过滤
* @param {string} severity - 严重程度
*/
filterBySeverity = (severity) => {
const errorItems = document.querySelectorAll('.error-item');
errorItems.forEach(item => {
if (severity === 'all' || item.classList.contains(`severity-${severity}`)) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
}
/**
* 按分类过滤
* @param {string} category - 错误分类
*/
filterByCategory = (category) => {
// 实现分类过滤逻辑
console.log('Filter by category:', category);
}
/**
* 导出错误报告
*/
exportReport = () => {
const stats = this.errorAnalyzer.getErrorStats();
const reportData = {
timestamp: new Date().toISOString(),
summary: stats.summary,
topErrors: stats.topErrors,
recommendations: stats.recommendations
};
const blob = new Blob([JSON.stringify(reportData, null, 2)], {
type: 'application/json'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `error-report-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
}
}
// 初始化仪表板
const dashboard = new ErrorDashboard();
// 集成到现有错误监控中
window.addEventListener('error', (event) => {
const errorReport = {
type: 'javascript',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack,
timestamp: Date.now(),
page: {
url: window.location.href,
title: document.title
},
user: {
id: localStorage.getItem('userId') || 'anonymous',
sessionId: sessionStorage.getItem('sessionId')
},
device: {
userAgent: navigator.userAgent
}
};
dashboard.errorAnalyzer.analyzeError(errorReport);
});
4. 监控工具集成:选择合适的监控方案
🔍 应用场景
- 生产环境的专业错误监控和告警
- 多项目、多环境的统一监控管理
- 错误数据的可视化分析和报表
- 团队协作和错误处理流程管理
❌ 常见问题
- 工具选择困难:不知道选择哪种监控工具
- 配置复杂:监控工具配置繁琐,上手困难
- 数据孤岛:不同工具数据无法统一管理
- 成本过高:商业工具费用昂贵,开源工具功能有限
✅ 推荐方案
javascript
/**
* 监控工具集成管理器
*/
class MonitoringIntegration {
constructor() {
this.providers = new Map();
this.initSentry();
}
/**
* 初始化Sentry监控
*/
initSentry = () => {
import('@sentry/browser').then((Sentry) => {
Sentry.init({
dsn: process.env.REACT_APP_SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 0.1,
beforeSend: (event) => this.filterError(event) ? event : null
});
this.providers.set('sentry', Sentry);
});
}
/**
* 错误过滤
*/
filterError = (event) => {
// 过滤掉开发环境的错误
if (process.env.NODE_ENV === 'development') return false;
// 过滤掉网络错误
if (event.message?.includes('Network Error')) return false;
return true;
}
/**
* 捕获错误
*/
captureError = (error, context) => {
this.providers.forEach(provider => {
provider.captureException?.(error, context);
});
}
}
}
const monitoring = new MonitoringIntegration();
🎯 核心要点
- 多平台支持:同时集成Sentry和自定义监控平台,避免单点故障
- 智能过滤:过滤掉无用的错误信息,提高数据质量和分析效率
- 上下文丰富:自动添加用户、会话等上下文信息,便于问题定位
- 容错处理:监控平台本身的错误不影响主业务逻辑
- 配置灵活:支持不同环境的差异化配置和采样率设置
🚀 实际应用
javascript
// 在React组件中使用
import { captureException, setUser, addBreadcrumb } from './monitoring';
const UserProfile = () => {
useEffect(() => {
// 设置用户信息
setUser({
id: user.id,
email: user.email,
username: user.username
});
// 添加页面访问面包屑
addBreadcrumb({
message: 'User visited profile page',
category: 'navigation',
level: 'info'
});
}, [user]);
const handleSubmit = async (data) => {
try {
await updateProfile(data);
// 记录成功操作
addBreadcrumb({
message: 'Profile updated successfully',
category: 'user.action',
level: 'info'
});
} catch (error) {
// 捕获业务错误
captureException(error, {
action: 'updateProfile',
formData: data,
userAgent: navigator.userAgent
});
showErrorMessage('更新失败,请重试');
}
};
};
5. 实时预警系统:第一时间发现问题
🔍 应用场景
- 错误率突增的实时监控和预警
- 关键业务流程的异常检测
- 用户体验指标的实时监控
- 多渠道通知和升级机制
❌ 常见问题
- 预警不及时:发现问题时用户已经大量流失
- 预警过多:频繁的误报导致预警疲劳
- 信息不足:预警内容缺乏关键信息,无法快速定位
- 渠道单一:只有邮件通知,重要问题可能被忽略
✅ 推荐方案
javascript
/**
* 智能预警系统
* @description 基于多维度指标的智能预警
*/
class AlertSystem {
constructor() {
this.rules = [];
this.alertHistory = new Map();
this.channels = new Map();
this.init();
}
/**
* 初始化预警系统
*/
init = () => {
this.setupAlertRules();
this.setupNotificationChannels();
this.startMonitoring();
}
/**
* 设置预警规则
*/
setupAlertRules = () => {
// 错误率预警
this.addRule({
id: 'error-rate-spike',
name: '错误率突增',
condition: (metrics) => {
const currentRate = metrics.errorRate;
const baselineRate = metrics.baselineErrorRate;
return currentRate > baselineRate * 3 && currentRate > 0.01; // 错误率超过基线3倍且大于1%
},
severity: 'high',
cooldown: 300000, // 5分钟冷却期
channels: ['email', 'slack', 'sms']
});
// 关键错误预警
this.addRule({
id: 'critical-error',
name: '关键错误',
condition: (metrics) => {
return metrics.criticalErrors > 0;
},
severity: 'critical',
cooldown: 0, // 立即预警
channels: ['email', 'slack', 'sms', 'phone']
});
// 用户影响预警
this.addRule({
id: 'user-impact',
name: '大量用户受影响',
condition: (metrics) => {
return metrics.affectedUsers > 100;
},
severity: 'high',
cooldown: 600000, // 10分钟冷却期
channels: ['email', 'slack']
});
// 页面可用性预警
this.addRule({
id: 'page-availability',
name: '页面不可用',
condition: (metrics) => {
return metrics.pageErrorRate > 0.5; // 页面错误率超过50%
},
severity: 'critical',
cooldown: 180000, // 3分钟冷却期
channels: ['email', 'slack', 'sms']
});
}
/**
* 设置通知渠道
*/
setupNotificationChannels = () => {
// 邮件通知
this.channels.set('email', {
send: async (alert) => {
await fetch('/api/notifications/email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
to: process.env.REACT_APP_ALERT_EMAIL,
subject: `[${alert.severity.toUpperCase()}] ${alert.title}`,
html: this.generateEmailTemplate(alert)
})
});
}
});
// Slack通知
this.channels.set('slack', {
send: async (alert) => {
await fetch(process.env.REACT_APP_SLACK_WEBHOOK, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `🚨 ${alert.title}`,
attachments: [{
color: this.getSeverityColor(alert.severity),
fields: [
{ title: '错误详情', value: alert.description, short: false },
{ title: '影响用户', value: alert.metrics.affectedUsers, short: true },
{ title: '错误次数', value: alert.metrics.errorCount, short: true },
{ title: '时间', value: new Date(alert.timestamp).toLocaleString(), short: true }
]
}]
})
});
}
});
// 短信通知
this.channels.set('sms', {
send: async (alert) => {
await fetch('/api/notifications/sms', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
phone: process.env.REACT_APP_ALERT_PHONE,
message: `[${alert.severity.toUpperCase()}] ${alert.title}: ${alert.description}`
})
});
}
});
}
/**
* 添加预警规则
* @param {Object} rule - 预警规则
*/
addRule = (rule) => {
this.rules.push(rule);
}
/**
* 开始监控
*/
startMonitoring = () => {
// 每分钟检查一次
setInterval(() => {
this.checkAlerts();
}, 60000);
}
/**
* 检查预警条件
*/
checkAlerts = async () => {
try {
const metrics = await this.collectMetrics();
for (const rule of this.rules) {
if (rule.condition(metrics)) {
await this.triggerAlert(rule, metrics);
}
}
} catch (error) {
console.error('Alert check failed:', error);
}
}
/**
* 收集监控指标
* @returns {Object} 监控指标
*/
collectMetrics = async () => {
const response = await fetch('/api/monitoring/metrics');
const data = await response.json();
return {
errorRate: data.errorRate,
baselineErrorRate: data.baselineErrorRate,
errorCount: data.errorCount,
affectedUsers: data.affectedUsers,
criticalErrors: data.criticalErrors,
pageErrorRate: data.pageErrorRate,
responseTime: data.responseTime,
timestamp: Date.now()
};
}
/**
* 触发预警
* @param {Object} rule - 预警规则
* @param {Object} metrics - 监控指标
*/
triggerAlert = async (rule, metrics) => {
// 检查冷却期
if (this.isInCooldown(rule.id)) {
return;
}
const alert = {
id: `${rule.id}_${Date.now()}`,
ruleId: rule.id,
title: rule.name,
description: this.generateAlertDescription(rule, metrics),
severity: rule.severity,
metrics,
timestamp: Date.now(),
channels: rule.channels
};
// 记录预警历史
this.alertHistory.set(rule.id, Date.now());
// 发送通知
await this.sendNotifications(alert);
// 记录预警日志
console.warn('Alert triggered:', alert);
}
/**
* 检查是否在冷却期内
* @param {string} ruleId - 规则ID
* @returns {boolean} 是否在冷却期
*/
isInCooldown = (ruleId) => {
const lastAlert = this.alertHistory.get(ruleId);
if (!lastAlert) return false;
const rule = this.rules.find(r => r.id === ruleId);
const cooldownPeriod = rule?.cooldown || 300000; // 默认5分钟
return Date.now() - lastAlert < cooldownPeriod;
}
/**
* 生成预警描述
* @param {Object} rule - 预警规则
* @param {Object} metrics - 监控指标
* @returns {string} 预警描述
*/
generateAlertDescription = (rule, metrics) => {
switch (rule.id) {
case 'error-rate-spike':
return `错误率从 ${(metrics.baselineErrorRate * 100).toFixed(2)}% 上升到 ${(metrics.errorRate * 100).toFixed(2)}%`;
case 'critical-error':
return `检测到 ${metrics.criticalErrors} 个关键错误`;
case 'user-impact':
return `${metrics.affectedUsers} 个用户受到影响`;
case 'page-availability':
return `页面错误率达到 ${(metrics.pageErrorRate * 100).toFixed(2)}%`;
default:
return '检测到异常情况';
}
}
/**
* 发送通知
* @param {Object} alert - 预警信息
*/
sendNotifications = async (alert) => {
const promises = alert.channels.map(async (channelName) => {
const channel = this.channels.get(channelName);
if (channel) {
try {
await channel.send(alert);
} catch (error) {
console.error(`Failed to send alert via ${channelName}:`, error);
}
}
});
await Promise.allSettled(promises);
}
/**
* 生成邮件模板
* @param {Object} alert - 预警信息
* @returns {string} HTML邮件内容
*/
generateEmailTemplate = (alert) => {
return `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<div style="background: ${this.getSeverityColor(alert.severity)}; color: white; padding: 20px; text-align: center;">
<h1>🚨 ${alert.title}</h1>
</div>
<div style="padding: 20px; background: #f9f9f9;">
<h2>错误详情</h2>
<p>${alert.description}</p>
<h3>关键指标</h3>
<ul>
<li>影响用户数: ${alert.metrics.affectedUsers}</li>
<li>错误次数: ${alert.metrics.errorCount}</li>
<li>错误率: ${(alert.metrics.errorRate * 100).toFixed(2)}%</li>
<li>发生时间: ${new Date(alert.timestamp).toLocaleString()}</li>
</ul>
<div style="margin-top: 30px; padding: 15px; background: #fff; border-left: 4px solid ${this.getSeverityColor(alert.severity)};">
<strong>建议操作:</strong>
<ol>
<li>立即检查错误监控平台</li>
<li>分析错误日志和用户反馈</li>
<li>如需要,准备回滚方案</li>
</ol>
</div>
</div>
</div>
`;
}
/**
* 获取严重程度对应的颜色
* @param {string} severity - 严重程度
* @returns {string} 颜色值
*/
getSeverityColor = (severity) => {
const colors = {
critical: '#dc3545',
high: '#fd7e14',
medium: '#ffc107',
low: '#28a745'
};
return colors[severity] || '#6c757d';
}
/**
* 手动触发预警测试
* @param {string} ruleId - 规则ID
*/
testAlert = async (ruleId) => {
const rule = this.rules.find(r => r.id === ruleId);
if (!rule) {
throw new Error(`Rule ${ruleId} not found`);
}
const testMetrics = {
errorRate: 0.05,
baselineErrorRate: 0.01,
errorCount: 100,
affectedUsers: 50,
criticalErrors: 1,
pageErrorRate: 0.3,
timestamp: Date.now()
};
await this.triggerAlert(rule, testMetrics);
}
}
const alertSystem = new AlertSystem();
export default alertSystem;
🎯 核心要点
- 多维度监控:错误率、用户影响、页面可用性等多个维度的综合监控
- 智能预警:基于基线对比和阈值的智能判断,减少误报
- 冷却机制:避免重复预警造成的干扰,提高预警质量
- 多渠道通知:邮件、Slack、短信等多种通知方式,确保及时响应
- 预警升级:根据严重程度自动选择合适的通知渠道
🚀 实际应用
javascript
// 在React应用中集成预警系统
import alertSystem from './alertSystem';
const MonitoringDashboard = () => {
const [alertStatus, setAlertStatus] = useState([]);
const [isTestingAlert, setIsTestingAlert] = useState(false);
useEffect(() => {
// 加载预警状态
loadAlertStatus();
// 设置实时更新
const interval = setInterval(loadAlertStatus, 30000);
return () => clearInterval(interval);
}, []);
/**
* 加载预警状态
*/
const loadAlertStatus = async () => {
try {
const response = await fetch('/api/monitoring/alerts/status');
const data = await response.json();
setAlertStatus(data.alerts);
} catch (error) {
console.error('Failed to load alert status:', error);
}
};
/**
* 测试预警功能
*/
const testAlert = async (ruleId) => {
setIsTestingAlert(true);
try {
await alertSystem.testAlert(ruleId);
message.success('预警测试已发送');
} catch (error) {
message.error('预警测试失败');
} finally {
setIsTestingAlert(false);
}
};
return (
<div className="monitoring-dashboard">
<h2>实时预警监控</h2>
{alertStatus.map(alert => (
<div key={alert.id} className={`alert-item ${alert.severity}`}>
<h3>{alert.title}</h3>
<p>{alert.description}</p>
<button onClick={() => testAlert(alert.ruleId)}>
测试预警
</button>
</div>
))}
</div>
);
};
📊 技巧对比总结
技巧 | 解决问题 | 核心价值 | 实施难度 | 效果评级 |
---|---|---|---|---|
全局错误捕获 | 错误遗漏 | 100%错误覆盖 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
智能错误上报 | 信息不足 | 完整上下文 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
错误分析分类 | 问题定位难 | 智能分组 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
监控工具集成 | 平台局限 | 专业能力 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
实时预警系统 | 发现滞后 | 主动监控 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
🎯 实战应用建议
最佳实践
- 渐进式实施:从基础错误捕获开始,逐步完善监控体系
- 数据质量优先:宁可少而精,不要多而杂的错误信息
- 用户隐私保护:避免收集敏感信息,遵守数据保护法规
- 性能影响最小化:监控代码本身不能影响用户体验
- 团队协作:建立错误处理流程和责任分工机制
性能考虑
- 错误上报采用异步方式,避免阻塞主线程
- 设置合理的采样率,生产环境建议10%-20%
- 使用防抖机制避免重复上报相同错误
- 监控脚本加载失败时要有降级方案
💡 总结
这5个前端错误监控技巧在日常开发中能够显著提升问题发现和解决效率,掌握它们能让你的错误处理更加专业:
- 全局错误捕获:确保100%错误覆盖,不遗漏任何异常
- 智能错误上报:收集完整上下文,提供精准定位信息
- 错误分析分类:智能分组归类,快速识别关键问题
- 监控工具集成:利用专业平台,提升监控能力
- 实时预警系统:主动发现问题,第一时间响应处理
希望这些技巧能帮助你在前端开发中构建更可靠的错误监控体系,写出更稳定的代码!
🔗 相关资源
📚 推荐阅读
🛠️ 工具推荐
- Sentry - 专业的错误监控平台
- LogRocket - 用户会话回放和监控
- Bugsnag - 实时错误监控和报告
- Rollbar - 实时错误追踪和调试
📖 延伸学习
- 前端性能监控体系构建
- 用户行为分析与优化
- 微前端架构下的监控策略
- 移动端H5错误监控实践
💡 今日收获:掌握了5个前端错误监控技巧,这些知识点在实际开发中非常实用。
如果这篇文章对你有帮助,欢迎点赞、收藏和分享!有任何问题也欢迎在评论区讨论。 🚀