前端错误监控与异常处理:构建健壮的Web应用

前言:那些让人抓狂的线上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="关闭">&times;</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的手足无措,到现在建立起完整的错误监控体系,这个过程让我深刻认识到:

  1. 预防胜于治疗:良好的错误处理机制能将问题消灭在萌芽状态
  2. 用户至上:即使出现问题,也要给用户清晰的反馈和指引
  3. 数据驱动:通过错误数据分析,持续优化系统稳定性
  4. 团队协作:统一的错误处理规范能提升团队开发效率

现在,当我在深夜收到监控系统的错误告警时,我不再感到恐慌,而是能够快速定位问题并解决。这种从容来自于对错误处理体系的信心,也来自于对技术的深入理解。

希望这篇文章能帮助大家构建更加健壮的Web应用,让我们的用户享受到更好的产品体验。记住,一个没有错误监控的应用,就像一艘没有雷达的船,在黑暗中航行总是充满风险的。

相关推荐
Entropy-Lee15 分钟前
JavaScript 语句和函数
开发语言·前端·javascript
Wcowin1 小时前
MkDocs文档日期插件【推荐】
前端·mkdocs
xw52 小时前
免费的个人网站托管-Cloudflare
服务器·前端
网安Ruler2 小时前
Web开发-PHP应用&Cookie脆弱&Session固定&Token唯一&身份验证&数据库通讯
前端·数据库·网络安全·php·渗透·红队
!win !2 小时前
免费的个人网站托管-Cloudflare
服务器·前端·开发工具
饺子不放糖2 小时前
基于BroadcastChannel的前端多标签页同步方案:让用户体验更一致
前端
饺子不放糖2 小时前
前端性能优化实战:从页面加载到交互响应的全链路优化
前端
Jackson__2 小时前
使用 ICE PKG 开发并发布支持多场景引用的 NPM 包
前端
cos2 小时前
FE Bits 前端周周谈 Vol.1|Hello World、TanStack DB 首个 Beta 版发布
前端·javascript·css