The popular error tracking SDKs are great and also large. For our analytics tool we wanted error capture that adds a couple of kb, not ninety. Here is the whole thing. It is smaller than you think.
Capture the two events that matter
type CapturedError = {
message: string; stack?: string; url: string;
line?: number; col?: number;
kind: "error" | "unhandledrejection"; ts: number;
};
const queue: CapturedError[] = [];
window.addEventListener("error", (e) => {
queue.push({ message: e.message, stack: e.error?.stack, url: location.href,
line: e.lineno, col: e.colno, kind: "error", ts: Date.now() });
schedule();
});
window.addEventListener("unhandledrejection", (e) => {
const r = e.reason;
queue.push({ message: typeof r === "string" ? r : r?.message ?? "unhandled rejection",
stack: r?.stack, url: location.href, kind: "unhandledrejection", ts: Date.now() });
schedule();
});
That is most of the value right there. error catches thrown exceptions, unhandledrejection catches the async ones everyone forgets.
Batch, debounce, and survive unload
let timer: number | undefined;
function schedule() {
clearTimeout(timer);
timer = setTimeout(flush, 2000) as unknown as number;
if (queue.length >= 20) flush();
}
function flush() {
if (!queue.length) return;
const batch = queue.splice(0, queue.length);
navigator.sendBeacon("/errors", JSON.stringify(batch));
}
addEventListener("pagehide", flush);
sendBeacon is the trick. A normal fetch on unload gets cancelled. Beacon does not.
Do not drown in noise
Two cheap filters save you from a flood:
const seen = new Set<string>();
function dedupe(err: CapturedError): boolean {
const sig = err.message + ":" + err.line + ":" + err.col;
if (seen.has(sig)) return false; // already reported this page load
seen.add(sig);
return true;
}
const IGNORE = [/ResizeObserver loop/, /Script error\.?$/, /extension/i];
const ignored = (err: CapturedError) => IGNORE.some((re) => re.test(err.message));
Script error. with no detail is almost always a cross origin script with no CORS headers. ResizeObserver loop limit exceeded is benign browser noise. Filter both.
The one thing you actually need a server for
Minified stacks are useless without source maps. Upload your source maps at build time and resolve the stack server side. That, not the capture, is the part worth real effort.
Takeaway
Two event listeners, a batched beacon, dedupe, and an ignore list gets you most of the value of a heavy SDK in a couple of kb. Spend your effort on source map resolution, not on the capture.
Disclosure: this is the error tracking we ship in Zenovay.
Top comments (0)