如何做前端异常监控

Query 2月前 ⋅ 267 阅读
ad

异常是指用户在使用应用时无法得到预期结果,处理异常的重要性以及前端中的异常类型和捕获方法。其中,try-catch只能捕获同步运行错误,而无法捕获语法和异步错误。另外,异常的上报也是需要考虑的问题,例如调整发送频率和设置采集率等。

原文作者:发声的沉默者
原文地址:人类身份验证 - SegmentFault

什么是异常

用户在使用应用时,可能会遇到预期之外的结果。不同的异常情况带来的后果也会有所不同,有些可能只会让用户感到不满意,而有些则可能导致产品无法正常使用,从而使用户失去对产品的信任。

为什么要处理异常

  • 增强用户体验
  • 远程定位问题
  • 无法复现问题,特别是移动端,各种原因,可能是系统版本,机型等等

前端有哪些异常

如何捕获异常

try-catch

try-catch 只能捕获同步运行错误,对语法和异步错误却捕获不到。

1、同步运行错误

try {
    kill;
} catch(err) {
    console.error('try: ', err);
}

结果:try: ReferenceError: kill is not defined

2、无法捕获语法错误

try {
    let name = '1;
} catch(err) {
    console.error('try: ', err);
}

结果:Unterminated string constant

编译器能够阻止运行语法错误。

3、无法捕获异步错误

try {
    setTimeout(() => {
        undefined.map(v => v);
    }, 1000);
} catch(err) {
    console.error('try: ', err);
}

在此代码中,出现了一个未捕获的类型错误。无法读取未定义的属性'map'。

window.onerror

当JavaScript在运行时发生错误(包括语法错误),window会引发一个error事件,该事件接口属于ErrorEvent。同时,window.onerror()函数将被执行。如果该函数返回true,则会阻止默认的事件处理函数执行。

1、同步运行错误

/**
* @param {String}  message   错误信息
* @param {String}  source    出错文件
* @param {Number}  lineno    行号
* @param {Number}  colno     列号
* @param {Object}  error     error对象
*/
window.onerror = (message, source, lineno, colno, error) => {
    console.error('捕获异常:', message, source, lineno, colno, error);
    return true;
};

kill;

结果:捕获异常: Uncaught ReferenceError: kill is not defined

2、无法捕获语法错误

/**
* @param {String}  message   错误信息
* @param {String}  source    出错文件
* @param {Number}  lineno    行号
* @param {Number}  colno     列号
* @param {Object}  error     error对象
*/
window.onerror = (message, source, lineno, colno, error) => {
    console.error('捕获异常:', message, source, lineno, colno, error);
    return true;
};

let name = '1;

结果:Unterminated string constant

编译器能够阻止运行语法错误。

3、异步错误

/**
* @param {String}  message   错误信息
* @param {String}  source    出错文件
* @param {Number}  lineno    行号
* @param {Number}  colno     列号
* @param {Object}  error     error对象
*/
window.onerror = (message, source, lineno, colno, error) => {
    console.error('捕获异常:', message, source, lineno, colno, error);
    return true;
};

setTimeout(() => {
    undefined.map(v => v);
}, 1000);

在SEO运营中,捕获到异常信息:未捕获的类型错误:无法读取未定义的属性'map'。

window.addEventListener('error')

当加载资源(例如<img>或<script>)时,如果出现加载失败的情况,元素会触发error事件,并调用元素上的onerror()处理函数。这些error事件不会冒泡到window对象,但可以通过window.addEventListener在Firefox中进行捕获。

<script>
window.addEventListener('error', (err) => {
    console.error('捕获异常:', err);
}, true);
</script>
<img src="./test.jpg" />

我们可以使用符合SEO规则的方式重新表达这段文字: "我们检测到一次异常捕获事件:"错误事件"{isTrusted: true, type: "error", target: img, currentTarget: Window, eventPhase: 1, …}"

window.addEventListener('unhandledrejection')

当Promise被拒绝且没有拒绝处理器时,会触发未处理的拒绝事件(unhandledrejection);这种情况可能发生在window环境下,也可能发生在Worker环境中。这对于调试退回错误处理非常有帮助,并符合SEO规则。

window.addEventListener("unhandledrejection", (err) => {
    err.preventDefault();
    console.error('捕获异常:', err);
});

Promise.reject('promise');

在符合SEO规则的情况下,重写以下文本:捕获到一个异常,异常信息是:PromiseRejectionEvent {isTrusted: true, promise: Promise, reason: "promise", type: "unhandledrejection", target: Window, …}

Vue

Vue.config.errorHandler = (err, vm, info) => {
  console.error('捕获异常:', err, vm, info);
}

React

React 16强化了错误处理功能,新增了一个名为componentDidCatch的内置函数,能够方便地捕获React产生的错误信息。

componentDidCatch(error, info) {
    console.error('捕获异常:', error, info);
}

但是,推荐ErrorBoundary

为了遵守SEO规则,用户界面中的JavaScript错误不应该对整个应用程序造成破坏。为了解决React用户在面对此问题时的困扰,React 16引入了一个新的概念,即“错误边界”。

新建ErrorBoundary.jsx组件:

import React from 'react';
import { Result, Button } from 'antd';

class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false, info: '' };
    }
  
    static getDerivedStateFromError(error) {
        return { hasError: true };
    }

    componentDidCatch(error, info) {
        this.setState({
            info: error + ''
        });
    }
  
    render() {
        if (this.state.hasError) {
            // 你可以渲染任何自定义的降级 UI
            return (
                <Result
                    status="500"
                    title="500"
                    subTitle={this.state.info}
                    extra={<Button type="primary">Report feedback</Button>}
                />
            );
        }
  
        return this.props.children; 
    }
}

export default ErrorBoundary;

使用:

<ErrorBoundary>
    <App />
</ErrorBoundary>

注意

错误边界不会捕获以下方面的错误

  • 事件处理程序
  • 异步代码(例如setTimeout或requestAnimationFrame回调)
  • 服务器端渲染
  • 在错误边界本身(而不是其子级)中引发的错误

iframe

由于浏览器的"同源策略"限制,处理iframe异常不太容易,除了基本属性如宽度和高度,无法从iFrame中获取太多信息。

<script>
    document.getElementById("myiframe").onload = () => {
        const self = document.getElementById('myiframe');
        
        try {
            (self.contentWindow || self.contentDocument).location.href;
        } catch(err) {
            console.log('捕获异常:' + err);
        }
    };
</script>

<iframe id="myiframe" src="https://nibuzhidao.com" frameBorder="0" />

Sentry

业界非常优秀的一款监控异常的产品,作者也是用的这款,文档齐全。

需要上报哪些信息

  • 错误id
  • 用户id
  • 用户名
  • 用户IP
  • 设备
  • 错误信息
  • 游览器
  • 系统版本
  • 应用版本
  • 机型
  • 时间戳
  • 异常级别(error、warning、info)

异常上报

  • 1、Ajax发送数据
  • 2、动态创建img标签

如果服务器负载过高,可能是因为异常数据量过大。为了解决这个问题,可以考虑将异常信息存储在客户端,并设定时间阀值,当达到阀值时进行上报。另外,可以调整采集率来减少数据量。采集率的设定应该根据实际情况来进行,可以使用随机数或某些用户特征来选择合适的采集率。

本文介绍了异常以及为什么要处理异常的重要性。同时详细描述了前端中的异常类型(同步运行错误、语法错误、异步错误)以及相应的捕获方法。此外,还提及了异常的上报问题,如何处理大量异常数据的服务器负载问题以及调整发送频率和设置采集率的方法。

参考链接:https://zhuanlan.zhihu.com/p/157618042

关于Webfunny

Webfunny专注于前端监控系统,前端埋点系统的研发。 致力于帮助开发者快速定位问题,帮助企业用数据驱动业务,实现业务数据的快速增长。支持H5/Web/PC前端、微信小程序、支付宝小程序、UniApp和Taro等跨平台框架。实时监控前端网页、前端数据分析、错误统计分析监控和BUG预警,第一时间报警,快速修复BUG!支持私有化部署,Docker容器化部署,可支持千万级PV的日活量!

  点赞 0   收藏 0
  • Query
    共发布11篇文章 获得0个收藏
全部评论: 0