前端監(jiān)控筆記(一)

錯誤監(jiān)聽

1. 基于 onerrorunhandledrejection 事件錯誤監(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、setIntervalrequestAnimationFrame 等異步回調(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)用原始行為。

  1. 保留 this 與參數(shù)上下文。
  2. 提供 unpatch,支持恢復(fù)原行為(便于測試和熱更新)。
  3. 通過標(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.targetevent.currentTarget。
  • setTimeout / setInterval / requestAnimationFrame:錯誤兜底與調(diào)度耗時分析。

總結(jié)

  • 錯誤捕獲:通過 onerrorunhandledrejection、資源錯誤監(jiān)聽、請求攔截與 Vue 全局處理器,基本覆蓋前端主要錯誤來源。
  • 無侵入增強(qiáng):通過 Monkey Patching 在不改業(yè)務(wù)調(diào)用方式下擴(kuò)展監(jiān)控能力,保留原函數(shù)行為,同時實現(xiàn)可恢復(fù) patch。

參考內(nèi)容

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前端的錯誤監(jiān)控、性能數(shù)據(jù)往往對業(yè)務(wù)的穩(wěn)定性有很重要的影響,即使我們在開發(fā)階段十分小心,也難免線上會出現(xiàn)異常,并且線...
    hzrWeber閱讀 900評論 0 1
  • 常見錯誤的分類 對于用戶在訪問頁面時發(fā)生的錯誤,主要包括以下幾個類型: 1、js運(yùn)行時錯誤 JavaScript代...
    Jiao_0805閱讀 1,190評論 0 0
  • 前言 為什么要處理前端異常,有以下幾方面的原因: 提高代碼健壯性:對于開發(fā)人員來說,這點很重要,代碼的健壯性越好,...
    yolkpie閱讀 1,179評論 0 0
  • 最近在做一個前端監(jiān)控的js 如圖,一個大概的思路是這樣的。 圖片版 -----------------------...
    Estarsyang閱讀 567評論 0 0
  • 一、xue的生命周期是什么 vue每個組件都是獨立的,,每個組件都有一個屬于他的生命周期,從一個組件創(chuàng)建、數(shù)據(jù)初始...
    康娜閱讀 1,249評論 0 0

友情鏈接更多精彩內(nèi)容