JS错误监控 上报后台你了解多少?

1.try-catch

1. 运行时错误

try {

    fn() 
    
} catch (e) {
    
    console.log(e);
}

2. 异步错误

try {
  setTimeout(() => {
  
    fn()        // 异步错误
  })
} catch(e) {

  console.log('我感知不到错误');
  
  console.log(e);
}

复制代码

总结: 只能捕获捉到运行时非异步错误,异步错误就显得无能为力,捕捉不到

2. window.onerror

1.同步错误

/**
 * @param {String}  msg    错误信息
 * @param {String}  url    出错文件
 * @param {Number}  row    行号
 * @param {Number}  col    列号
 * @param {Object}  error  错误详细信息
 */
window.onerror = function(msg, url, row, col, error) {
    console.log('我知道错误了');
    return true;
};

new Error();

2.异步错误

window.onerror = function(msg, url, row, col, error) {
    console.log('我知道异步错误了');
    return true;
};

setTimeout(() => {
   new Error();;
});
复制代码

注意:在事件处理程序中返回false,可以阻止浏览器报告错误的默认行为

window.onerror = function(msg, url, row, col, error) {
    return false;
}
复制代码

当我们遇到 "img src="./404.png" 报 404 网络请求异常的时候,window.onerror 是无法帮助我们捕获到异常的。

3.资源加载错误

  • 1. object.onerror
  • 2. performance.getEntries()
  • 3. Error事件捕获 (全局监控静态资源异常)
  1. object.onerror

    如script,image等标签src引用,会返回一个event对象 ,TIPS: object.onerror不会冒泡到window对象,所以window.onerror无法监控资源加载错误

var img = new Image();
img.src = 'http://xxx.com/xxx.jpg';
img.onerror = function(event) {
    console.log(event);
}
复制代码
  1. performance.getEntries()

    返回已成功加载的资源列表,然后自行做比对差集运算,核实哪些文件没有加载成功

var result = [];
window.performance.getEntries().forEach(function (perf) {
    result.push({
        'url': perf.name,
        'entryType': perf.entryType,
        'type': perf.initiatorType,
        'duration(ms)': perf.duration
    });
});
console.log(result);

复制代码

3. Error事件捕获

**404.png**
复制代码
window.addEventListener('error', (msg, url, row, col, error) => {
  console.log('我知道 404 错误了');
  console.log(
    msg, url, row, col, error
  );
  return false;
}, true);

复制代码

4. 全局去捕获promise error

window.addEventListener("unhandledrejection", function(e) {
    e.preventDefault()
    console.log('我知道 promise 的错误了');
    console.log(e.reason);
    return true;
});
Promise.reject('promise error').catch((err)=>{
    console.log(err);
})
new Promise((resolve, reject) => {
    reject('promise error');
}).catch((err)=>{
    console.log(err);
})
new Promise((resolve) => {
    resolve();
}).then(() => {
    throw 'promise error'
});
new Promise((resolve, reject) => {
    reject(123);
})

复制代码
5. 跨域的js错误捕获

一般涉及跨域的js运行错误时会抛出错误提示script error,但没有具体信息(如出错文件,行列号提示等), 可利用资源共享策略来捕获跨域js错误

  1. 客户端:在script标签增加crossorigin属性(客户端)
  2. 服务端:js资源响应头Access-Control-Allow-Origin: *

6. Iframe错误

<iframe src="./iframe.html" frameborder="0"></iframe>
<script>
  window.frames[0].onerror = function (msg, url, row, col, error) {
    console.log('我知道 iframe 的错误了,也知道错误信息');
    console.log({
      msg,  url,  row, col, error
    })
    return true;
  };
</script>
复制代码

7. Node 错误监控

  1. uncaughtException来全局捕获未捕获的Error

未捕获的 JavaScript 异常一直冒泡回到事件循环时,会触发 'uncaughtException' 事件。 默认情况下,Node.js 通过将堆栈跟踪打印到 stderr 并使用退出码 1 来处理此类异常,从而覆盖任何先前设置的 process.exitCode。 为 'uncaughtException' 事件添加处理程序会覆盖此默认行为。 或者,更改 'uncaughtException' 处理程序中的 process.exitCode,这将导致进程退出并提供退出码。 否则,在存在这样的处理程序的情况下,进程将以 0 退出

process.on("uncaughtException", function(a) {
    
})


复制代码
  1. unhandledRejection局部错误

如果在事件循环的一次轮询中,一个 Promise 被 rejected,并且此 Promise 没有绑定错误处理器, 'unhandledRejection 事件会被触发。 当使用 Promise 进行编程时,异常会以 "rejected promises" 的形式封装。Rejection 可以被 promise.catch() 捕获并处理,并且在 Promise 链中传播。'unhandledRejection 事件在探测和跟踪 promise 被 rejected,并且 rejection 未被处理的场景中是很有用的。

process.on("unhandledRejection", function(a) {
   
});
复制代码

8. console.error

var consoleError = window.console.error; 
window.console.error = function () { 
    alert(JSON.stringify(arguments)); // 自定义处理
    consoleError && consoleError.apply(window, arguments); 
};
复制代码

9. 接口错误

  1. xmlHttpRequest封装
if(!window.XMLHttpRequest) return;
var xmlhttp = window.XMLHttpRequest;
var _oldSend = xmlhttp.prototype.send;
var _handleEvent = function (event) {
    if (event && event.currentTarget && event.currentTarget.status !== 200) {
          // 自定义错误上报 }
}
xmlhttp.prototype.send = function () {
    if (this['addEventListener']) {
        this['addEventListener']('error', _handleEvent);
        this['addEventListener']('load', _handleEvent);
        this['addEventListener']('abort', _handleEvent);
    } else {
        var _oldStateChange = this['onreadystatechange'];
        this['onreadystatechange'] = function (event) {
            if (this.readyState === 4) {
                _handleEvent(event);
            }
            _oldStateChange && _oldStateChange.apply(this, arguments);
        };
    }
    return _oldSend.apply(this, arguments);
}
复制代码
  1. fetch封装
if(!window.fetch) return;
    let _oldFetch = window.fetch;
    window.fetch = function () {
        return _oldFetch.apply(this, arguments)
        .then(res => {
            if (!res.ok) { // True if status is HTTP 2xx
                // 上报错误
            }
            return res;
        })
        .catch(error => {
            // 上报错误
            throw error;  
        })
}

复制代码

统计每个页面的JS和CSS加载时间

在JS或者CSS加载之前打上时间戳,加载之后打上时间戳,并且将数据上报到后台。加载时间反映了页面白屏,可操作的等待时间。

<script>var cssLoadStart = +new Date</script>
<link rel="stylesheet" href="xxx.css" type="text/css" media="all">
<link rel="stylesheet" href="xxx1.css" type="text/css" media="all">
<link rel="stylesheet" href="xxx2.css" type="text/css" media="all">
<sript>
   var cssLoadTime = (+new Date) - cssLoadStart;
   var jsLoadStart = +new Date;
</script>
<script type="text/javascript" src="xx1.js"></script>
<script type="text/javascript" src="xx2.js"></script>
<script type="text/javascript" src="xx3.js"></script>
<script>
   var jsLoadTime = (+new Date) - jsLoadStart;
   var REPORT_URL = 'xxx/cgi?data='
   var img = new Image;
   img.onload = img.onerror = function(){
      img = null;
   };
   img.src = REPORT_URL + cssLoadTime + '-' + jsLoadTime;
</script>

复制代码

上报频率

错误信息频繁发送上报请求,会对后端服务器造成压力。 项目中我们可通过设置采集率,或对规定时间内数据汇总再上报,减少请求数量,从而缓解服务端压力。

// 借鉴别人的一个例子
Reporter.send=function(data) {
    // 只采集30%
    if(Math.random() < 0.3) {
        send(data); // 上报错误
    }
}
复制代码

异常上报后台服务器

  1. window.onerror
window.onerror = function(errorMessage, scriptURI, lineNo, columnNo, error) {

    // 构建错误对象
    var errorObj = {
        errorMessage: errorMessage || null,
        scriptURI: scriptURI || null,
        lineNo: lineNo || null,
        columnNo: columnNo || null,
        stack: error && error.stack ? error.stack : null
    };

    if (XMLHttpRequest) {
        var xhr = new XMLHttpRequest();

        xhr.open('post', '/middleware/errorMsg', true); // 上报给node中间层处理
        xhr.setRequestHeader('Content-Type', 'application/json'); // 设置请求头
        xhr.send(JSON.stringify(errorObj)); // 发送参数
    }
}
复制代码
  1. sourceMap解析
const express = require('express');
const fs = require('fs');
const router = express.Router();
const fetch = require('node-fetch');
const sourceMap = require('source-map');
const path = require('path');
const resolve = file => path.resolve(__dirname, file);

// 定义post接口
router.post('/errorMsg/', function(req, res) {
    let error = req.body; // 获取前端传过来的报错对象
    let url = error.scriptURI; // 压缩文件路径

    if (url) {
        let fileUrl = url.slice(url.indexOf('client/')) + '.map'; // map文件路径

        // 解析sourceMap
        let smc = new sourceMap.SourceMapConsumer(fs.readFileSync(resolve('../' + fileUrl), 'utf8')); // 返回一个promise对象

        smc.then(function(result) {

            // 解析原始报错数据
            let ret = result.originalPositionFor({
                line: error.lineNo, // 压缩后的行号
                column: error.columnNo // 压缩后的列号
            });

            let url = ''; // 上报地址

            // 将异常上报至后台
            fetch(url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    errorMessage: error.errorMessage, // 报错信息
                    source: ret.source, // 报错文件路径
                    line: ret.line, // 报错文件行号
                    column: ret.column, // 报错文件列号
                    stack: error.stack // 报错堆栈
                })
            }).then(function(response) {
                return response.json();
            }).then(function(json) {
                res.json(json);         
            });
        })
    }
});

module.exports = router;
复制代码

我们何时上报后台呢?

if (window.requestIdleCallback) {
    window.requestIdleCallback()
} else {
    setTimeout()
}
复制代码

体外活

  1. Vue 2.x中我们应该这样捕获全局异常:
Vue.config.errorHandler = function (err, vm, info) {
    let { 
        message, // 异常信息
        name, // 异常名称
        script,  // 异常脚本url
        line,  // 异常行号
        column,  // 异常列号
        stack  // 异常堆栈信息
    } = err;

    // vm为抛出异常的 Vue 实例
    // info为 Vue 特定的错误信息,比如错误所在的生命周期钩子
}
复制代码
  1. React 16.x 版本中引入了 Error Boundary:
class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false };
    }

    componentDidCatch(error, info) {
        this.setState({ hasError: true });

        // 将异常信息上报给服务器
        logErrorToMyService(error, info); 
    }

    render() {
        if (this.state.hasError) {
            return '出错了';
        }

        return this.props.children;
    }
}
复制代码

原文链接萝卜

JS监控总结

转载于:https://juejin.im/post/5c6ec4526fb9a049d37fb685

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值