JavaScript 異常的防範與監控

一套完善的前端體系應少不了異常統計與監控,即便有足夠的質量保證體系,不免會出現一些意料以外的事,尤爲是在複雜的網路環境和運行環境之下。爲了保證代碼的健壯性以及頁面的穩定性,咱們從多個方面來作異常的防範和監控。javascript

三種思路

主動防護

對於咱們操做的數據,尤爲是由 API 接口返回的,時常會有一個很複雜的深層嵌套的數據結構。爲了代碼的健壯性,不少時候須要對每一層訪問都做空值判斷,就像這樣:html

props.user &&
props.user.posts &&
props.user.posts[0] &&
props.user.posts[0].comments &&
props.user.posts[0].comments[0]

相似的代碼你們可能都寫過,沒寫過大概也見到別人寫過。看起來確實至關地不美觀,有句話說得很棒:前端

The opposite of beautiful is not ugly, but wrong.java

咱們得找到一種,更簡單、更優雅、更安全的方式來處理這種情形。參考這篇文章:Safely Accessing Deeply Nested Values In JavaScript,文章提到藉助 Ramda、Lenses、Lodash 以及 Immutable.js 等類庫的方式,並提供一個很是簡潔明瞭的原生解決方案:node

function getIn(p, o) {
    return p.reduce(function(xs, x) {
        return (xs && xs[x]) ? xs[x] : null;
    }, o);
}

接下來咱們這樣訪問就能夠了:git

getIn(['user', 'posts', 0, 'comments'], props)

若是正常訪問到,則返回對應的值,不然返回 nullgithub

這裏提供的只是主動防護的一種情形,關於如何編寫更安全的代碼這裏不做深刻展開。npm

全局監控

瀏覽器提供 window.onerror API 來幫助咱們進行全局的錯誤監控:瀏覽器

  • 當 JavaScript 運行時錯誤(包括語法錯誤)發生時,會執行 window.onerror()`
  • 當一項資源(如 <img><script> )加載失敗,能被單一的 window.addEventListener 捕獲
/**
 * @param  {String} message 錯誤信息
 * @param  {String} source  發生錯誤的腳本URL
 * @param  {Number} lineno  發生錯誤的行號
 * @param  {Number} colno   發生錯誤的列號
 * @param  {Object} error   Error對象
 */
window.onerror = function(message, source, lineno, colno, error) {
  // ...
}

其中 error 對象包含詳細的錯誤堆棧信息,在 IE9 之前,沒有這個參數。安全

針對性捕獲 try..catch

能夠經過 try..catch 來主動抓取錯誤,想要對一段代碼 try..catch,咱們能夠這樣:

try {
  // ...
} catch (error) {
  handler(error)
}

對一個函數作 try..catch 封裝:

function tryify(func) {
  return function() {
    try {
      return func.apply(this, arguments)
    } catch (error) {
      handleError(error)

      throw error
    }
  }
}

爲何是 script error

方案已經明確,但還有一些問題。在查看 JavaScript 錯誤統計時,發現 80% 以上都是 "script error"。原來,當加載自不一樣域的腳本中發生語法錯誤時,爲避免信息泄露,語法錯誤的細節將不會報告,而代之簡單的 "Script error."

而在大多數狀況下,咱們的靜態資源放在專門的 CDN 服務器上,跟站點並不在一個域,因此若是隻是簡單的抓取,只會獲得一堆意義不大的 script error

解決方案:

  • 添加 CORS 支持
  • 使用 try..catch

添加 CORS 支持

須要作兩點:

1.在 script 便籤添加 crossorigin,默認值 crossorigin="anonymous"

<script src="//xxx.com/example.js" crossorigin></script>

在 require.js 裏提供一個 onNodeCreated hook,供咱們提供擴展,要添加 crossorigin 屬性,以下所示:

<script>
// 若是在 require.js 加載以前定義了 requirejs,require.js 會將其做爲一個對象傳入 requirejs.config
var requirejs = {
  onNodeCreated: function(node, config, id, url) {
    node.setAttribute('crossorigin', 'anonymous')
  }
}
</script>
<script src="//xxx.com/require.js" charset="utf-8"></script>

在 2.2.0 版本以上可用(很遺憾的是,目前的集成解決方案版本恰好低於這個版本)。

2.同時在 CDN 服務器增長響應頭 access-control-allow-orgin,配置容許訪問 CORS 的域,不然瀏覽器直接將禁止加載。

try..catch

這一點上面也有提到,算是一種比較通用,可定製強的方案。固然,在性能上也會有一些損耗。

綜合考慮,try..catch 通用性更好,但因爲其在性能方面的一些損耗,CORS 優於 try..catch

一個監控小工具

隨後,介紹一個 JavaScript stack trace 的小工具:https://github.com/CurtisCBS/... ,工具由 Curtismirreal 共同完成。

主要是用於捕獲頁面 JavaScript 異常報錯,捕獲異常類型包含:

  • JavaScript runtime 異常捕捉 √
  • 靜態資源 load faided 異常捕捉 √
  • console.error 的異常捕獲 √
  • try..catch 錯誤捕獲 √

使用方式也很簡單,但使用 script mode 引入文件後,調用 init 函數,進行初始化配置和監聽

<script src="//unpkg.com/jstracker@latest/dist/jstracker.js"></script>

<script>
  jstracker.init({
    delay: 1000,
    maxError: 10,
    sampling: 1,
    report: function(errorLogs) {
      console.table(errorLogs)
    }
  })
</script>

若是是使用 module mode,以下:

// npm install jstracker --save
import jstracker from 'jstracker'

jstracker.init({
  concat: false,
  report: function(errorLogs) {
    // console.log('send')
  }
})

若是要使用 try..catch 捕獲,jstracker 暴露出一個 tryJS 對象,能夠處理 try..catch 包裝,就像這樣:

import jstracker from 'jstracker';

this.handleSelect = jstracker.tryJS.wrap(this.handleSelect);

全部錯誤信息統一由 report 函數處理,能夠在此之上作數據處理:

// ubt.js
import jstracker from 'jstracker';
import utility from 'utility';

jstracker.init({
    concat: false,
    report: function(errorLogs) {
        const errorLog = errorLogs[0];
        errorLog.ua = window.navigator.userAgent;
        ubtTracker.send(errorLog);
    }
});

const ubtTracker = {
    key: {
        UBT_JS_TRACKER: 'xxxx-xxxx-xxxx'
    },

    send(data) {
        const value = utility.deserializeUrl(data);
        
        xxxx.send(['trace', this.key.UBT_JS_TRACKER, value]);
    }
};

function wrapContext(ctx) {
    for (const func in ctx) {
        ctx[func] = jstracker.tryJS.wrap(ctx[func]);
    }
}

export {
    wrapContext,
    ubtTracker,
    jstracker
};

概述

做爲開發者以及項目維護者的身份,咱們應當編寫更安全健壯的代碼。但因爲環境的多樣性,不管再完善的測試,code review 都不免都所疏漏,咱們須要一套監控系統來完善整個前端體系。

在監控的時候,出於同源安全策略沒法拿到準確的錯誤信息,在此,有兩種解決方案:

  • 增長 CORS 支持
  • 使用 try..catch 進行異常捕獲

最後,咱們對整個監控工做封裝了一個基礎的核心,能夠監控 JavaScript Runtime 異常,資源加載異常,以及 try..catch 捕獲異常等,並給出一個實際工做中的示例。

相關文章
相關標籤/搜索