前端异常监控:如何捕获页面中的 XHR 和 Fetch 请求错误

作者:梁家玮(汽车之家:APP 架构前端工程师)

前言

在现代 Web 应用中,前端与后端 API 的异步通信是系统运行的核心环节。然而,网络环境复杂多变,接口请求难免会出现错误。

如何有效、统一地捕获这些错误,并将它们与全链路追踪(Tracing)结合,是前端异常监控与稳定性建设的关键。

本文将深入介绍:

  • 如何全局捕获通过 XMLHttpRequest (XHR) 和 Fetch 发起的请求错误;
  • 如何注入 B3 Trace Headers 实现全链路追踪;
  • 以及 B3 头在跨域场景下的限制与解决方案;
  • 最终提供一体化实践方案。

一、为什么需要全局错误捕获?

在开发过程中,开发者通常会在每个请求中手动使用 try/catch.catch() 处理错误。但在大型前端项目中,这种方式不仅繁琐、容易遗漏,而且缺乏统一的监控入口。

通过全局拦截与捕获,我们能实现:

  • ✅ 统一处理:集中化错误收集、日志记录、上报监控平台(如 Sentry、ARMS);
  • ✅ 减少冗余:避免在每个请求函数中重复编写错误逻辑;
  • ✅ 防止遗漏:即使开发者忘记处理错误,也可由系统兜底捕获;
  • ✅ 支持全链路追踪:通过 B3 头(traceId、spanId、sampled),将前端错误与后端日志一一对应。

二、捕获 XMLHttpRequest (XHR) 错误

实现原理

浏览器中基于 XMLHttpRequest 的库(如 Axios、jQuery Ajax)本质上都是对原生 XHR 的封装。因此,只需重写其 opensend 方法,即可在请求生命周期中插入自定义逻辑,实现全局捕获与上报。

实现示例

js 复制代码
const originalXhrOpen = XMLHttpRequest.prototype.open;
const originalXhrSend = XMLHttpRequest.prototype.send;

XMLHttpRequest.prototype.open = function (method, url) {
  this._requestUrl = url;
  this._requestMethod = method;
  return originalXhrOpen.apply(this, arguments);
};

XMLHttpRequest.prototype.send = function (body) {
  injectB3Headers(this);

  this.addEventListener('loadend', () => {
    if (this.status >= 400) {
      reportError({
        type: 'xhr',
        method: this._requestMethod,
        url: this._requestUrl,
        status: this.status,
        response: this.responseText
      });
    }
  });

  this.addEventListener('error', () => {
    reportError({
      type: 'xhr',
      method: this._requestMethod,
      url: this._requestUrl,
      status: 0,
      message: 'Network Error'
    });
  });

  this.addEventListener('timeout', () => {
    reportError({
      type: 'xhr',
      method: this._requestMethod,
      url: this._requestUrl,
      status: 0,
      message: 'Timeout'
    });
  });

  return originalXhrSend.apply(this, arguments);
};

三、捕获 Fetch 错误

实现原理

Fetch API 的 Promise 仅在网络层错误时才会 reject。对于 4xx、5xx 等 HTTP 错误,它依然会 resolve,因此必须手动判断状态码。

实现示例

js 复制代码
const originalFetch = window.fetch;

window.fetch = function (...args) {
  const requestUrl = typeof args[0] === 'string' ? args[0] : args[0].url;
  const requestMethod = (args[1]?.method || 'GET').toUpperCase();

  args[1] = args[1] || {};
  args[1].headers = {
    ...(args[1].headers || {}),
    ...genB3Headers()
  };

  return originalFetch.apply(this, args)
    .then(response => {
      if (!response.ok) {
        response.clone().text().then(text => {
          reportError({
            type: 'fetch',
            method: requestMethod,
            url: requestUrl,
            status: response.status,
            response: text
          });
        });
      }
      return response;
    })
    .catch(error => {
      reportError({
        type: 'fetch',
        method: requestMethod,
        url: requestUrl,
        status: 0,
        message: error.message || 'Fetch Error'
      });
      throw error;
    });
};

四、B3 全链路追踪原理与实现

1. 什么是 B3 头?

B3(Zipkin B3 Propagation)是一种分布式追踪上下文传递标准,用于在微服务体系中将一次请求的完整链路打通。

Header 名称 说明
X-B3-TraceId 唯一标识一条请求链路
X-B3-SpanId 当前调用的唯一标识
X-B3-ParentSpanId 父调用的标识(可选)
X-B3-Sampled 是否采样(1/0)

2. 生成与注入逻辑

js 复制代码
function genB3Headers() {
  const traceId = crypto.randomUUID().replace(/-/g, '').slice(0, 16);
  const spanId = crypto.randomUUID().replace(/-/g, '').slice(0, 16);
  return {
    'X-B3-TraceId': traceId,
    'X-B3-SpanId': spanId,
    'X-B3-Sampled': '1'
  };
}

function injectB3Headers(xhr) {
  const headers = genB3Headers();
  for (const key in headers) {
    try {
      xhr.setRequestHeader(key, headers[key]);
    } catch {}
  }
}

3. ⚠️ 跨域问题与服务端支持

B3 头属于自定义请求头(Custom Header),在跨域请求(CORS)场景下,浏览器会进行预检(Preflight)请求。

如果服务端未显式允许这些头字段,请求将被浏览器拦截。

解决方案:

1. 服务端需在响应头中添加允许字段:

makefile 复制代码
Access-Control-Allow-Origin: https://your-frontend-domain.com
Access-Control-Allow-Headers: X-B3-TraceId, X-B3-SpanId, X-B3-Sampled, Content-Type
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS

2. 确保 OPTIONS 请求能正确响应 200

否则浏览器会直接终止实际请求。

3. 若服务端暂不支持,可选择:

  • 临时关闭跨域 B3 注入;
  • 仅对同源接口启用;
  • 或通过反向代理层(如 Nginx、API Gateway)添加 B3 头转发。

实际建议:

  • 前端配置withB3: true 时自动携带头
  • 服务端配置:允许相关 B3 头跨域
  • 后端系统:对 traceId 进行透传和日志埋点,完成全链路闭环


五、总结

通过重写原生 XHR 与 Fetch 方法,我们可实现:

  • 🔍 全局统一的接口错误捕获与上报
  • 🔗 基于 B3 头的全链路追踪
相关推荐
xiaoxue..4 分钟前
高频事件的“冷静剂” 闭包的实用场景:防抖与节流
前端·javascript·面试·html·编程思想
优弧22 分钟前
2025 提效别再卷了:当我把 AI 当“团队”,工作真的顺了
前端
.try-25 分钟前
cssTab卡片式
java·前端·javascript
怕浪猫31 分钟前
2026最新React技术栈梳理,全栈必备
前端·javascript·面试
ulias21243 分钟前
多态理论与实践
java·开发语言·前端·c++·算法
Bigger44 分钟前
Tauri (24)——窗口在隐藏期间自动收起导致了位置漂移
前端·react.js·app
小肥宅仙女1 小时前
限流方案
前端·后端
雲墨款哥1 小时前
从一行好奇的代码说起:Vue怎么没有React的props.children
前端·vue.js·react.js
孜孜不倦不忘初心1 小时前
Axios 常用配置及使用
前端·axios
sTone873751 小时前
vscode 二开踩坑记录
前端