前端日志监控

现行有一些已经开源的前端异常监控库,如腾讯的badJs,全栈js监控fundebug,国外的sentry等。

错误分类

  1. javascript异常

    • 语法错误
    • 运行时错误
    • script文件内错误(跨域和未跨域)
  2. JS文件、CSS文件、img图片等(资源)的404错误(其实是有onerror事件的dom)

  3. promise的异常捕获

  4. ajax请求错误

错误上报

1、采用Ajax通信的方式上报(不常用)
2、利用Image对象上报(常用)

错误捕获类型

  1. 主动捕获(try catch / promise catch)
  2. 全局捕获(onerror / addEventListener)

错误捕获原理

error事件的事件处理程序。针对各种目标的不同类型的错误触发了 Error 事件:

当JavaScript运行时错误(包括语法错误)发生时,window会触发一个ErrorEvent接口的error事件,并执行window.onerror()。
当一项资源(如<img>或<script>)加载失败,加载资源的元素会触发一个Event接口的error事件,并执行该元素上的onerror()处理函数。这些error事件不会向上冒泡到window,不过(至少在Firefox中)能被单一的window.addEventListener捕获。
加载一个全局的error事件处理函数可用于自动收集错误报告。

由于历史原因,window.onerror和element.onerror接受不同的参数。
window.onerror = function(message, source, lineno, colno, error) { ... }
通过为页面上的 script 标签添加 crossOrigin 属性完成跨域上报,别忘了服务器也设置 Access-Control-Allow-Origin 的响应头。(解决跨域的js脚本错误上报)

window.addEventListener监听error事件
由于网络请求异常不会事件冒泡,因此必须在捕获阶段将其捕捉到才行,但是这种方式虽然可以捕捉到网络请求的异常,但是无法判断 HTTP 的状态是 404 还是其他比如 500 等等,所以还需要配合服务端日志才进行排查分析才可以。
window.addEventListener('error', function(event) { ... }) //兼容ie9
element.onerror = function(event) { ... } //dom0,兼容低版ie

注意事项:
当加载自不同域的脚本中发生语法错误时,为避免信息泄露(参见bug 363897),语法错误的细节将不会报告,而代之简单的"Script error."。在某些浏览器中,通过在<script>使用crossorigin属性并要求服务器发送适当的 CORS HTTP 响应头,该行为可被覆盖。一个变通方案是单独处理"Script error.",告知错误详情仅能通过浏览器控制台查看,无法通过JavaScript访问。
别忘了服务器也设置 Access-Control-Allow-Origin 的响应头。(解决跨域的js脚本错误上报)

try,catch 和window.onerror 比较

try,catch的方案有如下特点:
无法捕捉到语法错误,只能捕捉运行时错误;
可以拿到出错的信息,堆栈,name,message,stack,有些浏览器不能获取出错的文件、行号、列号;
需要借助工具把所有的function块以及文件块加入try,catch,可以在这个阶段打入更多的静态信息。
不能捕获异步错误(promise,setTimeout),但可以捕获async await,Generator 可以直接使用co 函数库来使用try...catch

window.onerror的方案有如下特点:
可以捕捉语法错误,也可以捕捉运行时错误;
可以拿到出错的信息,堆栈,出错的文件、行号、列号;
只要在当前页面执行的js脚本出错都会捕捉到,例如:浏览器插件的javascript、或者flash抛出的异常等。
跨域的资源需要特殊头部支持。

promise.catch主动捕获
unhandledrejection监听全局没有catch的promise执行.但是这个的兼容性不是很好
在一个JavaScript Promise 被 reject(拒绝) 但是没有 reject 处理函数来处理时触发。
window.onunhandledrejection = function(e) {
console.log(e.reason);
}
目前chrome49支持,在 Firefox 里,有实现这个接口但是默认是禁用的。要打开它的话,去到about:config 将 dom.promise_rejection_events.enabled 启用为真。

附:try catch Error对象属性:
1.Firefox中的Error对象拥有如下属性:

message —— 错误提示信息
fileName —— 表示出错代码所在文件
lineNumber —— 出错代码所在行数
stack —— 出错堆栈信息
name —— 异常对象名/类型

2.在IE下,Error对象只有如下属性:

name —— 异常对象名/类型,和Firefox中显示的名称可能不同
message —— 错误提示信息
description —— 和message属性相同
number —— ErrorCode,错误代码,对于普通开发人员来说基本没意义

3.在Safari中的Error对象拥有如下属性:

message —— 错误提示信息
line —— 出错代码所在行数
sourceId —— 一个数字,不明白什么意思
sourceURL —— 表示出错代码所在文件
name —— 异常对象名/类型

4.Opera下的Error对象拥有如下属性:

message —— 错误提示信息
opera#sourceloc —— 出错代码所在行数
stacktrace —— 出错堆栈信息

参考:https://www.jianshu.com/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参数
        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;//如果有环
                }
                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;
    }
});


// 监控资源加载错误(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语法错误
        // 合并上报的数据,包括默认上报的数据和自定义上报的数据
        var reportData=Object.assign({},params.data || {},defaults);

        // 把错误信息发送给后台
        alert(JSON.stringify(reportData))
    }


},true);

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

angularjs报错实现:

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;
});
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容