前端监控笔记(一)

错误监听

1. 基于 onerrorunhandledrejection 事件错误监听

  • window.onerror 属于 DOM0 级别接口,主要捕获 JS 运行时错误。
  • addEventListener('error', ..., true) 可在捕获阶段监听,能额外捕获资源加载错误(图片、脚本、样式、字体等)。
  • 通过 event.target.src || event.target.href 可区分资源错误与普通运行时错误。
  • 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 支持捕获阶段监听
  true
);

window.addEventListener('unhandledrejection', (event) => {
  // 统一错误上报入口
  captureException({ type: 'unhandledrejection', reason: event.reason });
});

2. 跨域脚本错误捕获

跨域脚本默认可能只看到 Script error。要拿到完整堆栈,需要前后端同时配置:

  • 前端标签增加 crossorigin
  • 服务端返回对应 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",服务端必须允许携带凭据,且 Access-Control-Allow-Origin 不能为 *

3. 异步任务错误捕获(包装全局 API)

setTimeoutsetIntervalrequestAnimationFrame 等异步回调可通过包装函数统一兜底,且不改变原有行为。

function wrap(fn) {
  return function wrapped(...args) {
    try {
      // 保留 this 和参数,保持原函数行为一致
      return fn.apply(this, args);
    } catch (err) {
      // 捕获后继续抛出,避免吞错
      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. 请求错误监听(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) {
    // 统一监听请求失败与异常中断
    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 错误监听

Vue 提供组件级 errorCaptured 与应用级 app.config.errorHandler。通常建议:

  • errorCaptured:用于局部兜底和降级。
  • errorHandler:用于全局聚合与统一上报。
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,
  });

  // 如需保留默认日志输出,可手动补充
  console.error(err);
};

无侵入拦截(Monkey Patching)

监控 SDK、埋点库常使用 Monkey Patching 实现无侵入注入。
本质是保留原函数引用,在外层扩展逻辑后再调用原始行为。

  1. 保留 this 与参数上下文。
  2. 提供 unpatch,支持恢复原行为(便于测试和热更新)。
  3. 通过标记位避免重复 patch。
function patchMethod(obj, name, replacement) {
  const original = obj[name]
  if (typeof current !== 'function') return () => {};
  if (current.__patched__) return () => {};

  obj[name] = function(...args) {
    // 把原函数 original 传进去,让 replacement 决定何时调用
    return replacement.call(this, original.bind(this), args)
  }
  // 保存引用方便回退使用
  obj[name].__original = original
  obj[name].__patched  = true

   // 返回 unpatch 函数
  return () => { 
        obj[name] = original
        delete obj[name].__original
        delete obj[name].__patched
    } 
}

// 使用示例:拦截 fetch,返回的unpatch 函数讲内容还原成默认值
const unpatch = patchMethod(window, 'fetch', (original, args) => {
  const [url, options] = args
  const start = Date.now()

    // 调用原始函数指向默认行为,然后修改返回内容
  return original(url, options).then(resp => {
    // 这里是记录请求示例
    recordRequest(url, resp.status, Date.now() - start)
    return resp
  })
})

常用场景

  • fetch / XMLHttpRequest:采集请求耗时、状态码、错误率,注入追踪头。
  • console.*:采集日志与面包屑,避免在 patch 内递归调用自身。
  • history.pushState / history.replaceState:路由变化监听,配合 popstate
  • addEventListener:采集交互行为,区分 event.targetevent.currentTarget
  • setTimeout / setInterval / requestAnimationFrame:错误兜底与调度耗时分析。

总结

  • 错误捕获:通过 onerrorunhandledrejection、资源错误监听、请求拦截与 Vue 全局处理器,基本覆盖前端主要错误来源。
  • 无侵入增强:通过 Monkey Patching 在不改业务调用方式下扩展监控能力,保留原函数行为,同时实现可恢复 patch。

参考内容

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 前端的错误监控、性能数据往往对业务的稳定性有很重要的影响,即使我们在开发阶段十分小心,也难免线上会出现异常,并且线...
    hzrWeber阅读 898评论 0 1
  • 常见错误的分类 对于用户在访问页面时发生的错误,主要包括以下几个类型: 1、js运行时错误 JavaScript代...
    Jiao_0805阅读 1,189评论 0 0
  • 前言 为什么要处理前端异常,有以下几方面的原因: 提高代码健壮性:对于开发人员来说,这点很重要,代码的健壮性越好,...
    yolkpie阅读 1,175评论 0 0
  • 最近在做一个前端监控的js 如图,一个大概的思路是这样的。 图片版 -----------------------...
    Estarsyang阅读 565评论 0 0
  • 一、xue的生命周期是什么 vue每个组件都是独立的,,每个组件都有一个属于他的生命周期,从一个组件创建、数据初始...
    康娜阅读 1,243评论 0 0

友情链接更多精彩内容