收集前端错误
编写错误上报逻辑,上报错误
代码上线打包将sourcemap文件上传至错误监控服务器
发生错误时监控服务器接收错误并记录到日志中
根据sourcemap和错误日志内容进行错误分析
| 异常类型 | 同步方法 | 异步方法 | 资源加载 | Promise | async/await |
|---|---|---|---|---|---|
| try/catch | ✔️ | ✔️ | |||
| onerror | ✔️ | ✔️ | |||
| error事件监听 | ✔️ | ✔️ | ✔️ | ||
| unhandledrejection事件监听 | ✔️ | ✔️ |
实际上我们可以将unhandledrejection事件抛出的异常再次抛出就可以统一通过error事件进行处理了。
- window.addEventListener("unhandledrejection", e => {
- throw e.reason
- });
- window.addEventListener('error', args => {
- console.log(
- 'error event:', args
- );
- return true;
- }, true);
为了对Vue发生的异常进行统一的上报,需要利用vue提供的handleError句柄。一旦Vue发生异常都会调用这个方法。
我们在src/main.js
- Vue.config.errorHandler = function (err, vm, info) {
- console.log('errorHandle:', err)
- }
动态创建img标签
其实上报就是要将捕获的异常信息发送到后端。最常用的方式首推动态创建标签方式。因为这种方式无需加载任何通讯库,而且页面是无需刷新的。基本上目前包括百度统计 Google统计都是基于这个原理做的埋点。
new Image().src = 'http://localhost:7001/monitor/error'+ '?info=xxxxxx'
通过动态创建一个img,浏览器就会向服务器发送get请求。可以把你需要上报的错误数据放在querystring字符串中,利用这种方式就可以将错误上报到服务器了。
Ajax上报
上报哪些数据
error事件参数
| 属性名称 | 含义 | 类型 |
|---|---|---|
| message | 错误信息 | string |
| filename | 异常的资源url | string |
| lineno | 异常行号 | int |
| colno | 异常列号 | int |
| error | 错误对象 | object |
| error.message | 错误信息 | string |
| error.stack | 错误信息 | string |
核心是错误栈,包含了绝大多数调试有关新。其中包括了异常位置(行号,列号),异常信息
[译]JavaScript错误处理和堆栈追踪 · Issue #49 · dwqs/blog · GitHub
由于通讯的时候只能以字符串方式传输,我们需要将对象进行序列化处理。
大概分成以下三步:
将异常数据从属性中解构出来存入一个JSON对象
将JSON对象转换为字符串
将字符串转换为Base64
当然在后端也要做对应的反向操作
- window.addEventListener('error', args => {
- console.log(
- 'error event:', args
- );
- uploadError(args)
- return true;
- }, true);
- function uploadError({
- lineno,
- colno,
- error: {
- stack
- },
- timeStamp,
- message,
- filename
- }) {
- // 过滤
- const info = {
- lineno,
- colno,
- stack,
- timeStamp,
- message,
- filename
- }
- // const str = new Buffer(JSON.stringify(info)).toString("base64");
- const str = window.btoa(JSON.stringify(info))
- const host = 'http://localhost:7001/monitor/error'
- new Image().src = `${host}?info=${str}`
- }
异常上报的数据一定是要有一个后端服务接收才可以。
我们就以比较流行的开源框架eggjs为例来演示
- # 全局安装egg-cli
- npm i egg-init -g
- # 创建后端项目
- egg-init backend --type=simple
- cd backend
- npm i
- # 启动项目
- npm run dev
首先在app/router.js添加一个新的路由
- module.exports = app => {
- const { router, controller } = app;
- router.get('/', controller.home.index);
- // 创建一个新的路由
- router.get('/monitor/error', controller.monitor.index);
- };
创建一个新的controller (app/controller/monitor)
- 'use strict';
-
- const Controller = require('egg').Controller;
- const { getOriginSource } = require('../utils/sourcemap')
- const fs = require('fs')
- const path = require('path')
-
- class MonitorController extends Controller {
- async index() {
- const { ctx } = this;
- const { info } = ctx.query
- const json = JSON.parse(Buffer.from(info, 'base64').toString('utf-8'))
- console.log('fronterror:', json)
- ctx.body = '';
- }
- }
-
- module.exports = MonitorController;
错误记入日志。实现的方法可以自己用fs写,也可以借助log4js这样成熟的日志库。
当然在eggjs中是支持我们定制日志那么我么你就用这个功能定制一个前端错误日志好了。
在/config/config.default.js中增加一个定制日志配置
- // 定义前端错误日志
- config.customLogger = {
- frontendLogger : {
- file: path.join(appInfo.root, 'logs/frontend.log')
- }
- }
在/app/controller/monitor.js中添加日志记录
- async index() {
- const { ctx } = this;
- const { info } = ctx.query
- const json = JSON.parse(Buffer.from(info, 'base64').toString('utf-8'))
- console.log('fronterror:', json)
- // 记入错误日志
- this.ctx.getLogger('frontendLogger').error(json)
- ctx.body = '';
- }