Frontend Development 30 min read

How to Build a Front‑End Error Monitoring Platform from Scratch

This article walks through designing and implementing a lightweight front‑end monitoring platform that captures JavaScript runtime and compile‑time errors, Promise rejections, resource loading failures, and network request issues, while also recording user interactions, page navigation, and environment details using modular code and browser APIs.

Inke Technology
Inke Technology
Inke Technology
How to Build a Front‑End Error Monitoring Platform from Scratch

Introduction

In today’s competitive web market, front‑end development plays a crucial role in product quality, but the diverse and uncontrolled runtime environments make error detection difficult. A robust monitoring platform is essential for quickly responding to issues and improving user experience.

Architecture Flow

The platform consists of four modules: error listening, screen recording, proactive environment detection, and user‑behavior logging. By injecting a small JavaScript snippet, the page can capture errors, user actions, and environment data, then report them together.

Basic Principles

The core of error capture is listening to the error event; the following sections explain how JavaScript achieves this.

3.1 JavaScript Errors

Two types of errors exist:

Compile‑time errors – syntax errors detected during parsing; build tools can catch them.

Runtime errors – exceptions thrown while executing interpreted code, such as reference or type errors.

When a runtime error occurs, the engine throws an Error object. If no try…catch handles it, the global error event fires on window , invoking window.onerror .

3.2 Standard Error Types

SyntaxError

ReferenceError

RangeError

TypeError

URLError

EvalError

3.3 Other Exceptions

3.3.1 Promise Rejection Handling

Uncaught Promise rejections trigger the unhandledrejection event and call window.onunhandledrejection . If a rejected Promise is later handled, the rejectionhandled event fires, allowing delayed reporting.

<code>window.onunhandledrejection = e => { console.log('onunhandledrejection=>', e) }
function foo() { return Promise.reject('Hello, Inke!') }
var r = foo();</code>

3.3.2 Resource Loading Errors

When img , script , or link resources fail, the element’s error event fires during the capture phase. The target element is examined to collect tag name, class, id, outerHTML, and XPath.

3.3.3 Network Request Errors

Both XMLHttpRequest and fetch can be wrapped to emit custom events for each request stage, enabling detailed monitoring of request method, URL, headers, body, status, and timing.

Implementation Steps

4.1 Error Listening Module

4.1.1 onerror vs. addEventListener

Using addEventListener avoids the risk of the handler being overwritten, while still supporting older browsers via attachEvent . A generic listener wrapper adds try…catch protection.

<code>// Generic event listener
const myAddEventListener = (name, fn, useCapture) => {
  if (addEventListener) {
    addEventListener(name, tryCatchFunc(fn), useCapture);
  } else if (attachEvent) {
    attachEvent(`on${name}`, tryCatchFunc(fn));
  }
};
export const tryCatchFunc = fn => function(...args) {
  try { return fn.apply(this, args) }
  catch (error) { console.warn('内部报错', error) }
};</code>

4.1.2 Promise Exception Listening

<code>const handlePromise = e => {
  let promiseTimer = setTimeout(tryCatchFunc(() => {
    const { reason } = e;
    e.message = typeof reason === 'object' ? JSON.stringify(reason) : (reason + '');
    e.name = 'unhandledrejection';
    errorReport(e);
  }), PROMISE_TIMEOUT);
  edithAddEventListener('rejectionhandled', tryCatchFunc(event => {
    if (event.promise === e.promise) { if (promiseTimer) clearTimeout(promiseTimer); promiseTimer = null; }
  }));
};</code>

4.1.3 Resource Load Exception Listening

<code>function errorReport(errorEvent) {
  const errorTarget = errorEvent.target;
  if (errorTarget !== window) {
    const tagName = errorTarget.tagName.toLowerCase();
    let sourceUrl = '';
    if (tagName === 'link') sourceUrl = errorTarget.href; else sourceUrl = errorTarget.src;
    errorEvent.message = sourceUrl;
    errorEvent.name = 'resourceError';
    errorEvent.targetDom = {
      tagName,
      className: errorTarget.className,
      id: errorTarget.id,
      outerHTML: errorTarget.outerHTML,
      xPath: getXPath(errorTarget)
    };
  }
  reportDebug(errorEvent);
}</code>

4.1.4 Network Request Error Listening

By proxying XMLHttpRequest and fetch , custom events such as ajaxOpen , ajaxSend , ajaxError , and fetchStart / fetchEnd are emitted, providing full request lifecycle data.

<code>const oldXHR = XMLHttpRequest;
function newXHR() {
  const realXHR = new oldXHR();
  // wrap send
  const send = realXHR.send;
  realXHR.send = function(...arg) { send.apply(realXHR, arg); realXHR.body = arg[0]; ajaxEventTrigger.call(realXHR, 'ajaxSend'); };
  // wrap open
  const open = realXHR.open;
  realXHR.open = function(...arg) { open.apply(realXHR, arg); realXHR.method = arg[0]; realXHR.originUrl = arg[1]; ajaxEventTrigger.call(realXHR, 'ajaxOpen'); };
  // wrap setRequestHeader
  const setRequestHeader = realXHR.setRequestHeader;
  realXHR.requestHeader = {};
  realXHR.setRequestHeader = function(name, value) { realXHR.requestHeader[name] = value; setRequestHeader.call(realXHR, name, value); };
  return realXHR;
}
window.XMLHttpRequest = newXHR;</code>

4.1.5 fetch Wrapping

<code>const oldFetchfn = fetch;
if (!oldFetchfn) return;
function fetchEventTrigger(event, detail) { const ajaxEvent = new CustomEvent(event, { detail }); dispatchEvent(ajaxEvent); }
fetch = function(...args) {
  const now = +new Date();
  const options = { url: args[0], method: 'GET', body: null, originUrl: args[0], ...args[1] };
  fetchEventTrigger.call(this, 'fetchStart', { options });
  return oldFetchfn.apply(this, args).then(res => { res.options = options; fetchEventTrigger.call(this, 'fetchEnd', res); return res; }, err => { err.options = options; fetchEventTrigger.call(this, 'fetchError', err); });
};</code>

4.2 Screen Recording Module

The platform uses MutationObserver to capture DOM changes and user interactions, generating a lightweight “video” of 20‑50 KB that can be replayed in an iframe without noticeable performance impact.

4.3 Proactive Detection

Additional environment data such as network latency, bandwidth, and device capabilities are collected using tiny image requests, XMLHttpRequest probes, and the requestIdleCallback API to avoid interfering with user activity.

<code>const measureDelay = (fn, count) => { /* load 1×1 image and measure time */ };
const checkNetSpeed = () => { if (navigator.connection && navigator.connection.downlink) return navigator.connection.downlink * 1024 / 8; };
function myNonEssentialWork(deadline) {
  while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && tasks.length > 0) { tasks.shift()(); }
  if (tasks.length > 0) requestIdleCallback(myNonEssentialWork);
}
requestIdleCallback(myNonEssentialWork, { timeout: 2000 });</code>

4.4 User‑Behavior Recording

Clicks, navigation changes, and request lifecycles are stored in a bounded breadcrumbs array with automatic overflow handling. Each record includes timestamps, page URL, title, and a concise snapshot of the target element (class, id, outerHTML, XPath).

<code>const breadcrumbs = [];
breadcrumbs.add = data => {
  const index = breadcrumbs.findIndex(i => data.eid && i.eid === data.eid);
  if (index >= 0) return breadcrumbs.splice(index, 1, { ...breadcrumbs[index], ...data });
  if (breadcrumbs.length >= RECORD_COUNT) breadcrumbs.shift();
  breadcrumbs.push({ eid: getRandomID(), ...data });
};
myAddEventListener('click', addActionRecord('click'), true);
function addActionRecord(type) { return event => { const target = event.target; const record = { type, time: getCurrentTime(), timeStamp: event.timeStamp, page: { url: location.href, title: document.title }, detail: { tagName: target.tagName, className: target.className, id: target.id, outerHTML: target.outerHTML.slice(0,100)+'...'+target.outerHTML.slice(-99), xPath: getXPath(target) } }; breadcrumbs.add(record); }; }</code>

4.5 Navigation Tracking

History API methods pushState and replaceState are proxied to emit a custom navigationChange event, while hashchange is used for hash‑based routing. For legacy browsers, a polling timer detects URL changes.

<code>function proxyState(prop, fn) { return function(...arg) { if (arg[2]) eventTrigger.call({ oldURL: location.href, newURL: arg[2], method: prop }, 'navigationChange'); return fn.apply(this, arg); }; }
history.pushState = proxyState('pushState', history.pushState);
history.replaceState = proxyState('replaceState', history.replaceState);
myAddEventListener('hashchange', addUrlRecord('hashchange'));
</code>

Feature Summary

JS runtime and compile‑time error capture

Promise unhandled rejection reporting

Resource load error detection

Network request error monitoring (XHR & fetch)

User interaction, request, and navigation logging

Lightweight screen recording via DOM snapshots

Proactive environment information collection

Conclusion

The article demonstrates how to assemble a front‑end monitoring script that gathers comprehensive error and performance data. The next steps involve backend storage, aggregation, and visualization, which are beyond the scope of this tutorial.

Source code: https://github.com/inkefe/edith-script
Frontendperformancejavascripterror monitoringWeb Development
Inke Technology
Written by

Inke Technology

Official account of Inke Technology

0 followers
Reader feedback

How this landed with the community

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