JS錯誤監控 上報後臺你瞭解多少?

1.try-catch

1. 運行時錯誤

try {

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

2. 異步錯誤

try {
  setTimeout(() => {
  
    fn()        // 異步錯誤
  })
} catch(e) {

  console.log('我感知不到錯誤');
  
  console.log(e);
}

複製代碼

總結: 只能捕獲捉到運行時非異步錯誤,異步錯誤就顯得無能爲力,捕捉不到javascript

2. window.onerror

1.同步錯誤css

/**
 * @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,能夠阻止瀏覽器報告錯誤的默認行爲html

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.onerrorjava

    如script,image等標籤src引用,會返回一個event對象 ,TIPS: object.onerror不會冒泡到window對象,因此window.onerror沒法監控資源加載錯誤node

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

    返回已成功加載的資源列表,而後自行作比對差集運算,覈實哪些文件沒有加載成功編程

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錯誤json

  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 退出segmentfault

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監控總結

相關文章
相關標籤/搜索