錯誤監控&性能上報

一、window.onerror

indow.onerror = function(message, source, lineno, colno, error) { ... }

onerror有五個入參:

參數 解釋
msg 錯誤信息
url 錯誤所在文件
line 錯誤所在代碼行,整型
colno 錯誤所在代碼列,整型
erro 錯誤Error對象

只需要把這些信息回傳到server端即可,再配合sourcemap的話我們就可以知道是源碼中的哪一行出錯了,從而實現完美的錯誤實時監控系統了。然而要完美還是需要做很多工作的。

1. 基本特性

以下三種方式可以引發onerror

  1. 運行時錯誤,例如無效的對象引用或安全限制
  2. 下載錯誤,如圖片
  3. 在IE9中,獲取多媒體數據失敗也會引發

可以通過設置returnValue=true,或直接return true來阻止瀏覽器顯示錯誤信息。但不會阻止script debuggers彈出的調試框。

只有運行錯誤才會觸發onerror,語法錯誤不會觸發。

<script> 標簽不支持onerror

定義在 <body> 標簽上的onerror屬性相當于window.onerror (經測試,Firefox、Opera支持,IE9、chrome無反應)。

2. 瀏覽器兼容性

QuirksM3ode列出的各瀏覽器對onError的支持情況
?Chrome 13+
?Firefox 6.1+
?Internet Explorer 5.5+
?Safari 5.1+
?Opera 11.61+ (QuirksMode 測試到11.51尚不支持,我手頭上的11.61已支持)
window對象外,支持onerror 的元素:

元素 支持情況
<img> 全支持
<script> IE9/IE10/safari 5.1+/chrome 13+ 支持
<css> 和 <iframe> 不支持onerror

3. 跨域

我們的js文件一般都是和網站不同域的,這是為了提高頁面的渲染速度以及架構的可維護性(單獨CDN域名,充分利用瀏覽器http并發數)。這樣的js文件中發生錯誤我們直接監控你會發現你啥信息都收集不到。

實驗一:

我們的站點是a.com,頁面中引用了兩個js文件,一個是a.com域名下的a.js,一個是b.com域名下的b.js,我們在a.js文件中添加window.onerror監控,在b.js文件中主動拋出錯誤

<!-- index.html  -->
<script type="text/javascript" src="http://a.com/a.js" ></script>
<script type="text/javascript" src="http://b.com/b.js" ></script>
// a.js
window.onerror = function (message, url, line, column, error) {
  console.log('log---onerror::::',message, url, line, column, error);
}
// b.js
throw new Error('this is the error happened in b.js');

我們可以看到下圖的結果,onerror函數拿到的信息是Script error, a 0 null,啥卵用都沒有,你完全不知道發生了什么錯誤,哪個文件發生的錯誤。

這是瀏覽器所做的安全限制措施,當加載自不同域(協議、域名、端口三者任一不同)的腳本中發生語法(?)錯誤時,為避免信息泄露,語法錯誤的細節將不會報告,而代之簡單的"Script error."。

實驗一的結果.png

但是我們確實是需要知道發生錯誤的具體信息啊,不然監控就沒有意義了。既然又是類同源限制的問題,那肯定是可以通過CORS來解決了。

實驗二

我們給b.js加上Access-Control-Allow-Origin:*response header,后面我們會發現還是沒啥變化。

實驗二的結果.png

實驗三

我們繼續給b.js加上crossorigin屬性,發現可以了,想要的信息都收集到了

<!-- index.html  -->
<script type="text/javascript" src="http://a.com/a.js" ></script>
<script type="text/javascript" src="http://b.com/b.js"  crossorigin></script>

實驗三的結果.png

結論:如果想通過onerror函數收集不同域的js錯誤,我們需要做兩件事:

  1. 相關的js文件上加上Access-Control-Allow-Origin:*response header
  2. 引用相關的js文件時加上crossorigin屬性

注意: 以上兩步缺一不可。實驗二告訴我們,如果只是加上Access-Control-Allow-Origin:*的話,錯誤還是無法捕獲。如果只加上crossorigin屬性,瀏覽器會報無法加載的錯誤,如下圖:

僅僅加上crossorigin屬性的script加載結果.png

可是。。。
如果你使用sentryraven.js的話,你會發現你什么都不用做,他依然可以幫你捕獲到一些錯誤的非常具體信息,確實是有點神奇啊,具體怎么做的?關鍵就是raven源碼中的install方法中調用的_instrumentTryCatch函數起了作用,他通過tryCatch的方式wrap了一些關鍵函數,使得這些函數里的報錯能夠捕獲,_instrumentTryCatch的具體實現原理我們后面再說

install: function() {
        var self = this;

        if (self.isSetup() && !self._isRavenInstalled) {
            TraceKit.report.subscribe(function () {
                self._handleOnErrorStackInfo.apply(self, arguments);
            });
            if (self._globalOptions.instrument && self._globalOptions.instrument.tryCatch) {
              self._instrumentTryCatch();// 通過tryCatch來wrap關鍵函數,從而獲得error的具體信息
            }

            if (self._globalOptions.autoBreadcrumbs)
                self._instrumentBreadcrumbs();

            // Install all of the plugins
            self._drainPlugins();

            self._isRavenInstalled = true;
        }

        Error.stackTraceLimit = self._globalOptions.stackTraceLimit;
        return this;
    },

其實如果你真的什么都不做,raven也只是能捕獲一些異步錯誤,同步錯誤還是無法捕獲,所以你即使使用了sentry等第三方的錯誤收集庫,你還是需要加上Access-Control-Allow-Origin:*crossorigin屬性

二、Performance

The Performance interface provides access to performance-related information for the current page. It's part of the High Resolution Time API, but is enhanced by the Performance Timeline API, the Navigation Timing API, the User Timing API, and the Resource Timing API.

Performace接口允許訪問當前頁面性能相關的信息。它是High Resolution Time API的一部分。但是它被Performance Timeline API, the Navigation Timing API,the User Timing API, 和the Resource Timing API擴展增強了。實際上Performance的主要功能都是由這幾個API提供的。

單單看上面的內容,大家一定還是會感到疑惑,這performace究竟是個什么東西?ok,我們直接打開百度的網頁,然后在控制臺里輸出Window.performance(window.performace返回的就是performance對象)


Performance對象里出現了4個屬性。

timing

timing對象提供了各種與瀏覽器處理相關的時間數據。具體如下表

名稱 作用(這里所有時間戳都代表UNIX毫秒時間戳)
connectEnd 瀏覽器與服務器之間的連接建立時的時間戳,連接建立指的是所有握手和認證過程全部結束
connectStart HTTP請求開始向服務器發送時的時間戳,如果是持久連接,則等同于fetchStart
domComplete 當前網頁DOM結構生成時,也就是Document.readyState屬性變為“complete”,并且相應的readystatechange事件觸發時的時間戳。
domContentLoadedEventEnd 當前網頁DOMContentLoaded事件發生時,也就是DOM結構解析完畢、所有腳本運行完成時的時間戳。
domContentLoadedEventStart 當前網頁DOMContentLoaded事件發生時,也就是DOM結構解析完畢、所有腳本開始運行時的時間戳。
domInteractive 當前網頁DOM結構結束解析、開始加載內嵌資源時,也就是Document.readyState屬性變為“interactive”、并且相應的readystatechange事件觸發時的時間戳。
domLoading 當前網頁DOM結構開始解析時,也就是Document.readyState屬性變為“loading”、并且相應的readystatechange事件觸發時的時間戳。
domainLookupEnd 域名查詢結束時的時間戳。如果使用持久連接,或者從本地緩存獲取信息的,等同于fetchStart
domainLookupStart 域名查詢開始時的時間戳。如果使用持久連接,或者從本地緩存獲取信息的,等同于fetchStart
fetchStart 瀏覽器準備通過HTTP請求去獲取頁面的時間戳。在檢查應用緩存之前發生。
loadEventEnd 當前網頁load事件的回調函數結束時的時間戳。如果該事件還沒有發生,返回0。
loadEventStart 當前網頁load事件的回調函數開始時的時間戳。如果該事件還沒有發生,返回0。
navigationStart 當前瀏覽器窗口的前一個網頁關閉,發生unload事件時的時間戳。如果沒有前一個網頁,就等于fetchStart
redirectEnd 最后一次重定向完成,也就是Http響應的最后一個字節返回時的時間戳。如果沒有重定向,或者上次重定向不是同源的。則為0
redirectStart 第一次重定向開始時的時間戳,如果沒有重定向,或者上次重定向不是同源的。則為0
requestStart 瀏覽器向服務器發出HTTP請求時(或開始讀取本地緩存時)的時間戳。
responseEnd 瀏覽器從服務器收到(或從本地緩存讀取)最后一個字節時(如果在此之前HTTP連接已經關閉,則返回關閉時)的時間戳
responseStart 瀏覽器從服務器收到(或從本地緩存讀取)第一個字節時的時間戳。
secureConnectionStart 瀏覽器與服務器開始安全鏈接的握手時的時間戳。如果當前網頁不要求安全連接,則返回0。
unloadEventEnd 如果前一個網頁與當前網頁屬于同一個域下,則表示前一個網頁的unload回調結束時的時間戳。如果沒有前一個網頁,或者之前的網頁跳轉不是屬于同一個域內,則返回值為0。
unloadEventStart 如果前一個網頁與當前網頁屬于同一個域下,則表示前一個網頁的unload事件發生時的時間戳。如果沒有前一個網頁,或者之前的網頁跳轉不是屬于同一個域內,則返回值為0。

了解上面timing提供的各種屬性之后,我們可以計算出網頁在加載時候某一部分消耗的具體時間,可以精確到千分之一毫秒。例如要計算出發送請求到接受完數據所消耗的時間。

const timing = window.performance.timing
const contactDuration = timing.responseEnd - timing.requestStart
navagation

PerformanceNavigation接口呈現了如何導航到當前文檔的信息。PerformanceNavigation有兩個屬性,一個是type,表示如何導航到當前頁面的,主要有4個值。

  • type=0:表示當前頁面是通過點擊鏈接,書簽和表單提交,或者腳本操作,或者在url中直接輸入地址訪問的。
  • type=1: 表示當前頁面是點擊刷新或者調用Location.reload()方法訪問的。
  • type=2: 表示當前頁面是通過歷史記錄或者前進后退按鈕訪問的。
  • type=255: 其他方式訪問的
    另外一個屬性是redirectCount,表示到達當前頁面之前經過幾次重定向。
其他屬性

performance.timeOrigin
表示performance性能測試開始的時間。是一個高精度時間戳(千分之一毫秒)

performance.onresourcetimingbufferfull
表示當瀏覽器資源時間性能緩沖區已滿時會觸發的回調函數。下面是mdn上關于這個屬性的一個demo。這個demo的主要內容是當緩沖區內容滿時,調用buffer_full函數。

function buffer_full(event) {
  console.log("WARNING: Resource Timing Buffer is FULL!");
  performance.setResourceTimingBufferSize(200);
}
function init() {
  // Set a callback if the resource buffer becomes filled
  performance.onresourcetimingbufferfull = buffer_full;
}
<body onload="init()">

performance.memory
一個非標準屬性,由chrome瀏覽器提供。這個屬性提供了一個可以獲取到基本內存使用情況的對象。

Performance.mark

The mark() method creates a timestamp in the browser's performance entry buffer with the given name.

這段話可以分解出三個關鍵詞。首先timestamp,這里的timestamp指的是高精度時間戳(千分之一毫秒),其次是performance entry bufferperformance entry buffer指的是存儲performance實例對象的區域,初始值為空。最后就是given name,表示生成的每一個timestamp都有相應的名稱。
所以這句話就可以理解成,在瀏覽器的performance entry buffer中,根據名稱生成高精度時間戳。也就是很多人說過的“打點”。

Performance.measure

The measure() method creates a named timestamp in the browser's performance entry buffer between two specified marks (known as the start mark and end mark, respectively). The named timestamp is referred to as a measure.

這段定義和上面mark的定義有些類似,其最核心的不同點在于這句話。between two specified marks。所以measur是指定兩個mark點之間的時間戳。如果說mark可以理解為"打點"的話,measure就可以理解為"連線"。

一個小例子
我們來看一個使用markmeasure的小demo,這個例子也是引用MDN,這里做一下簡單講解。

// 標記一個開始點
performance.mark("mySetTimeout-start");

// 等待1000ms
setTimeout(function() {
  // 標記一個結束點
  performance.mark("mySetTimeout-end");

  // 標記開始點和結束點之間的時間戳
  performance.measure(
    "mySetTimeout",
    "mySetTimeout-start",
    "mySetTimeout-end"
  );

  // 獲取所有名稱為mySetTimeout的measures
  var measures = performance.getEntriesByName("mySetTimeout");
  var measure = measures[0];
  console.log("setTimeout milliseconds:", measure.duration)

  // 清除標記
  performance.clearMarks();
  performance.clearMeasures();
}, 1000);

結果:


可以看到,高精度的時間戳是非常精準的。(我們知道由于執行隊列的原因,setTimeout不會在給定的1000ms之后就立即執行)
Performance API提供了很多方便測試我們程序性能的接口。比如markmeasure。很多優秀的框架也用到了這個API進行測試,比如我最近在看的Vue框架。它里面就頻繁用到了markmeasure來測試程序性能。所以想要開發高性能的web程序,了解Performace API還是非常重要的。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容

  • ??隨著 HTML5 的出現,面向未來 Web 應用的 JavaScript API 也得到了極大的發展。這些 A...
    霜天曉閱讀 1,043評論 0 0
  • pyspark.sql模塊 模塊上下文 Spark SQL和DataFrames的重要類: pyspark.sql...
    mpro閱讀 9,487評論 0 13
  • “你問我出生前在做什么我答我在天上挑媽媽看見你了覺得你特別好想做你的孩子又覺得自己可能沒那個運氣沒想到第二天一早我...
    飛飛的碎碎念閱讀 1,149評論 2 9
  • 我是從去年五月份開始在簡書上寫字,記得當初處于人生最灰暗時期,什么狗屁理想什么世相安穩歲月靜好,只記得當時我的世界...
    南方姑娘NF閱讀 207評論 2 4
  • 為什么要模塊化?模塊化使代碼結構更清晰維護起來更方便。 為什么要模塊化規范?有了模塊,我們可以方便地使用別人的代碼...
    LoveBugs_King閱讀 282評論 0 0