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.

AutoHome Frontend
AutoHome Frontend
AutoHome Frontend
How to Globally Capture XHR & Fetch Errors and Enable B3 Tracing in Frontend

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, OPTIONS

If 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

FrontendDistributed TracingfetchXHRb3 tracing
AutoHome Frontend
Written by

AutoHome Frontend

AutoHome Frontend Team

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.