前端错误处理与日志系统完全指南

HTMLPAGE 团队
17 分钟阅读

构建完善的前端错误处理机制和日志系统,从错误捕获、分类处理到上报监控,打造可观测、易排查的现代前端应用。

#错误处理 #日志系统 #监控告警 #前端稳定性 #可观测性

前端错误处理与日志系统完全指南

为什么需要系统化的错误处理

在生产环境中,前端错误可能导致:

  • 用户操作中断,流失客户
  • 关键业务流程失败
  • 难以复现和排查的问题
  • 品牌形象受损
没有错误处理系统 vs 有完善的错误处理系统:

没有系统:
用户遇到错误 → 白屏/卡死 → 用户离开 → 问题无人知晓
                                          ↓
                                      问题持续存在

有系统:
用户遇到错误 → 优雅降级 → 用户看到友好提示 → 错误被捕获上报
                                              ↓
              团队收到告警 ← 错误数据分析 ← 日志系统记录
                   ↓
              快速定位修复 → 问题解决

错误类型分类

JavaScript 错误类型

// 1. 语法错误(SyntaxError)
// 在编译时就会被发现,通常不会到达运行时
eval('const x = ;'); // SyntaxError

// 2. 引用错误(ReferenceError)
console.log(undefinedVariable); // ReferenceError

// 3. 类型错误(TypeError)
null.toString(); // TypeError
undefined.map(x => x); // TypeError

// 4. 范围错误(RangeError)
new Array(-1); // RangeError
(123).toFixed(200); // RangeError

// 5. URI 错误(URIError)
decodeURIComponent('%'); // URIError

// 6. 自定义错误
class BusinessError extends Error {
  constructor(code, message) {
    super(message);
    this.name = 'BusinessError';
    this.code = code;
  }
}

按来源分类

错误来源分类:

┌─────────────────────────────────────────────────────────────┐
│                      前端错误来源                           │
├─────────────────┬─────────────────┬─────────────────────────┤
│   JavaScript    │    资源加载     │      网络请求           │
├─────────────────┼─────────────────┼─────────────────────────┤
│ • 运行时错误    │ • 脚本加载失败  │ • API 请求失败          │
│ • Promise 拒绝  │ • 样式加载失败  │ • 超时                  │
│ • 事件处理错误  │ • 图片加载失败  │ • 网络断开              │
│ • 异步错误      │ • 字体加载失败  │ • CORS 错误             │
└─────────────────┴─────────────────┴─────────────────────────┘
                           │
                           ↓
┌─────────────────────────────────────────────────────────────┐
│                      框架特定错误                           │
├─────────────────┬─────────────────┬─────────────────────────┤
│      Vue        │     React       │        通用             │
├─────────────────┼─────────────────┼─────────────────────────┤
│ • 组件渲染错误  │ • 渲染错误      │ • 路由错误              │
│ • 生命周期错误  │ • Hooks 错误    │ • 状态管理错误          │
│ • Watcher 错误  │ • 事件处理错误  │ • 第三方库错误          │
└─────────────────┴─────────────────┴─────────────────────────┘

全局错误捕获

原生 JavaScript 错误捕获

// 1. 同步错误捕获
window.onerror = function(message, source, lineno, colno, error) {
  console.error('Global Error:', {
    message,
    source,
    lineno,
    colno,
    stack: error?.stack
  });
  
  // 上报错误
  reportError({
    type: 'javascript',
    message,
    source,
    lineno,
    colno,
    stack: error?.stack
  });
  
  // 返回 true 阻止默认错误处理
  return true;
};

// 2. Promise 未捕获拒绝
window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled Promise Rejection:', event.reason);
  
  reportError({
    type: 'unhandledrejection',
    message: event.reason?.message || String(event.reason),
    stack: event.reason?.stack
  });
  
  // 阻止默认处理
  event.preventDefault();
});

// 3. 资源加载错误
window.addEventListener('error', (event) => {
  // 区分 JS 错误和资源加载错误
  if (event.target && (event.target.src || event.target.href)) {
    const target = event.target;
    
    reportError({
      type: 'resource',
      tagName: target.tagName,
      src: target.src || target.href,
      message: `Failed to load ${target.tagName.toLowerCase()}`
    });
  }
}, true); // 使用捕获阶段

Vue 3 错误处理

// plugins/errorHandler.ts
import type { App } from 'vue';

export function setupErrorHandler(app: App) {
  // 全局错误处理器
  app.config.errorHandler = (err, instance, info) => {
    console.error('Vue Error:', err);
    
    const error = {
      type: 'vue',
      message: err instanceof Error ? err.message : String(err),
      stack: err instanceof Error ? err.stack : undefined,
      componentName: instance?.$options?.name || 'Unknown',
      info, // 如 'setup function', 'render function' 等
      props: instance?.$props,
      route: instance?.$route?.fullPath
    };
    
    reportError(error);
  };
  
  // 警告处理器(开发环境)
  if (process.env.NODE_ENV !== 'production') {
    app.config.warnHandler = (msg, instance, trace) => {
      console.warn('Vue Warning:', msg);
      console.warn('Component trace:', trace);
    };
  }
}

// main.ts
import { createApp } from 'vue';
import { setupErrorHandler } from './plugins/errorHandler';

const app = createApp(App);
setupErrorHandler(app);
app.mount('#app');

Nuxt 3 错误处理

// plugins/error-handler.client.ts
export default defineNuxtPlugin((nuxtApp) => {
  // Vue 错误
  nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
    console.error('Vue Error:', error);
    
    // 使用 Nuxt 的错误处理
    showError({
      statusCode: 500,
      message: error instanceof Error ? error.message : 'Unknown error'
    });
    
    // 上报错误
    reportError({
      type: 'vue',
      error,
      info,
      componentName: instance?.$options?.name
    });
  };
  
  // 全局错误钩子
  nuxtApp.hook('vue:error', (error, instance, info) => {
    console.error('Nuxt Vue Error Hook:', error);
  });
  
  // 应用错误钩子
  nuxtApp.hook('app:error', (error) => {
    console.error('Nuxt App Error:', error);
  });
});

// error.vue - 全局错误页面
<script setup lang="ts">
const props = defineProps({
  error: Object
});

const handleError = () => clearError({ redirect: '/' });
</script>

<template>
  <div class="error-page">
    <h1>{{ error?.statusCode || 500 }}</h1>
    <p>{{ error?.message || '出错了' }}</p>
    <button @click="handleError">返回首页</button>
  </div>
</template>

错误边界组件

Vue 错误边界

<!-- components/ErrorBoundary.vue -->
<script setup lang="ts">
import { ref, onErrorCaptured } from 'vue';

const props = defineProps<{
  fallback?: string;
  onError?: (error: Error, info: string) => void;
}>();

const hasError = ref(false);
const errorMessage = ref('');

onErrorCaptured((error, instance, info) => {
  hasError.value = true;
  errorMessage.value = error.message;
  
  // 调用自定义错误处理
  props.onError?.(error, info);
  
  // 上报错误
  reportError({
    type: 'component',
    message: error.message,
    stack: error.stack,
    info
  });
  
  // 返回 false 阻止错误继续向上传播
  return false;
});

const retry = () => {
  hasError.value = false;
  errorMessage.value = '';
};
</script>

<template>
  <div v-if="hasError" class="error-boundary">
    <slot name="fallback">
      <div class="error-content">
        <h3>{{ fallback || '组件加载失败' }}</h3>
        <p class="error-message">{{ errorMessage }}</p>
        <button @click="retry">重试</button>
      </div>
    </slot>
  </div>
  <slot v-else />
</template>

<style scoped>
.error-boundary {
  padding: 20px;
  border: 1px solid #f5c6cb;
  background: #f8d7da;
  border-radius: 8px;
}
.error-content {
  text-align: center;
}
.error-message {
  color: #721c24;
  font-size: 14px;
}
</style>

使用错误边界

<template>
  <div class="app">
    <!-- 关键组件使用错误边界包裹 -->
    <ErrorBoundary 
      fallback="数据加载失败"
      @error="handleComponentError"
    >
      <DataVisualization :data="chartData" />
    </ErrorBoundary>
    
    <!-- 带自定义 fallback -->
    <ErrorBoundary>
      <template #fallback>
        <div class="custom-error">
          <img src="/error-illustration.svg" alt="Error" />
          <p>图表组件暂时无法显示</p>
          <button @click="refreshData">刷新数据</button>
        </div>
      </template>
      <ComplexChart :config="chartConfig" />
    </ErrorBoundary>
  </div>
</template>

<script setup>
const handleComponentError = (error, info) => {
  console.error('组件错误:', error);
  // 可以显示全局通知
  showNotification({
    type: 'error',
    message: '部分内容加载失败,请刷新重试'
  });
};
</script>

API 错误处理

统一的请求错误处理

// utils/request.ts
import axios, { AxiosError, AxiosResponse } from 'axios';

// 错误类型定义
interface ApiError {
  code: string;
  message: string;
  details?: Record<string, any>;
  timestamp: number;
  requestId?: string;
}

// 创建 axios 实例
const request = axios.create({
  baseURL: '/api',
  timeout: 30000,
  headers: {
    'Content-Type': 'application/json'
  }
});

// 请求拦截器
request.interceptors.request.use(
  (config) => {
    // 添加请求 ID 用于追踪
    config.headers['X-Request-ID'] = generateRequestId();
    
    // 添加认证 token
    const token = getAuthToken();
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    
    return config;
  },
  (error) => {
    reportError({
      type: 'request',
      phase: 'request',
      message: error.message
    });
    return Promise.reject(error);
  }
);

// 响应拦截器
request.interceptors.response.use(
  (response: AxiosResponse) => {
    return response.data;
  },
  (error: AxiosError<ApiError>) => {
    const errorInfo = handleApiError(error);
    
    // 上报错误
    reportError({
      type: 'api',
      ...errorInfo
    });
    
    return Promise.reject(errorInfo);
  }
);

// API 错误处理函数
function handleApiError(error: AxiosError<ApiError>) {
  // 网络错误
  if (!error.response) {
    if (error.code === 'ECONNABORTED') {
      return {
        code: 'TIMEOUT',
        message: '请求超时,请检查网络连接',
        retryable: true
      };
    }
    return {
      code: 'NETWORK_ERROR',
      message: '网络连接失败,请检查网络设置',
      retryable: true
    };
  }
  
  const { status, data } = error.response;
  const requestId = error.config?.headers?.['X-Request-ID'];
  
  // HTTP 状态码处理
  switch (status) {
    case 400:
      return {
        code: 'BAD_REQUEST',
        message: data?.message || '请求参数错误',
        details: data?.details,
        retryable: false
      };
      
    case 401:
      // 触发登出或刷新 token
      handleUnauthorized();
      return {
        code: 'UNAUTHORIZED',
        message: '登录已过期,请重新登录',
        retryable: false
      };
      
    case 403:
      return {
        code: 'FORBIDDEN',
        message: '没有权限执行此操作',
        retryable: false
      };
      
    case 404:
      return {
        code: 'NOT_FOUND',
        message: '请求的资源不存在',
        retryable: false
      };
      
    case 422:
      return {
        code: 'VALIDATION_ERROR',
        message: data?.message || '数据验证失败',
        details: data?.details,
        retryable: false
      };
      
    case 429:
      return {
        code: 'RATE_LIMITED',
        message: '请求过于频繁,请稍后再试',
        retryAfter: error.response.headers['retry-after'],
        retryable: true
      };
      
    case 500:
      return {
        code: 'SERVER_ERROR',
        message: '服务器错误,请稍后重试',
        requestId,
        retryable: true
      };
      
    case 502:
    case 503:
    case 504:
      return {
        code: 'SERVICE_UNAVAILABLE',
        message: '服务暂时不可用,请稍后重试',
        requestId,
        retryable: true
      };
      
    default:
      return {
        code: 'UNKNOWN_ERROR',
        message: data?.message || '发生未知错误',
        requestId,
        retryable: false
      };
  }
}

// 请求重试封装
async function requestWithRetry<T>(
  config: any,
  options: { maxRetries?: number; retryDelay?: number } = {}
): Promise<T> {
  const { maxRetries = 3, retryDelay = 1000 } = options;
  let lastError;
  
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await request(config);
    } catch (error: any) {
      lastError = error;
      
      // 不可重试的错误直接抛出
      if (!error.retryable || attempt === maxRetries) {
        throw error;
      }
      
      // 等待后重试
      await sleep(retryDelay * Math.pow(2, attempt)); // 指数退避
    }
  }
  
  throw lastError;
}

export { request, requestWithRetry };

日志系统设计

日志级别和格式

// utils/logger.ts

enum LogLevel {
  DEBUG = 0,
  INFO = 1,
  WARN = 2,
  ERROR = 3,
  FATAL = 4
}

interface LogEntry {
  level: LogLevel;
  message: string;
  timestamp: number;
  context?: Record<string, any>;
  userId?: string;
  sessionId?: string;
  pageUrl?: string;
  userAgent?: string;
  stack?: string;
}

class Logger {
  private static instance: Logger;
  private minLevel: LogLevel;
  private buffer: LogEntry[] = [];
  private bufferSize: number = 10;
  private flushInterval: number = 5000;
  private endpoint: string;
  
  private constructor() {
    this.minLevel = process.env.NODE_ENV === 'production' 
      ? LogLevel.INFO 
      : LogLevel.DEBUG;
    this.endpoint = '/api/logs';
    
    // 定时刷新日志
    setInterval(() => this.flush(), this.flushInterval);
    
    // 页面卸载前刷新
    window.addEventListener('beforeunload', () => this.flush());
  }
  
  static getInstance(): Logger {
    if (!Logger.instance) {
      Logger.instance = new Logger();
    }
    return Logger.instance;
  }
  
  private createEntry(
    level: LogLevel,
    message: string,
    context?: Record<string, any>
  ): LogEntry {
    return {
      level,
      message,
      timestamp: Date.now(),
      context,
      userId: getUserId(),
      sessionId: getSessionId(),
      pageUrl: window.location.href,
      userAgent: navigator.userAgent
    };
  }
  
  private log(level: LogLevel, message: string, context?: Record<string, any>) {
    if (level < this.minLevel) return;
    
    const entry = this.createEntry(level, message, context);
    
    // 开发环境输出到控制台
    if (process.env.NODE_ENV !== 'production') {
      const consoleFn = this.getConsoleFn(level);
      consoleFn(`[${LogLevel[level]}]`, message, context || '');
    }
    
    // 生产环境加入缓冲区
    if (process.env.NODE_ENV === 'production') {
      this.buffer.push(entry);
      
      // 缓冲区满或高优先级日志立即刷新
      if (this.buffer.length >= this.bufferSize || level >= LogLevel.ERROR) {
        this.flush();
      }
    }
  }
  
  private getConsoleFn(level: LogLevel) {
    switch (level) {
      case LogLevel.DEBUG: return console.debug;
      case LogLevel.INFO: return console.info;
      case LogLevel.WARN: return console.warn;
      case LogLevel.ERROR:
      case LogLevel.FATAL: return console.error;
      default: return console.log;
    }
  }
  
  private async flush() {
    if (this.buffer.length === 0) return;
    
    const logs = [...this.buffer];
    this.buffer = [];
    
    try {
      // 使用 sendBeacon 确保页面卸载时也能发送
      if (navigator.sendBeacon) {
        navigator.sendBeacon(
          this.endpoint,
          JSON.stringify({ logs })
        );
      } else {
        await fetch(this.endpoint, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ logs }),
          keepalive: true
        });
      }
    } catch (error) {
      // 发送失败,放回缓冲区
      this.buffer = [...logs, ...this.buffer].slice(0, this.bufferSize * 2);
    }
  }
  
  // 公开方法
  debug(message: string, context?: Record<string, any>) {
    this.log(LogLevel.DEBUG, message, context);
  }
  
  info(message: string, context?: Record<string, any>) {
    this.log(LogLevel.INFO, message, context);
  }
  
  warn(message: string, context?: Record<string, any>) {
    this.log(LogLevel.WARN, message, context);
  }
  
  error(message: string, context?: Record<string, any>) {
    this.log(LogLevel.ERROR, message, context);
  }
  
  fatal(message: string, context?: Record<string, any>) {
    this.log(LogLevel.FATAL, message, context);
  }
  
  // 性能日志
  performance(name: string, duration: number, context?: Record<string, any>) {
    this.info(`Performance: ${name}`, { ...context, duration });
  }
  
  // 用户行为日志
  track(event: string, properties?: Record<string, any>) {
    this.info(`Track: ${event}`, properties);
  }
}

export const logger = Logger.getInstance();

结构化日志

// 业务日志示例
import { logger } from '@/utils/logger';

// 用户操作日志
function handleLogin(username: string) {
  logger.info('User login attempt', { 
    username,
    method: 'password'
  });
  
  try {
    await loginApi({ username, password });
    logger.info('User login success', { username });
  } catch (error) {
    logger.error('User login failed', {
      username,
      errorCode: error.code,
      errorMessage: error.message
    });
  }
}

// 业务流程日志
async function processOrder(orderId: string) {
  const traceId = generateTraceId();
  
  logger.info('Order processing started', { orderId, traceId });
  
  try {
    // 验证库存
    logger.debug('Checking inventory', { orderId, traceId });
    await checkInventory(orderId);
    
    // 处理支付
    logger.debug('Processing payment', { orderId, traceId });
    const paymentResult = await processPayment(orderId);
    logger.info('Payment processed', { 
      orderId, 
      traceId,
      transactionId: paymentResult.transactionId 
    });
    
    // 创建发货单
    logger.debug('Creating shipment', { orderId, traceId });
    await createShipment(orderId);
    
    logger.info('Order processing completed', { orderId, traceId });
  } catch (error) {
    logger.error('Order processing failed', {
      orderId,
      traceId,
      step: error.step,
      error: error.message
    });
    throw error;
  }
}

// 性能日志
function measureApiCall() {
  const start = performance.now();
  
  try {
    const result = await fetchData();
    const duration = performance.now() - start;
    
    logger.performance('API fetchData', duration, {
      endpoint: '/api/data',
      resultCount: result.length
    });
    
    return result;
  } catch (error) {
    const duration = performance.now() - start;
    logger.performance('API fetchData (failed)', duration, {
      endpoint: '/api/data',
      error: error.message
    });
    throw error;
  }
}

错误上报系统

错误上报服务

// services/errorReporter.ts

interface ErrorReport {
  type: string;
  message: string;
  stack?: string;
  context?: Record<string, any>;
  timestamp: number;
  userId?: string;
  sessionId?: string;
  pageUrl: string;
  userAgent: string;
  screenSize: string;
  // 性能信息
  performance?: {
    loadTime?: number;
    memoryUsage?: number;
  };
}

class ErrorReporter {
  private endpoint: string;
  private queue: ErrorReport[] = [];
  private sending: boolean = false;
  private sampleRate: number;
  
  constructor(options: { endpoint: string; sampleRate?: number }) {
    this.endpoint = options.endpoint;
    this.sampleRate = options.sampleRate || 1; // 默认全量上报
    
    // 定时发送队列中的错误
    setInterval(() => this.processQueue(), 5000);
  }
  
  report(error: Partial<ErrorReport>) {
    // 采样
    if (Math.random() > this.sampleRate) return;
    
    const report: ErrorReport = {
      type: error.type || 'unknown',
      message: error.message || 'Unknown error',
      stack: error.stack,
      context: error.context,
      timestamp: Date.now(),
      userId: getUserId(),
      sessionId: getSessionId(),
      pageUrl: window.location.href,
      userAgent: navigator.userAgent,
      screenSize: `${window.screen.width}x${window.screen.height}`,
      performance: this.getPerformanceInfo()
    };
    
    // 去重处理
    if (this.isDuplicate(report)) return;
    
    this.queue.push(report);
    
    // 严重错误立即发送
    if (error.type === 'fatal' || error.type === 'unhandledrejection') {
      this.processQueue();
    }
  }
  
  private isDuplicate(report: ErrorReport): boolean {
    // 简单去重:相同错误在短时间内只上报一次
    const key = `${report.type}-${report.message}`;
    const lastReport = this.lastReports.get(key);
    
    if (lastReport && Date.now() - lastReport < 60000) {
      return true;
    }
    
    this.lastReports.set(key, Date.now());
    return false;
  }
  
  private lastReports = new Map<string, number>();
  
  private getPerformanceInfo() {
    if (typeof window === 'undefined') return undefined;
    
    const timing = performance.timing;
    const memory = (performance as any).memory;
    
    return {
      loadTime: timing.loadEventEnd - timing.navigationStart,
      memoryUsage: memory?.usedJSHeapSize
    };
  }
  
  private async processQueue() {
    if (this.sending || this.queue.length === 0) return;
    
    this.sending = true;
    const batch = this.queue.splice(0, 10); // 每次最多发送 10 条
    
    try {
      await fetch(this.endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ errors: batch }),
        keepalive: true
      });
    } catch (e) {
      // 发送失败,放回队列
      this.queue.unshift(...batch);
    } finally {
      this.sending = false;
    }
  }
}

// 全局错误上报实例
export const errorReporter = new ErrorReporter({
  endpoint: '/api/errors',
  sampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1 // 生产环境 10% 采样
});

// 便捷方法
export function reportError(error: Partial<ErrorReport>) {
  errorReporter.report(error);
}

第三方错误监控集成

// 集成 Sentry
import * as Sentry from '@sentry/vue';

export function initSentry(app: App, router: Router) {
  Sentry.init({
    app,
    dsn: process.env.SENTRY_DSN,
    environment: process.env.NODE_ENV,
    release: process.env.APP_VERSION,
    
    integrations: [
      new Sentry.BrowserTracing({
        routingInstrumentation: Sentry.vueRouterInstrumentation(router)
      }),
      new Sentry.Replay({
        // 会话回放配置
        maskAllText: true,
        blockAllMedia: true
      })
    ],
    
    // 采样率
    tracesSampleRate: 0.1, // 10% 的事务
    replaysSessionSampleRate: 0.1, // 10% 的会话回放
    replaysOnErrorSampleRate: 1.0, // 错误时 100% 回放
    
    // 过滤敏感信息
    beforeSend(event) {
      // 过滤敏感数据
      if (event.request?.data) {
        delete event.request.data.password;
        delete event.request.data.token;
      }
      return event;
    },
    
    // 忽略特定错误
    ignoreErrors: [
      'ResizeObserver loop limit exceeded',
      'Non-Error promise rejection captured'
    ]
  });
}

// 手动上报
export function captureException(error: Error, context?: Record<string, any>) {
  Sentry.captureException(error, {
    extra: context,
    tags: {
      feature: context?.feature
    }
  });
}

用户友好的错误提示

错误消息转换

// utils/errorMessages.ts

const errorMessages: Record<string, string> = {
  // 网络错误
  NETWORK_ERROR: '网络连接失败,请检查网络设置',
  TIMEOUT: '请求超时,请稍后重试',
  
  // 认证错误
  UNAUTHORIZED: '登录已过期,请重新登录',
  FORBIDDEN: '没有权限执行此操作',
  
  // 业务错误
  INSUFFICIENT_BALANCE: '余额不足,请先充值',
  STOCK_EMPTY: '商品已售罄',
  ORDER_EXPIRED: '订单已过期',
  
  // 通用错误
  SERVER_ERROR: '服务器繁忙,请稍后重试',
  UNKNOWN_ERROR: '操作失败,请重试'
};

export function getErrorMessage(error: any): string {
  // 已经是友好消息
  if (typeof error === 'string') return error;
  
  // 有预定义的消息
  if (error.code && errorMessages[error.code]) {
    return errorMessages[error.code];
  }
  
  // 有消息字段
  if (error.message) {
    // 避免展示技术性的错误消息
    if (error.message.includes('undefined') || 
        error.message.includes('null') ||
        error.message.includes('TypeError')) {
      return '操作失败,请重试';
    }
    return error.message;
  }
  
  return '操作失败,请重试';
}

// 带操作建议的错误处理
export function handleError(error: any): { message: string; action?: () => void; actionText?: string } {
  const code = error.code || 'UNKNOWN_ERROR';
  
  switch (code) {
    case 'UNAUTHORIZED':
      return {
        message: '登录已过期',
        action: () => router.push('/login'),
        actionText: '重新登录'
      };
      
    case 'NETWORK_ERROR':
      return {
        message: '网络连接失败',
        action: () => window.location.reload(),
        actionText: '刷新页面'
      };
      
    case 'INSUFFICIENT_BALANCE':
      return {
        message: '余额不足',
        action: () => router.push('/wallet/recharge'),
        actionText: '去充值'
      };
      
    default:
      return {
        message: getErrorMessage(error)
      };
  }
}

最佳实践总结

错误处理与日志系统最佳实践:

错误捕获:
✓ 全局捕获 window.onerror 和 unhandledrejection
✓ 使用错误边界隔离组件错误
✓ API 请求统一错误处理
✓ 异步操作使用 try-catch

错误上报:
✓ 收集足够的上下文信息
✓ 错误去重和采样
✓ 使用批量发送减少请求
✓ 敏感信息过滤

日志系统:
✓ 定义清晰的日志级别
✓ 结构化日志便于分析
✓ 包含追踪 ID 关联请求
✓ 开发和生产环境区分处理

用户体验:
✓ 提供友好的错误消息
✓ 给出可操作的建议
✓ 错误状态可恢复
✓ 避免白屏和卡死

监控告警:
✓ 设置错误率告警阈值
✓ 关键业务流程重点监控
✓ 定期分析错误趋势
✓ 快速响应严重错误

完善的错误处理和日志系统是高质量前端应用的基础设施。它不仅能帮助快速定位和修复问题,还能持续改进用户体验和系统稳定性。