前言:那些让人抓狂的线上bug
作为一个前端开发者,我永远不会忘记第一次在生产环境遇到诡异bug时的绝望。那是一个周五的晚上,我正准备下班回家,突然收到运营同事的紧急电话:"网站挂了!用户都在投诉!"
登录后台一看,控制台里满屏的红色错误信息,用户界面一片空白。更让人崩溃的是,这些错误在开发环境和测试环境都从来没有出现过。那一刻,我深刻体会到了什么叫"线上一分钟,线下十年功"。
从那以后,我开始重视前端错误监控和异常处理,不再只是简单地用try-catch包裹代码,而是构建了一套完整的错误监控体系。今天,我想和大家分享一下我的经验和心得。
错误监控的重要性
1. 用户体验保障
用户遇到错误时,如果没有任何反馈,会感到困惑和沮丧。良好的错误处理能提升用户体验。
2. 问题快速定位
线上问题往往难以复现,完善的错误监控能帮助我们快速定位问题根源。
3. 数据驱动优化
通过错误数据分析,我们可以发现系统的薄弱环节,有针对性地进行优化。
4. 团队协作效率
统一的错误监控平台能让团队成员快速了解系统状态,提高协作效率。
JavaScript运行时错误监控
全局错误捕获
javascript
// 全局未捕获的JavaScript错误
window.addEventListener('error', function(event) {
console.error('JavaScript Error:', {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error,
stack: event.error?.stack
});
// 上报错误到监控服务
ErrorReporter.report({
type: 'javascript_error',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: Date.now()
});
});
// Promise未捕获的拒绝
window.addEventListener('unhandledrejection', function(event) {
console.error('Unhandled Promise Rejection:', {
reason: event.reason,
promise: event.promise
});
ErrorReporter.report({
type: 'unhandled_promise_rejection',
reason: event.reason?.message || String(event.reason),
stack: event.reason?.stack,
url: window.location.href,
timestamp: Date.now()
});
// 阻止默认的浏览器行为
event.preventDefault();
});
自定义错误类
javascript
// 基础错误类
class BaseError extends Error {
constructor(message, code, details = {}) {
super(message);
this.name = this.constructor.name;
this.code = code;
this.details = details;
this.timestamp = new Date().toISOString();
// 保证堆栈跟踪正确
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
toJSON() {
return {
name: this.name,
message: this.message,
code: this.code,
details: this.details,
timestamp: this.timestamp,
stack: this.stack
};
}
}
// 网络错误类
class NetworkError extends BaseError {
constructor(message, statusCode, url, details = {}) {
super(message, 'NETWORK_ERROR', {
statusCode,
url,
...details
});
this.statusCode = statusCode;
this.url = url;
}
}
// 业务逻辑错误类
class BusinessError extends BaseError {
constructor(message, businessCode, details = {}) {
super(message, 'BUSINESS_ERROR', details);
this.businessCode = businessCode;
}
}
// 验证错误类
class ValidationError extends BaseError {
constructor(field, value, rule, details = {}) {
super(`Validation failed for field: ${field}`, 'VALIDATION_ERROR', {
field,
value,
rule,
...details
});
this.field = field;
this.value = value;
this.rule = rule;
}
}
// 使用示例
try {
// 模拟业务逻辑
if (!userData.email) {
throw new ValidationError('email', userData.email, 'required');
}
const response = await fetch('/api/user', {
method: 'POST',
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new NetworkError(
'Failed to create user',
response.status,
response.url
);
}
const result = await response.json();
if (result.code !== 0) {
throw new BusinessError(result.message, result.code, result.data);
}
} catch (error) {
// 统一错误处理
ErrorHandler.handle(error);
}
异步操作错误处理
Promise链式调用的错误处理
javascript
class PromiseErrorHandler {
// 安全的Promise执行
static async safeExecute(promiseFn, fallbackValue = null) {
try {
const result = await promiseFn();
return { success: true, data: result };
} catch (error) {
console.error('Promise execution failed:', error);
return { success: false, error: error.message, data: fallbackValue };
}
}
// 带重试机制的Promise执行
static async retryExecute(promiseFn, maxRetries = 3, delay = 1000) {
let lastError;
for (let i = 0; i <= maxRetries; i++) {
try {
const result = await promiseFn();
return result;
} catch (error) {
lastError = error;
if (i === maxRetries) {
throw error;
}
console.warn(`Attempt ${i + 1} failed, retrying in ${delay}ms...`);
await this.sleep(delay * Math.pow(2, i)); // 指数退避
}
}
}
// Promise超时控制
static withTimeout(promise, timeout = 5000) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Operation timeout')), timeout)
)
]);
}
// 延迟函数
static sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 使用示例
async function fetchUserData(userId) {
try {
// 带超时控制的请求
const response = await PromiseErrorHandler.withTimeout(
fetch(`/api/users/${userId}`),
3000
);
if (!response.ok) {
throw new NetworkError('Failed to fetch user data', response.status);
}
return await response.json();
} catch (error) {
// 重试机制
return PromiseErrorHandler.retryExecute(
() => fetch(`/api/users/${userId}`),
3,
1000
);
}
}
async/await中的错误处理模式
javascript
// 错误处理装饰器模式
function errorHandler(errorHandlerFn) {
return function(target, propertyName, descriptor) {
const method = descriptor.value;
descriptor.value = async function(...args) {
try {
return await method.apply(this, args);
} catch (error) {
return await errorHandlerFn.call(this, error, ...args);
}
};
};
}
// 重试装饰器
function retry(maxRetries = 3, delay = 1000) {
return function(target, propertyName, descriptor) {
const method = descriptor.value;
descriptor.value = async function(...args) {
let lastError;
for (let i = 0; i <= maxRetries; i++) {
try {
return await method.apply(this, args);
} catch (error) {
lastError = error;
if (i === maxRetries) {
throw error;
}
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i)));
}
}
};
};
}
// 使用示例
class UserService {
@retry(3, 1000)
@errorHandler(async function(error, userId) {
console.error(`Failed to fetch user ${userId}:`, error);
// 记录错误并返回默认值
ErrorReporter.report({
type: 'user_fetch_error',
userId,
error: error.message,
stack: error.stack
});
return { id: userId, name: 'Unknown User' };
})
async fetchUser(userId) {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new NetworkError('User not found', response.status);
}
return response.json();
}
}
DOM操作和用户交互错误处理
事件处理器的错误防护
javascript
class SafeEventHandler {
// 安全的事件绑定
static addEventListener(element, event, handler, options = {}) {
const safeHandler = async function(event) {
try {
await handler.call(this, event);
} catch (error) {
console.error(`Error in ${event.type} handler:`, error);
ErrorReporter.report({
type: 'event_handler_error',
event: event.type,
element: element.tagName,
error: error.message,
stack: error.stack
});
// 显示用户友好的错误提示
UserFeedback.showError('操作失败,请稍后重试');
}
};
element.addEventListener(event, safeHandler, options);
return safeHandler;
}
// 批量事件绑定
static addEventListeners(element, events, handler, options = {}) {
const handlers = {};
Object.keys(events).forEach(event => {
handlers[event] = this.addEventListener(
element,
event,
events[event],
options
);
});
return handlers;
}
// 安全的表单提交
static async safeFormSubmit(form, submitHandler) {
const submitButton = form.querySelector('button[type="submit"]');
const originalText = submitButton?.textContent;
try {
// 显示加载状态
if (submitButton) {
submitButton.disabled = true;
submitButton.textContent = '提交中...';
}
// 执行提交逻辑
const result = await submitHandler(new FormData(form));
// 成功处理
UserFeedback.showSuccess('提交成功');
return result;
} catch (error) {
console.error('Form submission failed:', error);
// 错误处理
if (error instanceof ValidationError) {
this.showFieldErrors(form, error.details);
} else {
UserFeedback.showError('提交失败,请稍后重试');
}
ErrorReporter.report({
type: 'form_submission_error',
form: form.id,
error: error.message,
stack: error.stack
});
} finally {
// 恢复按钮状态
if (submitButton) {
submitButton.disabled = false;
submitButton.textContent = originalText;
}
}
}
// 显示字段错误
static showFieldErrors(form, errors) {
// 清除之前的错误
form.querySelectorAll('.field-error').forEach(el => el.remove());
form.querySelectorAll('.error').forEach(el => el.classList.remove('error'));
// 显示新的错误
Object.keys(errors).forEach(field => {
const fieldElement = form.querySelector(`[name="${field}"]`);
const error = errors[field];
if (fieldElement) {
fieldElement.classList.add('error');
const errorElement = document.createElement('div');
errorElement.className = 'field-error';
errorElement.textContent = error;
fieldElement.parentNode.appendChild(errorElement);
}
});
}
}
// 使用示例
const form = document.getElementById('user-form');
SafeEventHandler.addEventListener(form, 'submit', async function(e) {
e.preventDefault();
await SafeEventHandler.safeFormSubmit(form, async (formData) => {
const response = await fetch('/api/users', {
method: 'POST',
body: formData
});
if (!response.ok) {
const errorData = await response.json();
if (errorData.code === 'VALIDATION_ERROR') {
throw new ValidationError('Form validation failed', errorData.code, errorData.errors);
}
throw new NetworkError('Submission failed', response.status);
}
return response.json();
});
});
网络请求错误处理
统一的API客户端
javascript
class ApiClient {
constructor(baseURL, options = {}) {
this.baseURL = baseURL;
this.defaultOptions = {
timeout: 10000,
retries: 3,
...options
};
this.interceptors = {
request: [],
response: []
};
}
// 添加请求拦截器
addRequestInterceptor(interceptor) {
this.interceptors.request.push(interceptor);
}
// 添加响应拦截器
addResponseInterceptor(interceptor) {
this.interceptors.response.push(interceptor);
}
// 统一请求方法
async request(url, options = {}) {
const config = { ...this.defaultOptions, ...options };
let requestUrl = url.startsWith('http') ? url : `${this.baseURL}${url}`;
// 应用请求拦截器
for (const interceptor of this.interceptors.request) {
const result = await interceptor(requestUrl, config);
if (result) {
[requestUrl, config] = result;
}
}
// 执行请求
return this.executeRequest(requestUrl, config);
}
async executeRequest(url, config) {
let lastError;
for (let i = 0; i <= config.retries; i++) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), config.timeout);
const response = await fetch(url, {
...config,
signal: controller.signal
});
clearTimeout(timeoutId);
// 应用响应拦截器
let processedResponse = response;
for (const interceptor of this.interceptors.response) {
processedResponse = await interceptor(processedResponse);
}
// 检查HTTP状态
if (!processedResponse.ok) {
throw new NetworkError(
`HTTP ${processedResponse.status}: ${processedResponse.statusText}`,
processedResponse.status,
url
);
}
return await processedResponse.json();
} catch (error) {
lastError = error;
if (i === config.retries) {
throw error;
}
// 指数退避
await new Promise(resolve =>
setTimeout(resolve, 1000 * Math.pow(2, i))
);
}
}
}
// 便捷方法
get(url, options = {}) {
return this.request(url, { method: 'GET', ...options });
}
post(url, data, options = {}) {
return this.request(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
...options
});
}
put(url, data, options = {}) {
return this.request(url, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
...options
});
}
delete(url, options = {}) {
return this.request(url, { method: 'DELETE', ...options });
}
}
// 创建API客户端实例
const apiClient = new ApiClient('/api', {
timeout: 5000,
retries: 2
});
// 添加认证拦截器
apiClient.addRequestInterceptor(async (url, config) => {
const token = localStorage.getItem('auth_token');
if (token) {
config.headers = {
...config.headers,
'Authorization': `Bearer ${token}`
};
}
return [url, config];
});
// 添加错误处理拦截器
apiClient.addResponseInterceptor(async (response) => {
if (response.status === 401) {
// 处理未授权错误
localStorage.removeItem('auth_token');
window.location.href = '/login';
}
return response;
});
// 使用示例
async function fetchUserProfile(userId) {
try {
const user = await apiClient.get(`/users/${userId}`);
return user;
} catch (error) {
if (error instanceof NetworkError) {
switch (error.statusCode) {
case 404:
throw new BusinessError('用户不存在', 'USER_NOT_FOUND');
case 500:
UserFeedback.showError('服务器错误,请稍后重试');
break;
default:
UserFeedback.showError('网络错误,请检查连接');
}
}
throw error;
}
}
错误上报和监控系统
统一错误上报器
javascript
class ErrorReporter {
constructor(options = {}) {
this.options = {
endpoint: '/api/errors',
maxErrors: 100,
sampleRate: 1.0,
ignoreErrors: [],
...options
};
this.errorQueue = [];
this.isSending = false;
this.errorCount = new Map();
// 定期发送错误报告
setInterval(() => this.flush(), 30000);
}
// 上报错误
report(error, context = {}) {
// 采样控制
if (Math.random() > this.options.sampleRate) {
return;
}
// 错误去重
const errorKey = this.getErrorKey(error);
const count = this.errorCount.get(errorKey) || 0;
if (count > this.options.maxErrors) {
return; // 避免同一错误过度上报
}
this.errorCount.set(errorKey, count + 1);
// 构造错误报告
const report = {
id: this.generateId(),
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
language: navigator.language,
platform: navigator.platform,
...context
};
// 根据错误类型处理
if (error instanceof BaseError) {
report.type = 'application_error';
report.error = error.toJSON();
} else if (error instanceof Error) {
report.type = 'javascript_error';
report.error = {
name: error.name,
message: error.message,
stack: error.stack
};
} else {
report.type = 'unknown_error';
report.error = {
message: String(error),
stack: new Error().stack
};
}
this.errorQueue.push(report);
// 立即发送严重错误
if (this.isCriticalError(error)) {
this.flush();
}
}
// 生成错误唯一标识
getErrorKey(error) {
if (error instanceof BaseError) {
return `${error.name}:${error.message}:${error.code}`;
}
return `${error.name}:${error.message}`;
}
// 判断是否为严重错误
isCriticalError(error) {
const criticalErrors = [
'ChunkLoadError', // 资源加载失败
'SecurityError', // 安全错误
'QuotaExceededError' // 存储空间不足
];
return criticalErrors.includes(error.name);
}
// 发送错误报告
async flush() {
if (this.errorQueue.length === 0 || this.isSending) {
return;
}
this.isSending = true;
const errorsToSend = this.errorQueue.splice(0, 50); // 批量发送
try {
await fetch(this.options.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
errors: errorsToSend,
sessionId: this.getSessionId(),
userId: this.getUserId()
})
});
} catch (error) {
console.error('Failed to send error reports:', error);
// 发送失败时重新加入队列
this.errorQueue.unshift(...errorsToSend);
} finally {
this.isSending = false;
}
}
// 生成唯一ID
generateId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
// 获取会话ID
getSessionId() {
let sessionId = sessionStorage.getItem('session_id');
if (!sessionId) {
sessionId = this.generateId();
sessionStorage.setItem('session_id', sessionId);
}
return sessionId;
}
// 获取用户ID
getUserId() {
return localStorage.getItem('user_id') || null;
}
}
// 全局错误上报器实例
const errorReporter = new ErrorReporter({
endpoint: '/api/errors',
sampleRate: 0.1, // 10%采样率
maxErrors: 50
});
// 统一错误处理器
class ErrorHandler {
static handle(error, context = {}) {
// 记录错误
console.error('Application Error:', error);
// 上报错误
errorReporter.report(error, context);
// 用户反馈
if (this.shouldShowUserError(error)) {
UserFeedback.showError(this.getUserFriendlyMessage(error));
}
}
// 判断是否显示用户错误提示
static shouldShowUserError(error) {
const silentErrors = [
'AbortError', // 用户取消操作
'NotAllowedError' // 用户拒绝权限
];
return !silentErrors.includes(error.name);
}
// 获取用户友好的错误消息
static getUserFriendlyMessage(error) {
if (error instanceof NetworkError) {
return '网络连接异常,请检查网络后重试';
}
if (error instanceof BusinessError) {
return error.message;
}
if (error instanceof ValidationError) {
return `输入验证失败:${error.details.field}`;
}
return '操作失败,请稍后重试';
}
}
用户反馈和错误恢复
用户友好的错误提示
javascript
class UserFeedback {
constructor() {
this.container = this.createContainer();
this.toastQueue = [];
this.isShowing = false;
}
createContainer() {
let container = document.getElementById('feedback-container');
if (!container) {
container = document.createElement('div');
container.id = 'feedback-container';
container.className = 'feedback-container';
document.body.appendChild(container);
}
return container;
}
// 显示成功提示
showSuccess(message, duration = 3000) {
this.showToast({
type: 'success',
message,
duration
});
}
// 显示错误提示
showError(message, duration = 5000) {
this.showToast({
type: 'error',
message,
duration
});
}
// 显示警告提示
showWarning(message, duration = 4000) {
this.showToast({
type: 'warning',
message,
duration
});
}
// 显示信息提示
showInfo(message, duration = 3000) {
this.showToast({
type: 'info',
message,
duration
});
}
showToast(options) {
this.toastQueue.push(options);
this.processToastQueue();
}
async processToastQueue() {
if (this.isShowing || this.toastQueue.length === 0) {
return;
}
this.isShowing = true;
const toast = this.toastQueue.shift();
const toastElement = this.createToastElement(toast);
this.container.appendChild(toastElement);
// 动画显示
await this.animateIn(toastElement);
// 等待指定时间后隐藏
await new Promise(resolve => setTimeout(resolve, toast.duration));
// 动画隐藏
await this.animateOut(toastElement);
// 移除元素
toastElement.remove();
this.isShowing = false;
// 处理队列中的下一个toast
this.processToastQueue();
}
createToastElement(options) {
const element = document.createElement('div');
element.className = `toast toast-${options.type}`;
element.innerHTML = `
<div class="toast-icon">
${this.getIcon(options.type)}
</div>
<div class="toast-content">
<div class="toast-message">${options.message}</div>
</div>
<button class="toast-close" aria-label="关闭">×</button>
`;
// 绑定关闭事件
element.querySelector('.toast-close').addEventListener('click', () => {
this.hideToast(element);
});
return element;
}
getIcon(type) {
const icons = {
success: '✓',
error: '✕',
warning: '⚠',
info: 'ℹ'
};
return icons[type] || 'ℹ';
}
async animateIn(element) {
element.style.transform = 'translateX(100%)';
element.style.opacity = '0';
// 强制重排
element.offsetHeight;
element.style.transition = 'all 0.3s ease';
element.style.transform = 'translateX(0)';
element.style.opacity = '1';
return new Promise(resolve => {
setTimeout(resolve, 300);
});
}
async animateOut(element) {
element.style.transform = 'translateX(100%)';
element.style.opacity = '0';
return new Promise(resolve => {
setTimeout(resolve, 300);
});
}
async hideToast(element) {
await this.animateOut(element);
element.remove();
this.isShowing = false;
this.processToastQueue();
}
}
// 全局用户反馈实例
const userFeedback = new UserFeedback();
// CSS样式
const feedbackStyles = `
.feedback-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 10000;
pointer-events: none;
}
.toast {
display: flex;
align-items: center;
min-width: 300px;
max-width: 500px;
padding: 12px 16px;
margin-bottom: 10px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
pointer-events: auto;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.toast-success {
background-color: #f6ffed;
border: 1px solid #b7eb8f;
color: #52c41a;
}
.toast-error {
background-color: #fff2f0;
border: 1px solid #ffccc7;
color: #ff4d4f;
}
.toast-warning {
background-color: #fffbe6;
border: 1px solid #ffe58f;
color: #faad14;
}
.toast-info {
background-color: #e6f7ff;
border: 1px solid #91d5ff;
color: #1890ff;
}
.toast-icon {
font-size: 16px;
margin-right: 8px;
font-weight: bold;
}
.toast-content {
flex: 1;
font-size: 14px;
}
.toast-close {
background: none;
border: none;
font-size: 18px;
cursor: pointer;
padding: 0;
margin-left: 8px;
color: inherit;
opacity: 0.7;
}
.toast-close:hover {
opacity: 1;
}
`;
// 注入样式
const styleElement = document.createElement('style');
styleElement.textContent = feedbackStyles;
document.head.appendChild(styleElement);
错误监控面板
简单的错误统计面板
javascript
class ErrorDashboard {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.errors = [];
this.init();
}
init() {
this.render();
this.bindEvents();
// 定期更新数据
setInterval(() => this.updateData(), 30000);
}
render() {
this.container.innerHTML = `
<div class="error-dashboard">
<div class="dashboard-header">
<h2>错误监控面板</h2>
<button id="refresh-errors">刷新</button>
</div>
<div class="dashboard-stats">
<div class="stat-card">
<div class="stat-value" id="total-errors">0</div>
<div class="stat-label">总错误数</div>
</div>
<div class="stat-card">
<div class="stat-value" id="today-errors">0</div>
<div class="stat-label">今日错误</div>
</div>
<div class="stat-card">
<div class="stat-value" id="unique-errors">0</div>
<div class="stat-label">唯一错误</div>
</div>
</div>
<div class="error-list">
<h3>最近错误</h3>
<table id="errors-table">
<thead>
<tr>
<th>时间</th>
<th>类型</th>
<th>消息</th>
<th>页面</th>
<th>次数</th>
</tr>
</thead>
<tbody id="errors-tbody">
</tbody>
</table>
</div>
</div>
`;
this.addStyles();
}
addStyles() {
const styles = `
.error-dashboard {
font-family: Arial, sans-serif;
padding: 20px;
}
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.dashboard-stats {
display: flex;
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: #f5f5f5;
padding: 20px;
border-radius: 4px;
text-align: center;
flex: 1;
}
.stat-value {
font-size: 24px;
font-weight: bold;
margin-bottom: 5px;
}
.stat-label {
color: #666;
font-size: 14px;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background-color: #f8f8f8;
font-weight: bold;
}
button {
padding: 8px 16px;
background-color: #007cba;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #005a87;
}
`;
const styleSheet = document.createElement('style');
styleSheet.textContent = styles;
document.head.appendChild(styleSheet);
}
bindEvents() {
document.getElementById('refresh-errors').addEventListener('click', () => {
this.updateData();
});
}
async updateData() {
try {
// 模拟获取错误数据
const response = await fetch('/api/errors/stats');
const stats = await response.json();
this.updateStats(stats);
this.updateErrorList(stats.recentErrors);
} catch (error) {
console.error('Failed to fetch error stats:', error);
}
}
updateStats(stats) {
document.getElementById('total-errors').textContent = stats.total || 0;
document.getElementById('today-errors').textContent = stats.today || 0;
document.getElementById('unique-errors').textContent = stats.unique || 0;
}
updateErrorList(errors) {
const tbody = document.getElementById('errors-tbody');
tbody.innerHTML = errors.map(error => `
<tr>
<td>${new Date(error.timestamp).toLocaleString()}</td>
<td>${error.type}</td>
<td>${error.message}</td>
<td>${error.url}</td>
<td>${error.count}</td>
</tr>
`).join('');
}
}
// 在开发环境中启用错误监控面板
if (process.env.NODE_ENV === 'development') {
// 创建监控面板容器
const dashboardContainer = document.createElement('div');
dashboardContainer.id = 'error-dashboard-container';
document.body.appendChild(dashboardContainer);
// 初始化监控面板
new ErrorDashboard('error-dashboard-container');
}
结语:构建可靠的Web应用
错误监控和异常处理是构建可靠Web应用的重要组成部分。通过建立完善的错误处理体系,我们不仅能提升用户体验,还能快速发现和解决问题,保障系统的稳定运行。
回顾我从最初面对线上bug的手足无措,到现在建立起完整的错误监控体系,这个过程让我深刻认识到:
- 预防胜于治疗:良好的错误处理机制能将问题消灭在萌芽状态
- 用户至上:即使出现问题,也要给用户清晰的反馈和指引
- 数据驱动:通过错误数据分析,持续优化系统稳定性
- 团队协作:统一的错误处理规范能提升团队开发效率
现在,当我在深夜收到监控系统的错误告警时,我不再感到恐慌,而是能够快速定位问题并解决。这种从容来自于对错误处理体系的信心,也来自于对技术的深入理解。
希望这篇文章能帮助大家构建更加健壮的Web应用,让我们的用户享受到更好的产品体验。记住,一个没有错误监控的应用,就像一艘没有雷达的船,在黑暗中航行总是充满风险的。