日志监控

最近项目上有对于网站监控的需求,主要功能如下 1. 白屏时间 2. ajax请求响应时间 3. js报错信息 这些都需要通过request请求将对应的数据提交到后端,虽然从功能上来说,后端运维的log日志完全可以看到大多数内容,但是既然要求做了那就写呗

白屏时间

这个就是使用浏览器自带的 performance 对象来实现了 performance.timing则是包含浏览器在各个状态下的时间戳以及其他属性值 部分js时间计算的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Logger.prototype.getTiming = function () {
  var _return = {
      // DNS查询耗时
      dnsT: this.timing.domainLookupEnd - this.timing.domainLookupStart,
      // 白屏时间
      loadT: this.timing.domLoading - this.timing.navigationStart,
      // request请求耗时
      requestT: this.timing.responseEnd - this.timing.responseStart,
      // TCP链接耗时
      tcpT: this.timing.connectEnd - this.timing.connectStart,
      // 解析dom树耗时
      renderDomT: this.timing.domComplete - this.timing.domInteractive,
      // domready时间(用户可操作时间节点) 
      readyDomT: this.timing.domContentLoadedEventEnd - this.timing.navigationStart,
      // onload时间(总下载时间)
      onLoadT: this.timing.loadEventEnd - this.timing.navigationStart
  };
  return _return;
};

ajax请求响应时间

由于公司网址都是用的$.ajax的请求方法,新老官网以及落地页,关于ajax请求的代码就有好几百个,不可能一个个在ajax里写starttime然后成功或者失败定义endtime拿到差值再减,看起来很难维护 想了很久,最后决定重写$.ajax,也就是包装这个方法,专业术语叫 AOP 装饰者模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
(function ($, w) {
  // window上初始一个方法
  w._ajax = $.ajax;
  $.ajax = function (arg) {
    // 请求之前的时间
    var start_t = new Date().getTime()
    // 拿到$.ajax的执行的成功回调,记录下来
    var success = arg.success
    // 重写succes
    arg.success = function () {
      // 拿到$.ajax传入的参数
      var data = arguments
      // 成功执行后定义的方法
      var time = new Date().getTime() - start_t
      // 此时执行默认的ajax,因为默认的ajax不会捕获success之后的回调函数
      _ajax({
        url: 'url',
        data: {
          time: time
        }
        success: success.call(this, data)
      });
    }
    return _ajax.call($, arg)
  };
})(jQuery, window);

js报错信息

js报错主要就是用window的onerror事件去监听js的报错信息 这里需要注意一下几点

1.不能使用 window.onerror = function () {...} ,而是使用 window.addEventListener ('error', funciton (e) {})

原因就是 window.onerror 会覆盖或者被覆盖,因为他是表达式,所以在其他地方执行这个方法的时候,会被覆盖,addEventListener则是添加一个error的监听事件,不会影响之前error的监听代码

2.执行顺序

error的监听代码尽量写在js最前面,因为涉及到调用 $.ajax , 因此放在jquery引入之后,其余的代码则放在后面, 先初始化监听, 后执行后续代码

3.crossorigin

在script引入日志监控代码标签没有用到crossorigin的时候, 由于跨域调用的windows脚本, onerror的事件不会打印详细的报错信息, 为了打印详细信息, 需配置crossorigin属性, 这需要后端的 Access-Control-Allow-Origin 支持

4.不是所有js报错代码都可以监听

  • console.error 不会被执行
  • try {} catch () {} 不会被监听
  • 静态资源加载错误不会被监听

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Logger.prototype.initErrorEvent = function () {
  /**
    * @param {String}  errorMessage   错误信息
    * @param {String}  scriptURL      出错文件的URL
    * @param {Number}  lineNumber     出错代码的行号
    * @param {Number}  columnNumber   出错代码的列号
    * @param {Object}  errorObj       错误信息Object
    */
  var _this = this;
  window.addEventListener('error', function (e) {
      setTimeout(function () {
          _this.staticError({
              errorMessage: e.message,
              scriptURL: e.filename,
              lineNumber: e.lineno,
              columnNumber: e.colno,
              errorObj: e.error
          });
      }, 0);
  });
};

Logger.prototype.staticError = function (data) {
    $.ajax({
        url: 'url...',
        type: 'POST',
        dataType: 'json',
        data: {
            url: window.location.href,
            current_time: new Date().getTime(),
            js_url: data.scriptURL,
            error_info: data.errorMessage,
            error_line: data.lineNumber,
            error_column: data.columnNumber
        }
    });
};

完整的logger代码,不包含包装的ajax方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
var Logger = /** @class */ (function () {
    function Logger() {
        this.timing = window.performance.timing;
        this.initErrorEvent();
    }
    Logger.prototype.getTiming = function () {
        var _return = {
            // DNS查询耗时
            dnsT: this.timing.domainLookupEnd - this.timing.domainLookupStart,
            // 白屏时间
            loadT: this.timing.domLoading - this.timing.navigationStart,
            // request请求耗时
            requestT: this.timing.responseEnd - this.timing.responseStart,
            // TCP链接耗时
            tcpT: this.timing.connectEnd - this.timing.connectStart,
            // 解析dom树耗时
            renderDomT: this.timing.domComplete - this.timing.domInteractive,
            // domready时间(用户可操作时间节点) 
            readyDomT: this.timing.domContentLoadedEventEnd - this.timing.navigationStart,
            // onload时间(总下载时间)
            onLoadT: this.timing.loadEventEnd - this.timing.navigationStart
        };
        return _return;
    };
    /**
     * 初始化error监听事件
     */
    Logger.prototype.initErrorEvent = function () {
        /**
         * @param {String}  errorMessage   错误信息
         * @param {String}  scriptURL      出错文件的URL
         * @param {Number}  lineNumber     出错代码的行号
         * @param {Number}  columnNumber   出错代码的列号
         * @param {Object}  errorObj       错误信息Object
         */
        var _this = this;
        window.addEventListener('error', function (e) {
            setTimeout(function () {
                _this.staticError({
                    errorMessage: e.message,
                    scriptURL: e.filename,
                    lineNumber: e.lineno,
                    columnNumber: e.colno,
                    errorObj: e.error
                });
            }, 0);
        });
    };
    /**
     * 事件错误的回调事件
     */
    Logger.prototype.staticError = function (data) {
        $.ajax({
            url: '',
            type: 'POST',
            dataType: 'json',
            data: {
                url: window.location.href,
                current_time: new Date().getTime(),
                js_url: data.scriptURL,
                error_info: data.errorMessage,
                error_line: data.lineNumber,
                error_column: data.columnNumber
            }
        });
    };
    /**
     * 页面加载时长的数据信息
     */
    Logger.prototype.fetchPageLoadInfo = function () {
        var timingInfo = this.getTiming();
        $.ajax({
            url: '',
            type: 'POST',
            dataType: 'json',
            data: {
                url: window.location.href,
                current_time: new Date().getTime(),
                response_time: timingInfo.loadT
            }
        });
    };
    return Logger;
}());
window.logger = new Logger();
window.addEventListener('load', function () {
    setTimeout(function () {
        logger.fetchPageLoadInfo();
    }, 1000);
}, false);
上一篇 : 关于cookie下一篇 : 手写一个简单的发布订阅者模式