上一篇我們聊了用 Sentry 這種現成 SaaS 服務來做錯誤監控,方便歸方便,但如果你遇到的是這幾種情況:公司內網環境不允許資料外流、SaaS 按量計費太貴、或是你想要完全掌控上報欄位與儲存格式——那麼「自建一個輕量監控 SDK」就是你的下一步。今天這篇實戰指南,我會帶你從零手搓一個不到 200 行的前端監控 SDK,涵蓋 錯誤採集、效能指標、上報策略 三大核心,寫完你對整個監控體系的運作原理就通了。
一、監控 SDK 的核心架構:採集 → 緩存 → 上報 → 聚合
先建立全局觀念。任何前端監控系統(不管自建還是 SaaS)都是這條流水線:
- 採集層:攔截錯誤事件與效能資料(SDK 的本體)
- 緩存層:先把資料放進佇列,湊批再送
- 上報層:用 sendBeacon / fetch 把資料送到後端
- 聚合層:後端儲存、去重、聚合成看板
重點:SDK 只負責前三層,聚合層屬於後端職責。個人專案可以用一個簡單的 Node 服務 + 資料庫起步;企業環境則通常接內部的日誌系統(如 Kafka + Elasticsearch)。
二、步驟 1:攔截三類全域錯誤
前端錯誤主要分三類:JS 執行錯誤、Promise 未捕獲的 rejection、資源載入失敗。逐一攔截:
// monitor.ts - 錯誤採集核心
type Report = { type: string; msg: string; stack?: string; ts: number };
const queue: Report[] = [];
// 1. JS 執行錯誤 與 資源載入失敗
window.addEventListener('error', (e) => {
if (e.target !== window) { // 資源失敗(script/img/css)
const t = e.target as HTMLElement;
queue.push({ type: 'resource',
msg: `載入失敗: ${(t as any).src || (t as any).href}`, ts: Date.now() });
} else {
queue.push({ type: 'js', msg: e.message,
stack: e.error?.stack, ts: Date.now() });
}
}, true); // 捕獲階段才能抓到資源錯誤
// 2. Promise rejection
window.addEventListener('unhandledrejection', (e) => {
queue.push({ type: 'promise', msg: String(e.reason), ts: Date.now() });
});
特別注意:資源載入失敗不會冒泡,監聽必須開啟 capture: true 才抓得到,這是新手最常踩的坑。
三、步驟 2:用 PerformanceObserver 收集 Web Vitals
效能方面,現代瀏覽器提供了 PerformanceObserver,可以直接觀察 LCP、INP 等指標:
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
queue.push({ type: 'lcp',
msg: `LCP=${Math.round(entry.startTime)}ms`, ts: Date.now() });
}
}).observe({ type: 'largest-contentful-paint', buffered: true });
如果不想自己算指標,web-vitals 這個官方套件封裝得很好,onLCP / onCLS / onINP 一行一個指標,拿回來直接塞進佇列即可。
四、步驟 3:上報策略——sendBeacon + 批量 + 採樣
上報是最講究工程細節的一環,三個原則:
- 用 sendBeacon 而非 fetch:頁面關閉時也能保證資料送出,不會被瀏覽器中斷
- 批量上報:佇列湊滿 5 筆或停留 10 秒才送一次,減少請求數
- 採樣控制:流量大的站點只上報 10%~20%,後端資料量才扛得住
function flush() {
if (!queue.length) return;
const batch = queue.splice(0, queue.length);
navigator.sendBeacon('/api/monitor', JSON.stringify(batch));
}
setInterval(flush, 10_000);
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') flush(); // 離開頁面前最後一搏
});
重點:把 flush() 掛在 visibilitychange 的 hidden 狀態上,是確保「用戶白屏閃退前最後一筆錯誤」能送出去的關鍵。
五、步驟 4:接入 Vue 3 / Vben 專案
SDK 本體完成後,接進專案只要兩行:
import { monitor } from '@/utils/monitor';
// Vue 元件內錯誤統一接管
app.config.errorHandler = (err) => monitor.capture(err as Error);
// axios 攔截器補一刀接口錯誤
axios.interceptors.response.use(undefined, (e) => {
monitor.api(`POST ${e.config?.url} → ${e.response?.status}`);
return Promise.reject(e);
});
這樣一來,JS 錯誤、Promise 錯誤、資源失敗、Vue 元件錯誤、接口錯誤五條管道全打通。後端只需要一個接收 JSON 陣列的介面,把資料寫進資料表,再按「錯誤訊息 + 出現次數」聚合排序,一個最小可用的監控後台就成型了。
結語
自建 SDK 的意義不在於省錢,而在於你理解了監控系統每一環的原理:Sentry 的 breadcrumb、source map 還原、採樣策略,底層邏輯都是同一套。先手搓一個最小版本,再回頭看 SaaS 的功能,你會發現每個選單背後都是今天寫過的某段程式碼。
互動話題:你的專案目前是用 SaaS 監控還是自建方案?在資料合規與成本之間,你會怎麼權衡?歡迎留言聊聊你的經驗!