前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >我的JavaScript异常监控策略:保护前端应用免受错误的困扰!

我的JavaScript异常监控策略:保护前端应用免受错误的困扰!

原创
作者头像
zayyo
发布2023-11-08 19:50:23
2750
发布2023-11-08 19:50:23
举报
文章被收录于专栏:zayyo前端zayyo前端

在上一篇文章“如何及时发现网页的隐形错误”中我们讲了,前端有哪些常见的异常,以及如今监控获取这些异常的方法,今天我们就来讲讲我是如何来监控我的JavaScript异常的。

背景

浏览器侧的异常分为两种类型

  • JavaScript 错误,一般来自代码。
  • 静态资源错误,他们可能来自:
    • 通过 XMLHttpRequest、Fetch() 的方式来请求的 http 资源。
    • 利用 <img> 、<script>、<video>、<audio>、<iframe> 等标签加载的资源。
    • 通过创建实例的方式,例如 new Image() 等代码来实现初始化。

既然如此,那就先从JavaScript异常下手

如何做好 JS 异常监控

我们都知道获取异常信息的常见几种方式是

  • window.onerror = cb (DOM0)
  • window.addEventListener('error', cb, true)
  • try-catch (ES提供基本的错误捕获语法)
  • Vue.errorHandler()

我在这里选择选择的是使用JavaScript的window.addEventListener()监听errorunhandledrejection

原因

  • try-catch 。这种异常一般无法直接捕获,因为写了 try-catch 说明开发者已经意识到并做了处理,当然开发者也可以通过自定义上报机制来额外地处理之。
  • 没有被 catch 的 Error。可以通过监听 error 事件捕获。
  • 当 Promise 被 reject 且没有 reject 处理器的时候触发的 PromiseRejection,监听 unhandledrejection 即可。
  • 语法错误,一般语法异常在开发、构建阶段就能发现,这类异常出现程序本身就无法正常运行。不过有特殊情况:eval 中的语法错误是可以捕获的。
  • window.addEventListener(error和unhandledrejection)可以捕获全局范围内发生的未处理异常,无论是同步还是异步代码而且错误信息足够详细并且处理起来方便。

具体代码:

代码语言:javascript
复制
// 导出一个函数,用于创建 JS 错误监视器
export function createJsErrorMonitor(options: JsErrorMonitorOptions) {


  function getBrowserWindow() {
    return window;
  }
    // 获取浏览器窗口
    const window = getBrowserWindow();
  
  // 如果没有windown,则返回
  if (!window) {
    return;
  }

  // 定义处理错误和拒绝的函数
  const handleError = (e: ErrorEvent) => {
    // 调用 onReport 函数,报告 JS 错误
    options.onReport({
      eventType: EventType.JS_ERROR,
      data: formatError(e),
    });
  };

  const handleRejection = (e: PromiseRejectionEvent) => {
    // 调用 onReport 函数,报告 JS 错误
    options.onReport({
      eventType: EventType.JS_ERROR,
      data: formatError(e),
    });
  };

  // 定义销毁监听器的函数
  const destroyListeners = () => {
    // 移除 error 事件监听器
    window.removeEventListener('error', handleError);
    // 移除 unhandledrejection 事件监听器
    window.removeEventListener('unhandledrejection', handleRejection);
  };

  // 捕获异步 error
  // 添加 error 事件监听器
  window.addEventListener('error', handleError);
  // 添加 unhandledrejection 事件监听器
  window.addEventListener('unhandledrejection', handleRejection);

  // 返回销毁监听器的函数
  return {
    destroy: destroyListeners,
  };
}

但是我们需要注意的是,我们的代码在处理跨域脚本时,还存在一些问题

假设我们要对一段浏览器跨域请求的代码进行监控效果会是怎么样呢?

示例:

代码语言:javascript
复制
    <!-- 监控脚本 -->
    <script src="监控代码"></script>

    <script>
        // 创建 JavaScript 错误监控
        Monitor.createJsErrorMonitor({
            onReport: (e) => {
                console.log(e);
            }
        });
    </script>

    <!-- 示例脚本,模拟跨域错误 -->
   <script src="https://example.com/another-nonexistent.js"></script>

    <!-- 带 crossorigin 属性的示例脚本 -->
    <script src="https://example.com/another-nonexistent.j" crossorigin="anonymous"></script>

结果是代码会出现异常无法捕捉的情况

我们的第一个 script 的异常没有被监控程序捕获,但是第二个却可以。你可能会问这是为什么呢?

这是因为浏览器跨域规则的限制,在这种情况下捕获到的 ErrorEvent 没有任何有价值的信息。(只能拿到一个模糊的 Script Error 0)。

但是解决方案很简单,我们只需要将相应的 script 标签增加一条 crossorigin="anonymous" 属性即可

代码语言:javascript
复制
<script src="xxxxx.js" crossorigin="anonymous"></script>

而在真实的 webpack 工程化环境中,我们不应该也不可能去一一的手动修改它们,而是会通过编写一个 webpack 插件,hook 到 html-webpack-pluginalterAssetTagGroups 生命周期钩子上为标签增加属性,

代码如下:

代码语言:javascript
复制
import webpack from 'webpack';
import { Hooks } from 'html-webpack-plugin';

export class AddAnonymousWebpackPlugin {
  apply(compiler: webpack.Compiler) {
    compiler.hooks.compilation.tap('AddAnonymousWebpackPlugin', (compilation) => {
      // 通过最终的 webpack 配置的 plugins 属性,根据插件的 constructor.name 拿到 html-webpack-plugin 实例
     const HtmlWebpackPluginInstance: any = compiler.options.plugins
       // 获取插件构造函数
       .map(({ constructor }) => constructor)
        // 查找HtmlWebpackPlugin构造函数
        .find(constructor => constructor && constructor.name === 'HtmlWebpackPlugin');

      if (HtmlWebpackPluginInstance) {
        // 获取 html-webpack-plugin 所有的 hooks
        const hooks = HtmlWebpackPluginInstance.getHooks(compilation) as Hooks;

        // 在插入标签之前做些什么
        hooks.alterAssetTagGroups.tap(
          'AddAnonymousWebpackPlugin', (data) => {
            // 拿到所有的标签,如果是 script 标签,并且满足我们的匹配函数,则将其 attributes.crossorigin = "anonymous"
          data.headTags.forEach(tag => {
              if (tag.tagName === 'script') {
                // 为script标签添加crossorigin属性
                tag.attributes.crossorigin = "anonymous";
              }
            });
            return data;
          },
        );
      }
    });
  }
}

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 如何做好 JS 异常监控
  • 原因
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com