錯誤監(jiān)聽
1. 基于 onerror、unhandledrejection 事件錯誤監(jiān)聽
-
window.onerror屬于 DOM0 級別接口,主要捕獲 JS 運(yùn)行時錯誤。 -
addEventListener('error', ..., true)可在捕獲階段監(jiān)聽,能額外捕獲資源加載錯誤(圖片、腳本、樣式、字體等)。 - 通過
event.target.src || event.target.href可區(qū)分資源錯誤與普通運(yùn)行時錯誤。 -
unhandledrejection用于捕獲未被處理的 Promise 拒絕錯誤。
window.addEventListener(
'error',
(event) => {
const target = event.target;
// 判斷錯誤類型
const isResourceError =
!!target &&
('src' in target || 'href' in target);
if (isResourceError) {
const url =
target.src ||
target.href ||
'unknown';
captureException({ type: 'resource_error', url });
return;
}
captureException(event.error || new Error(event.message));
},
// 配置 true 支持捕獲階段監(jiān)聽
true
);
window.addEventListener('unhandledrejection', (event) => {
// 統(tǒng)一錯誤上報入口
captureException({ type: 'unhandledrejection', reason: event.reason });
});
2. 跨域腳本錯誤捕獲
跨域腳本默認(rèn)可能只看到 Script error。要拿到完整堆棧,需要前后端同時配置:
- 前端標(biāo)簽增加
crossorigin。 - 服務(wù)端返回對應(yīng) CORS 頭。
<script src="https://cdn.example.com/app.js" crossorigin="anonymous"></script>
Access-Control-Allow-Origin: https://your-site.com
Access-Control-Allow-Credentials: true
注意:若使用 crossorigin="use-credentials",服務(wù)端必須允許攜帶憑據(jù),且 Access-Control-Allow-Origin 不能為 *。
3. 異步任務(wù)錯誤捕獲(包裝全局 API)
setTimeout、setInterval、requestAnimationFrame 等異步回調(diào)可通過包裝函數(shù)統(tǒng)一兜底,且不改變原有行為。
function wrap(fn) {
return function wrapped(...args) {
try {
// 保留 this 和參數(shù),保持原函數(shù)行為一致
return fn.apply(this, args);
} catch (err) {
// 捕獲后繼續(xù)拋出,避免吞錯
captureException(err);
throw err;
}
};
}
const originalSetTimeout = window.setTimeout;
window.setTimeout = function (fn, delay, ...rest) {
if (typeof fn === 'function') {
return originalSetTimeout(wrap(fn), delay, ...rest);
}
return originalSetTimeout(fn, delay, ...rest);
};
4. 請求錯誤監(jiān)聽(XHR / Fetch)
XHR 攔截
if (window.XMLHttpRequest) {
const originalSend = XMLHttpRequest.prototype.send;
function handleXhrEvent(event) {
const xhr = event.currentTarget;
if (xhr && xhr.status >= 400) {
captureException({
type: 'xhr_error',
status: xhr.status,
responseURL: xhr.responseURL,
});
}
}
XMLHttpRequest.prototype.send = function (...args) {
// 統(tǒng)一監(jiān)聽請求失敗與異常中斷
this.addEventListener('error', handleXhrEvent);
this.addEventListener('abort', handleXhrEvent);
this.addEventListener('load', handleXhrEvent);
return originalSend.apply(this, args);
};
}
Fetch 攔截
if (window.fetch) {
const originalFetch = window.fetch;
window.fetch = function (...args) {
return originalFetch
.apply(this, args)
.then((res) => {
if (!res.ok) {
captureException({
type: 'fetch_error',
status: res.status,
url: res.url,
});
}
return res;
})
.catch((error) => {
captureException({ type: 'fetch_exception', error });
throw error;
});
};
}
5. Vue 錯誤監(jiān)聽
Vue 提供組件級 errorCaptured 與應(yīng)用級 app.config.errorHandler。通常建議:
-
errorCaptured:用于局部兜底和降級。 -
errorHandler:用于全局聚合與統(tǒng)一上報。
app.config.errorHandler = (err, instance, info) => {
const componentName =
instance?.$options?.name ??
instance?.$options?.__name ??
'AnonymousComponent';
captureException({
type: "vue_error",
error: err,
component: componentName,
hook: info,
});
// 如需保留默認(rèn)日志輸出,可手動補(bǔ)充
console.error(err);
};
無侵入攔截(Monkey Patching)
監(jiān)控 SDK、埋點庫常使用 Monkey Patching 實現(xiàn)無侵入注入。
本質(zhì)是保留原函數(shù)引用,在外層擴(kuò)展邏輯后再調(diào)用原始行為。
- 保留
this與參數(shù)上下文。 - 提供
unpatch,支持恢復(fù)原行為(便于測試和熱更新)。 - 通過標(biāo)記位避免重復(fù) patch。
function patchMethod(obj, name, replacement) {
const original = obj[name]
if (typeof current !== 'function') return () => {};
if (current.__patched__) return () => {};
obj[name] = function(...args) {
// 把原函數(shù) original 傳進(jìn)去,讓 replacement 決定何時調(diào)用
return replacement.call(this, original.bind(this), args)
}
// 保存引用方便回退使用
obj[name].__original = original
obj[name].__patched = true
// 返回 unpatch 函數(shù)
return () => {
obj[name] = original
delete obj[name].__original
delete obj[name].__patched
}
}
// 使用示例:攔截 fetch,返回的unpatch 函數(shù)講內(nèi)容還原成默認(rèn)值
const unpatch = patchMethod(window, 'fetch', (original, args) => {
const [url, options] = args
const start = Date.now()
// 調(diào)用原始函數(shù)指向默認(rèn)行為,然后修改返回內(nèi)容
return original(url, options).then(resp => {
// 這里是記錄請求示例
recordRequest(url, resp.status, Date.now() - start)
return resp
})
})
常用場景:
-
fetch/XMLHttpRequest:采集請求耗時、狀態(tài)碼、錯誤率,注入追蹤頭。 -
console.*:采集日志與面包屑,避免在 patch 內(nèi)遞歸調(diào)用自身。 -
history.pushState/history.replaceState:路由變化監(jiān)聽,配合popstate。 -
addEventListener:采集交互行為,區(qū)分event.target與event.currentTarget。 -
setTimeout/setInterval/requestAnimationFrame:錯誤兜底與調(diào)度耗時分析。
總結(jié)
-
錯誤捕獲:通過
onerror、unhandledrejection、資源錯誤監(jiān)聽、請求攔截與 Vue 全局處理器,基本覆蓋前端主要錯誤來源。 - 無侵入增強(qiáng):通過 Monkey Patching 在不改業(yè)務(wù)調(diào)用方式下擴(kuò)展監(jiān)控能力,保留原函數(shù)行為,同時實現(xiàn)可恢復(fù) patch。