首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

AJAX 錯誤處理機制深度探討:從客戶端捕獲到伺服器端日誌聯動

     編輯:Java 相關知識

錯誤分類與處理策略

層級錯誤類型處理目標關鍵技術
網路/傳輸層DNS 失敗、連線逾時、TCP 重置、SSL 驗證失敗重試、降級、用戶提示Fetch AbortController、指數退避重試、Service Worker 離線快取
HTTP 語義層4xx (客戶端錯誤)、5xx (伺服器錯誤)、非預期狀態碼統一錯誤碼映射、業務邏輯分流攔截器統一處理、Problem Details (RFC 7807) 格式
應用資料層JSON 解析失敗、欄位缺失、型別不符、業務規則違反資料驗證、友好提示、上報遙測Zod/JSON Schema 驗證、i18n 錯誤訊息、錯誤碼國際化
執行時層JS 例外 (TypeError, ReferenceError)、Promise 未捕獲拒絕、框架生命週期錯誤防止白屏、收集堆疊、自動上報window.onerror、unhandledrejection、Error Boundary (React)、Sentry/Rollbar

客戶端錯誤捕獲與上報架構

1. 全域錯誤收集器

class ErrorReporter {
  constructor(endpoint = '/api/client-errors', options = {}) {
    this.endpoint = endpoint;
    this.batch = [];
    this.timer = null;
    this.maxBatch = options.maxBatch || 10;
    this.delay = options.delay || 2000;
    this._installGlobalHandlers();
  }

  _installGlobalHandlers() {
    // 同步錯誤
    window.addEventListener('error', e => {
      if (e.error) this.capture(e.error, {type: 'sync', filename: e.filename, lineno: e.lineno, colno: e.colno});
      else this.capture(new Error(e.message), {type: 'sync', filename: e.filename, lineno: e.lineno, colno: e.colno});
      return false; // 不阻止預設行為 (讓瀏覽器 Console 仍顯示)
    });

    // Promise 拒絕
    window.addEventListener('unhandledrejection', e => {
      const err = e.reason instanceof Error ? e.reason : new Error(String(e.reason));
      this.capture(err, {type: 'promise'});
      return false;
    });
  }

  capture(error, context = {}) {
    const payload = {
      message: error.message,
      stack: error.stack,
      name: error.name,
      timestamp: Date.now(),
      url: location.href,
      userAgent: navigator.userAgent,
      context: { ...context, ...this._getExtraContext() }
    };
    this._queue(payload);
  }

  _getExtraContext() {
    return {
      viewport: `${innerWidth}x${innerHeight}`,
      online: navigator.onLine,
      referrer: document.referrer,
      // 可加入用戶 ID、Session ID、版本號等
    };
  }

  _queue(payload) {
    this.batch.push(payload);
    if (this.batch.length >= this.maxBatch) this._flush();
    else if (!this.timer) this.timer = setTimeout(() => this._flush(), this.delay);
  }

  async _flush() {
    if (this.timer) { clearTimeout(this.timer); this.timer = null; }
    if (!this.batch.length) return;
    const payloads = this.batch.splice(0, this.maxBatch);
    try {
      await fetch(this.endpoint, {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({errors: payloads}),
        keepalive: true // 頁面卸載時仍發送
      });
    } catch (e) {
      console.error('錯誤上報失敗', e);
      // 可選:存入 localStorage 待下次重試
    }
  }
}

// 初始化 (盡早執行)
const reporter = new ErrorReporter();

2. AJAX 請求層統一攔截

// Axios 範例
axios.interceptors.response.use(
  resp => resp,
  error => {
    const ctx = {
      type: 'ajax',
      url: error.config?.url,
      method: error.config?.method,
      status: error.response?.status,
      data: error.response?.data
    };
    if (error.response) {
      // 401 統一導向登入、403 提示權限、5xx 顯示系統繁忙
      switch (error.response.status) {
        case 401: redirectToLogin(); break;
        case 403: showToast('權限不足'); break;
        default: showToast('系統暫時無法響應,請稍後再試');
      }
    } else if (error.code === 'ECONNABORTED') {
      showToast('請求逾時,請檢查網路');
    } else {
      showToast('網路異常');
    }
    reporter.capture(new Error(error.message), ctx);
    return Promise.reject(error); // 讓業務碼仍可捕獲
  }
);

伺服器端接收與處理 (Spring Boot 範例)

@RestController
@RequestMapping("/api/client-errors")
public class ClientErrorController {

    private static final Logger log = LoggerFactory.getLogger(ClientErrorController.class);

    @PostMapping
    public ResponseEntity receive(@RequestBody ClientErrorBatch batch) {
        batch.getErrors().forEach(e -> {
            // 結構化日誌 (ELK / Loki 友好)
            log.warn("ClientError: {} | {} | url={} ua={}",
                    e.getName(), e.getMessage(), e.getUrl(), e.getUserAgent(),
                    kv("stack", e.getStack()), kv("context", e.getContext()));
            // 可依錯誤類型/頻率觸發告警 (Prometheus Alertmanager / PagerDuty)
        });
        return ResponseEntity.noContent().build();
    }

    record ClientErrorBatch(List errors) {}
    record ClientError(String name, String message, String stack, long timestamp,
                       String url, String userAgent, Map context) {}
}

進階策略

  1. Source Map 還原:上報時附帶 sourceMapUrl,伺服器/分析平台自動還原混淆堆疊。
  2. 錯誤去重與指紋:hash(name + message + topStackFrame) 分組,避免噪音淹沒核心問題。
  3. 用戶行為重現:整合 RRWeb 或 OpenReplay 錄製會話,錯誤上下文附帶重播連結。
  4. 分級上報:致命錯誤即時發送 (keepalive),非致命批次延遲,節省頻寬。
  5. 隱私合規:避免上報 PII (密碼、Token、個資),Context 過濾敏感鍵。

測試驗證清單

  • 人為拋出 throw new Error('test') → 後端收到結構化日誌。
  • 斷網狀態觸發 AJAX → 捕獲網路錯誤、顯示友好提示、上報含 online: false。
  • Promise.reject() 未捕獲 → unhandledrejection 觸發上報。
  • 大量並發錯誤 → 批次合併、不阻塞主執行緒、伺服器正常入庫。
  • 頁面關閉前錯誤 → keepalive: true 確保送達。

總結

AJAX 錯誤處理非單點技術,而是全鏈路觀測體系:

  1. 客戶端:全域捕獲 + 請求攔截 + 批次上報 + 使用者友好降級。
  2. 傳輸層:keepalive、CORS、壓縮、重試策略。
  3. 伺服器端:結構化入庫、即時告警、Source Map 還原、會話重現。
  4. 治理層:錯誤指紋分組、趨勢儀表板、版本關聯、隱私合規。

投資完善錯誤處理,等同投資系統可觀測性與用戶信任。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved