如何搭建前端異常監控系統

什麼是異常

是指用戶在使用應用時,沒法獲得預期的結果。不一樣的異常帶來的後果程度不一樣,輕則引發用戶使用不悅,重則致使產品沒法使用,從而使用戶喪失對產品的承認。css

爲何要處理異常

  • 加強用戶體驗
  • 遠程定位問題
  • 沒法復現問題,特別是移動端,各類緣由,多是系統版本,機型等等

前端有哪些異常

異常 頻率
JavaScript異常(語法錯誤、代碼錯誤) 常常
靜態資源加載異常(img、js、css) 偶爾
Ajax 請求異常 偶爾
promise異常 較少
iframe 異常 較少

如何捕獲異常

try-catch

try-catch 只能捕獲同步運行錯誤,對語法和異步錯誤卻捕獲不到。html

一、同步運行錯誤前端

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

結果:try: ReferenceError: kill is not definedvue

二、沒法捕獲語法錯誤react

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

結果:Unterminated string constantgit

編譯器可以阻止運行語法錯誤。github

三、沒法捕獲異步錯誤promise

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

結果:Uncaught TypeError: Cannot read property 'map' of undefined瀏覽器

window.onerror

當JavaScript運行時錯誤(包括語法錯誤)發生時,window會觸發一個ErrorEvent接口的error事件,並執行window.onerror()。若該函數返回true,則阻止執行默認事件處理函數。服務器

一、同步運行錯誤

/**
* @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

二、沒法捕獲語法錯誤

/**
* @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

編譯器可以阻止運行語法錯誤。

三、異步錯誤

/**
* @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);

結果:捕獲異常: Uncaught TypeError: Cannot read property 'map' of undefined

window.addEventListener('error')

當一項資源(如<img><script>)加載失敗,加載資源的元素會觸發一個Event接口的error事件,並執行該元素上的onerror()處理函數。這些error事件不會向上冒泡到window,不過(至少在Firefox中)能被單一的window.addEventListener捕獲。

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

結果:捕獲異常:Event {isTrusted: true, type: "error", target: img, currentTarget: Window, eventPhase: 1, …}

window.addEventListener('unhandledrejection')

當Promise 被 reject 且沒有 reject 處理器的時候,會觸發 unhandledrejection 事件;這可能發生在 window 下,但也可能發生在 Worker 中。 這對於調試回退錯誤處理很是有用。

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

Promise.reject('promise');

結果:捕獲異常: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

用戶界面中的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)

異常上報

一、Ajax發送數據
二、動態建立img標籤

若是異常數據量大,致使服務器負載高,調整發送頻率(能夠考慮把異常信息存儲在客戶端,設定時間閥值,進行上報)或設置採集率(採集率應該經過實際狀況來設定,隨機數,或者某些用戶特徵都是不錯的選擇)。

流程圖

異常監控流程圖

博客

歡迎關注個人博客

參考文獻

相關文章
相關標籤/搜索