How to Globally Capture XHR & Fetch Errors and Enable B3 Tracing in Frontend
This article explains why global error capture for XHR and Fetch requests is essential, shows how to intercept and report those errors, demonstrates injecting B3 trace headers for end‑to‑end tracing, and provides practical solutions for cross‑origin scenarios.
Why Global Error Capture Is Needed
In large‑scale front‑end projects, wrapping each request with try/catch or .catch() is tedious, error‑prone, and lacks a unified monitoring entry point. A global interceptor centralizes error collection, reduces redundant code, prevents missed errors, and enables full‑link tracing via B3 headers.
Capturing XMLHttpRequest (XHR) Errors
Implementation principle
Most XHR‑based libraries (Axios, jQuery Ajax) wrap the native XHR object. Overriding open and send allows injection of custom logic into the request lifecycle.
Code example
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);
};Capturing Fetch Errors
Implementation principle
The Fetch API returns a Promise that rejects only on network failures. HTTP errors (4xx, 5xx) resolve successfully, so the status code must be checked manually.
Code example
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 Full‑Link Tracing
Header definitions
X-B3-TraceId : unique identifier for a request chain
X-B3-SpanId : identifier for the current call
X-B3-ParentSpanId : identifier of the parent call (optional)
X-B3-Sampled : indicates whether the request is sampled (1/0)
Generation and injection
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(e) {}
}
}Cross‑origin considerations
B3 headers are custom request headers. In CORS scenarios the browser sends a pre‑flight OPTIONS request. The server must explicitly allow these headers, otherwise the request is blocked.
Server configuration example
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, OPTIONSIf the server cannot be modified, options include disabling B3 injection for cross‑origin calls, limiting injection to same‑origin APIs, or adding the headers at a reverse‑proxy layer (e.g., Nginx, API Gateway).
Practical Recommendations
Frontend: set withB3: true to automatically attach B3 headers.
Server: allow the B3 headers in CORS settings.
Backend: propagate traceId, log it, and close the tracing loop.
Conclusion
By overriding native XHR and Fetch methods, developers obtain a unified error‑capture and reporting mechanism while enriching requests with B3 trace headers for end‑to‑end observability. This reduces code duplication, prevents missed errors, and enables full‑link tracing across front‑end and back‑end services.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
