前端日志監(jiān)控

現(xiàn)行有一些已經(jīng)開源的前端異常監(jiān)控庫,如騰訊的badJs,全棧js監(jiān)控fundebug,國外的sentry等。

錯誤分類

  1. javascript異常

    • 語法錯誤
    • 運(yùn)行時(shí)錯誤
    • script文件內(nèi)錯誤(跨域和未跨域)
  2. JS文件、CSS文件、img圖片等(資源)的404錯誤(其實(shí)是有onerror事件的dom)

  3. promise的異常捕獲

  4. ajax請求錯誤

錯誤上報(bào)

1、采用Ajax通信的方式上報(bào)(不常用)
2、利用Image對象上報(bào)(常用)

錯誤捕獲類型

  1. 主動捕獲(try catch / promise catch)
  2. 全局捕獲(onerror / addEventListener)

錯誤捕獲原理

error事件的事件處理程序。針對各種目標(biāo)的不同類型的錯誤觸發(fā)了 Error 事件:

當(dāng)JavaScript運(yùn)行時(shí)錯誤(包括語法錯誤)發(fā)生時(shí),window會觸發(fā)一個ErrorEvent接口的error事件,并執(zhí)行window.onerror()。
當(dāng)一項(xiàng)資源(如<img>或<script>)加載失敗,加載資源的元素會觸發(fā)一個Event接口的error事件,并執(zhí)行該元素上的onerror()處理函數(shù)。這些error事件不會向上冒泡到window,不過(至少在Firefox中)能被單一的window.addEventListener捕獲。
加載一個全局的error事件處理函數(shù)可用于自動收集錯誤報(bào)告。

由于歷史原因,window.onerror和element.onerror接受不同的參數(shù)。
window.onerror = function(message, source, lineno, colno, error) { ... }
通過為頁面上的 script 標(biāo)簽添加 crossOrigin 屬性完成跨域上報(bào),別忘了服務(wù)器也設(shè)置 Access-Control-Allow-Origin 的響應(yīng)頭。(解決跨域的js腳本錯誤上報(bào))

window.addEventListener監(jiān)聽error事件
由于網(wǎng)絡(luò)請求異常不會事件冒泡,因此必須在捕獲階段將其捕捉到才行,但是這種方式雖然可以捕捉到網(wǎng)絡(luò)請求的異常,但是無法判斷 HTTP 的狀態(tài)是 404 還是其他比如 500 等等,所以還需要配合服務(wù)端日志才進(jìn)行排查分析才可以。
window.addEventListener('error', function(event) { ... }) //兼容ie9
element.onerror = function(event) { ... } //dom0,兼容低版ie

注意事項(xiàng):
當(dāng)加載自不同域的腳本中發(fā)生語法錯誤時(shí),為避免信息泄露(參見bug 363897),語法錯誤的細(xì)節(jié)將不會報(bào)告,而代之簡單的"Script error."。在某些瀏覽器中,通過在<script>使用crossorigin屬性并要求服務(wù)器發(fā)送適當(dāng)?shù)?CORS HTTP 響應(yīng)頭,該行為可被覆蓋。一個變通方案是單獨(dú)處理"Script error.",告知錯誤詳情僅能通過瀏覽器控制臺查看,無法通過JavaScript訪問。
別忘了服務(wù)器也設(shè)置 Access-Control-Allow-Origin 的響應(yīng)頭。(解決跨域的js腳本錯誤上報(bào))

try,catch 和window.onerror 比較

try,catch的方案有如下特點(diǎn):
無法捕捉到語法錯誤,只能捕捉運(yùn)行時(shí)錯誤;
可以拿到出錯的信息,堆棧,name,message,stack,有些瀏覽器不能獲取出錯的文件、行號、列號;
需要借助工具把所有的function塊以及文件塊加入try,catch,可以在這個階段打入更多的靜態(tài)信息。
不能捕獲異步錯誤(promise,setTimeout),但可以捕獲async await,Generator 可以直接使用co 函數(shù)庫來使用try...catch

window.onerror的方案有如下特點(diǎn):
可以捕捉語法錯誤,也可以捕捉運(yùn)行時(shí)錯誤;
可以拿到出錯的信息,堆棧,出錯的文件、行號、列號;
只要在當(dāng)前頁面執(zhí)行的js腳本出錯都會捕捉到,例如:瀏覽器插件的javascript、或者flash拋出的異常等。
跨域的資源需要特殊頭部支持。

promise.catch主動捕獲
unhandledrejection監(jiān)聽全局沒有catch的promise執(zhí)行.但是這個的兼容性不是很好
在一個JavaScript Promise 被 reject(拒絕) 但是沒有 reject 處理函數(shù)來處理時(shí)觸發(fā)。
window.onunhandledrejection = function(e) {
console.log(e.reason);
}
目前chrome49支持,在 Firefox 里,有實(shí)現(xiàn)這個接口但是默認(rèn)是禁用的。要打開它的話,去到about:config 將 dom.promise_rejection_events.enabled 啟用為真。

附:try catch Error對象屬性:
1.Firefox中的Error對象擁有如下屬性:

message —— 錯誤提示信息
fileName —— 表示出錯代碼所在文件
lineNumber —— 出錯代碼所在行數(shù)
stack —— 出錯堆棧信息
name —— 異常對象名/類型

2.在IE下,Error對象只有如下屬性:

name —— 異常對象名/類型,和Firefox中顯示的名稱可能不同
message —— 錯誤提示信息
description —— 和message屬性相同
number —— ErrorCode,錯誤代碼,對于普通開發(fā)人員來說基本沒意義

3.在Safari中的Error對象擁有如下屬性:

message —— 錯誤提示信息
line —— 出錯代碼所在行數(shù)
sourceId —— 一個數(shù)字,不明白什么意思
sourceURL —— 表示出錯代碼所在文件
name —— 異常對象名/類型

4.Opera下的Error對象擁有如下屬性:

message —— 錯誤提示信息
opera#sourceloc —— 出錯代碼所在行數(shù)
stacktrace —— 出錯堆棧信息

參考:http://www.itdecent.cn/p/85d9a2778d80
https://developer.mozilla.org/zh-CN/docs/Web/API/GlobalEventHandlers/onerror

代碼示例

(function(global, factory) {

    'use strict';

    if (typeof module === 'object' && typeof module.exports === 'object') {
        module.exports = global.document ? factory(global, true) : function(w) {
            if (!w.document) {
                throw new Error('track.js requires a window with a document');
            }
            return factory(w);
        };
    } else {
        factory(global);
    }

})(typeof window !== 'undefined' ? window : this, function(window, noGlobal) {
    window.onerror = function(message, url, line, column, error) {

        var stackInfo;
        //不一定所有瀏覽器都支持col參數(shù)
        column = column || (window.event && window.event.errorCharacter);
        if (error && error.stack) {

            stackInfo = error.stack.toString();
        } else {
            var stack = [];
            var i = 3;
            var f = arguments.callee.caller;

            while (f && i > 0) {
                var funcName = f.name || f.toString().match(/^function\s*([^\s(]+)/)[1]; //IE不支持function.name
                stack.push('function ' + funcName + ' () ');
                if (f === f.caller) {
                    break;//如果有環(huán)
                }
                f = f.caller;
                i--;
            }
            stackInfo = stack.join('\n   at   ');
        }
        var data = {
            msg: message,
            url: url,
            page: location.href,
            line: line,
            col: column,
            stack: stackInfo

        };
        track({
            'type': 'error',
            'info': data
        });

    };

    window.addEventListener('unhandledrejection', function(error) {
        track({
            'type': 'error',
            'info': error.reason
        });
    });

    var commonParams = function() {
        var o = {},
            screen;
        if (screen = window && window.screen) {
            var sc = screen.width,
                sh = screen.height;
            o.screen = sc + ' x ' + sh;
            o.colorDepth = screen.colorDepth && (screen.colorDepth + '-bit');
        }
        if (navigator) {
            o.language = (navigator.language || navigator.browserLanguage).toLowerCase();
            o.javaEnabled = navigator.javaEnabled();
        }
        return o;
    };

    var track = function(extendParams) {
        var o = {};
        for(var temp in extendParams){
            o[temp]=extendParams[temp];
        }
        o.commonParams = track.commonParams || (track.commonParams=commonParams(track));

        var arr = [];
        for (var key in o) {
            if (o[key] !== undefined) {
                if (o[key] instanceof Object) {
                    for (var okey in o[key]) {
                        arr.push(encodeURIComponent(key + '[' + okey + ']') + '=' + encodeURIComponent(o[key][okey]));
                    }

                } else {
                    arr.push(key + '=' + encodeURIComponent(o[key]));

                }
            }

        }
        console.log(arr,11)
        var img = new Image();
        img.src = 'http://172.31.11.245:3000/_h5track?' + arr.join('&') + '&timestamp=' + new Date().getTime();
        // img.onload = img.onerror = function() {
        //  img=null;
        // };
    };
    if (!noGlobal) {
        window.track = window.trackLog = track;
    }
});


// 監(jiān)控資源加載錯誤(img,script,css,以及jsonp)
window.addEventListener('error',function(e){
    defaults.t =new Date().getTime();
    defaults.msg =e.target.localName+' is load error';
    defaults.data = JSON.stringify({
        target: e.target.localName,
        type: e.type,
        resourceUrl:e.target.currentSrc,
        pageUrl:location.href,
        category:'resource'
    });
    if(e.target!=window){//拋去js語法錯誤
        // 合并上報(bào)的數(shù)據(jù),包括默認(rèn)上報(bào)的數(shù)據(jù)和自定義上報(bào)的數(shù)據(jù)
        var reportData=Object.assign({},params.data || {},defaults);

        // 把錯誤信息發(fā)送給后臺
        alert(JSON.stringify(reportData))
    }


},true);

window.addEventListener('unhandledrejection', function(err) {
    console.log(err);
});

angularjs報(bào)錯實(shí)現(xiàn):

var app = angular.module('myApp', []);

app.config(function($provide) {
    $provide.decorator('$exceptionHandler', [
        '$httpParamSerializerJQLike', '$delegate', function($httpParamSerializerJQLike, $delegate) {
            return function(exception, cause) {
                $delegate(exception, cause);

                var data = {
                    msg: exception.toString(),
                    cause: cause,
                    url:exception.fileName||exception.sourceURL,
                    page: location.href,
                    line: exception.lineNumber||exception.line ||exception['opera#sourceloc'],
                    stack: exception.stack||exception.stacktrace
                };
                track(data);
            };
        }]);
});
app.controller('myCtrl', function($scope) {
    $scope.firstName = 'John';
    $scope.lastName = 'Doe';
    $scope.c=undefined.a;
    // t;
});
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容