在长期生产 bug 并修复 bug 的循环中,上线产品不可避免出现异常。如何能快速定位到发生错误的代码位置、第一时间通知开发人员异常发生以及报错的堆栈信息、用户OS与浏览器版本等十分重要。
而错误埋点追踪系统的出现就是为了应对上述问题的解决方案。
前端异常捕获js 异常的特点是,出现不会导致 JS 引擎崩溃,最多只会终止当前执行的任务。在Javascript中,我们通常有以下两种异常捕获机制。
1. try…catch 语句能捕捉到的异常,必须是线程执行已经进入 try catch 但 try catch 未执行完的时候抛出来的,优点是能够较好地进行异常捕获,不至于使得页面由于一处错误挂掉,缺点是显得过于臃肿,大多代码使用try ... catch
包裹,影响代码可读性。以下都是无法被捕获到的情形:
2. window.onerror 最大的好处就是同步任务、异步任务都可捕获,可以得到具体的异常信息、异常文件的URL、异常的行号与列号及异常的堆栈信息,捕获异常后,统一上报至我们的日志服务器,而且可以全局监听。缺点是跨域脚本无法准确捕获异常,跨域之后 window.onerror
捕获不到正确的异常信息,而是统一返回一个Script error
,可通过在使用
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;
错误上报
两种方式:
通过动态创建一个img,这种方式无需加载任何库,而且页面是无需刷新的,将需要上报的错误数据放在url中,相当于 get 请求,没有跨域问题。缺点是有url长度限制,一般够用;ajax 与正常的接口请求无异,可以用 post。确定上报的内容,应该包含异常位置(行号,列号),异常信息,在错误堆栈中包含了绝大多数调试有关的信息,请求的时候只能以字符串方式传输,因此需要将对象进行序列化处理。
将异常数据从属性中解构出来,存入一个JSON对象将JSON对象转换为字符串将字符串转换为Base64function 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事件中的报错应该考虑防抖般的处理。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)