nuxt.js项目中全局捕获异常并生成错误日志全过程
需求:客户在使用过程中页面报错时,可以生成错误记录传回服务器,以便改进。
步骤:
一.全局捕获异常,
二.发送到服务端,
三.生成错误日志。
一.全局捕获异常
如图,vue提供了errorHandle这个方法来处理全局异常,更多详细内容参见官网。
我在页面中写了一个错误的函数,触发了errorHandler,控制台打印如下:
在utils.js中写了如下代码:
1 //系统错误捕获 2 const errorHandler = (error,vm,info)=>{ 3 getErr(error,vm,info); 4 } 5 6 Vue.config.errorHandler = errorHandler; 7 Vue.prototype.$throw = (error,vm,info)=> errorHandler(error,vm,info);
然后在另一个公用的js(commonService.js)中:
/** * 捕获异常 */ const getErr = async(err,_this,info) => { _this.$store.dispatch('getErr',{ err:err.stack, hook:info } }
说明一下,之所以写在两个文件中,是因为项目结构就是这样的。
二.发送到服务端
接下来就是如何把请求发送到node服务器上了。既然通过dispatch触发,那就统一在store目录下的index.js中处理:
// 捕获异常,存在node服务器中
async getErr({ commit },{ err_info }) {
console.log({err_info})
await axios.post('/api/getErr',{ err_info })
}
在浏览器中调试,发现接口报了500.发现err_info是undefined,即{err_info}是{"err_info":undefined}。找了半天也没发现问题,所以就不再通过dispatch触发,直接在commonService.js中拿到数据就发到node服务器。另外,查资料的时候看到dispatch中最多只能传两个参数,还有一个是commit,所有可以把其他的参数拼装在一个对象中,避免出现undefined。这条没有实践过,暂时存疑。于是我在commonService.js中修改了原来的代码,结果如下:
const getErr = async(err,_this,info) => {
await axios.post('/api/getErr',{ err:err.stack,hook:info })
}
运行一下,请求发送成功,如图:
三.生成错误日志
OK,现在服务端已经收到请求,但是还没有返回值,所以要开始写服务端的代码了。我们的项目是基于nuxt的,所以代码在api/index.js中:
// 捕获异常 router.post('/getErr', (req, res) => { req.session.getErr = {err:req.body.err,hook:req.body.hook}; return res.json({ok: true}) })
终于在network中看到返回值了...接下来就要生成错误日志并保存到服务器了。照例开始查文档:http://nodejs.cn/api/fs.html。首先要引入文件系统(File System,就是下文的fs):
const fs = require('fs');
写文件的方法是writeFile,异步地写入数据到文件,如果文件已经存在,则覆盖文件。
fs.writeFile(file, data[, options], callback)
具体参数如下:
文件名加上时间戳,每次都生成一个新文件,点点点点,于是有了好多日志文件:
贴一下代码:
// 捕获异常 router.post('/getErr', (req, res) => { req.session.getErr = {err:req.body.err,hook:req.body.hook,userInfo:req.body.userInfo}; let time = new Date(); // 记录错误内容 fs.writeFile( 'tm_wap_err_' + time.getTime() +'.txt', '报错内容:' + req.session.getErr.err + '\r\n' + '所在钩子:' + req.session.getErr.hook + '\r\n' + '报错时间:' + time.toLocaleString() + '\r\n' + '用户信息:' + JSON.stringify(req.session.getErr.userInfo), (err) => { if (err) throw err; }); // console.log(666,req.session.getErr); return res.json({ok: true}); })
这里我还记录了时间用户的登录状态。需求大体完成了,想办法优化一下,每次报错都生成一个新文件感觉太奢侈了,能不能搞个增量更新,统一记录在一个文件中?既方便查阅又省空间,还能练手,那就搞起来吧!大致思路如下:先判断文件是否存在,若不存在就创建一个。读取到文件内容以后再新加上跟新的内容,然后再写入。这种方式涉及到了I/O操作,但是相比增加很多文件还是会性能更好吧。最终代码如下:
// 捕获异常 router.post('/getErr', (req, res) => { req.session.getErr = { err:req.body.err, hook:req.body.hook, userInfo:req.body.userInfo, url:req.body.url }; let time = new Date(); let content = ''; // 若文件不存在,就创建一个吧! fs.exists("toolmall_wap_err.txt", function(exists) { if(!exists) { fs.writeFile('toolmall_wap_err.txt','', function(err) { if(err) { return console.log(err); } }); } }); // 增量更新日志文件,先读取 fs.readFile('toolmall_wap_err.txt','utf8',(err, data) => { if (err) throw err; data += '\r\n'; data += '报错内容:' + req.session.getErr.err + '\r\n'; data += '所在钩子:' + req.session.getErr.hook + '\r\n'; data += '报错时间:' + time.toLocaleString() + '\r\n'; data += '报错页面:' + req.session.getErr.url + '\r\n'; data += '用户信息:' + JSON.stringify(req.session.getErr.userInfo) + '\r\n'; content = data; // 记录错误内容 fs.writeFile( 'toolmall_wap_err.txt', content, (err) => { if (err) throw err; }); }); return res.json({ok: true}); })
最终成果如下:
以上是文件夹,放在了根目录下。
下面是日志的内容:
终于完成了,今晚加鸡腿~