從前端監控開始:自建輕量 SDK,掌握錯誤採集與效能上報實戰指南

上一篇我們聊了用 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 監控還是自建方案?在資料合規與成本之間,你會怎麼權衡?歡迎留言聊聊你的經驗!