作者:梁家玮(汽车之家: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 的封装。因此,只需重写其 open 与 send 方法,即可在请求生命周期中插入自定义逻辑,实现全局捕获与上报。
实现示例
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 头的全链路追踪