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.
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
Inke Technology
Official account of Inke Technology
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.