IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 浅析前端异常埋点系统 -> 正文阅读

[JavaScript知识库]浅析前端异常埋点系统

在长期生产 bug 并修复 bug 的循环中,上线产品不可避免出现异常。如何能快速定位到发生错误的代码位置、第一时间通知开发人员异常发生以及报错的堆栈信息、用户OS与浏览器版本等十分重要。

而错误埋点追踪系统的出现就是为了应对上述问题的解决方案。

前端异常捕获

js 异常的特点是,出现不会导致 JS 引擎崩溃,最多只会终止当前执行的任务。在Javascript中,我们通常有以下两种异常捕获机制。

1. try…catch 语句能捕捉到的异常,必须是线程执行已经进入 try catch 但 try catch 未执行完的时候抛出来的,优点是能够较好地进行异常捕获,不至于使得页面由于一处错误挂掉,缺点是显得过于臃肿,大多代码使用try ... catch包裹,影响代码可读性。以下都是无法被捕获到的情形:

  1. 异步任务抛出的异常(执行时 try catch 已经从执行完了,比如 setTimeout)。
  2. promise,正常情况下异常被 promise 内部捕获到了,并未往上抛异常,使用 promise.catch()?处理或者 promise 前用 await 就可以被 try... catch 捕获了。
  3. 语法错误(代码运行前,在编译时就检查出来了的错误)。

2. window.onerror?最大的好处就是同步任务、异步任务都可捕获,可以得到具体的异常信息、异常文件的URL、异常的行号与列号及异常的堆栈信息,捕获异常后,统一上报至我们的日志服务器,而且可以全局监听。缺点是跨域脚本无法准确捕获异常,跨域之后?window.onerror?捕获不到正确的异常信息,而是统一返回一个Script error,可通过在<script>使用?crossorigin属性来规避这个问题:

window.addEventListener('error', function() {
  console.log(error);
  // ...
  // 异常上报
});

3. Promise?内部异常,如果遗漏处理,最好是添加一个 Promise 全局异常捕获事件?unhandledrejection?:

window.addEventListener("unhandledrejection", e => {
 console.log('unhandledrejection',e)
});

4.?vue工程异常 ,window.onerror 并不能捕获.vue文件发生的获取。使用Vue.config.errorHandler这样的Vue全局配置处理函数,被调用时,可获取错误信息和Vue 实例:

//main.js
import { createApp } from "vue";
import App from "./App.vue";

let app = createApp(App);
app.config.errorHandler = function(e) {
  console.log(e);
  //错误上报...
};
app.mount("#app");

综合以上所述的前端异常捕获方式:

import { createApp } from "vue";
import App from "./App.vue";

let app = createApp(App);

window.addEventListener(
  "error",
  (e) => {
    console.log(e);
    /** TODO:上报逻辑 */
    return true;
  },
  true
);
/** 处理未捕获的异常,主要是promise内部异常,统一抛给 onerror */
window.addEventListener("unhandledrejection", (e) => {
  throw e.reason;
});
/** 框架异常统一捕获 */
app.config.errorHandler = function(err, vm, info) {
  /** TODO:上报逻辑 */
  console.log(err, vm, info);
};
app.mount("#app");

sourcemap

通常在生产环境下的代码是经过 webpack 打包后压缩混淆的,否则源代码泄漏易造成安全问题。在该环境下,代码被压缩成了一行。而webpack 打包后会生成一份.map的脚本文件,浏览器利用它对错误位置进行追踪,但这种做法并不可取。

更为推荐的是在服务端使用 Node.js 接收到的日志信息时使用 source-map 对其进行解析,以避免源代码的泄露造成风险。

vue.config.js配置里通过属性?productionSourceMap: true来控制 webpack 是否生成 map 文件。

编写一个插件让 webpack 在打包完成后触发一个钩子实现 sourcemap 文件上传

首先,在vue.config.js中进行配置:

import SourceMapUploader from "./source-map-upload";
module.exports = {
    configureWebpack: {
        resolve: {
            alias: {
                "@": resolve("src"),
            },
        },
        plugins: [
             new SourceMapUploader({url: "http://localhost:3000/upload"})
        ],
    }
    //   chainWebpack: (config) => {},
}

sourcemap 文件上传实现(source-map-upload.js?):

const fs = require("fs");
const http = require("http");
const path = require("path");

class SourceMapUploader {
  constructor(options) {
    this.options = options;
  }
  /**
   * 用到了hooks,done表示在打包完成之后
   * status.compilation.outputOptions就是打包的dist文件
   */
  apply(compiler) {
    if (process.env.NODE_ENV == "production") {
      compiler.hooks.done.tap("sourcemap-uploader", async (status) => {
        /** 读取目录下的 map 后缀的文件 */
        let dir = path.join(status.compilation.outputOptions.path, "/js/");
        let chunks = fs.readdirSync(dir);
        let map_file = chunks.filter((item) => {
          return item.match(/\.js\.map$/) !== null;
        });
        /** 上传 sourcemap */
        while (map_file.length > 0) {
          let file = map_file.shift();
          await this.upload(this.options.url, path.join(dir, file));
        }
      });
    }
  }
  
  /** 调用upload接口,上传文件 */
  upload(url, file) {
    return new Promise((resolve) => {
      let req = http.request(`${url}?name=${path.basename(file)}`, {
        method: "POST",
        headers: {
          "Content-Type": "application/octet-stream",
          Connection: "keep-alive",
        },
      });

      let fileStream = fs.createReadStream(file);
      fileStream.pipe(req, { end: false });
      fileStream.on("end", function() {
        req.end();
        resolve();
      });
    });
  }
}
module.exports = SourceMapUploader;

错误上报

两种方式:

  1. 通过动态创建一个img,这种方式无需加载任何库,而且页面是无需刷新的,将需要上报的错误数据放在url中,相当于 get 请求,没有跨域问题。缺点是有url长度限制,一般够用;
  2. ajax 与正常的接口请求无异,可以用 post。

确定上报的内容,应该包含异常位置(行号,列号),异常信息,在错误堆栈中包含了绝大多数调试有关的信息,请求的时候只能以字符串方式传输,因此需要将对象进行序列化处理。

  1. 将异常数据从属性中解构出来,存入一个JSON对象
  2. 将JSON对象转换为字符串
  3. 将字符串转换为Base64
function uploadErr({ lineno, colno, error: { stack }, message, filename }) {
  let str = window.btoa(
    JSON.stringify({
      lineno,
      colno,
      error: { stack },
      message,
      filename,
    })
  );
  let front_ip = "http://localhost:3000/error";
  new Image().src = `${front_ip}?info=${str}`;
}

后端或者监控平台接收到信息后进行对应的反向操作,就可以在日志中记录。

读取到监控平台时,先读取对应的 map文件(按 filename 对应),然后只需传入压缩后的JS报错行号列号即可,就会返回压缩前JS的错误信息。

而且,在上报的时候增加报错时间,用户浏览器信息,对错误类型区分,自定义错误类型统计,引入图表可视化展示,更加直观地追踪。

同时对上报频率做限制。如类似mouseover事件中的报错应该考虑防抖般的处理。

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-24 18:03:56  更:2022-05-24 18:06:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:08:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码