sentry-web前端异常堆栈计算功能逻辑解析

什么是sentry

Sentry 是一个实时事件日志记录和聚集的平台。其专一于错误监控以及提取一切过后处理所需信息而不依赖于麻烦的用户反馈。它分为客户端和服务端,客户端(目前客户端有Javascript,Python, PHP,C#, Ruby等多种语言)就嵌入在你的应用程序中间,程序出现异常就向服务端发送消息,服务端将消息记录到数据库中并提供一个web页方便查看。Sentry由python编写,源码开放,性能卓越,易于扩展,目前著名的用户有Disqus, Path, mozilla, Pinterest等。javascript

sentry的集成与使用,推荐到sentry官网查询与学习,本篇文章只对其前端异常堆栈计算的核心逻辑进行梳理前端

sentry前端错误收集原理(针对旧版sentry js的SDK raven-js)

sentry实现前端错误监控,经过对window.onerror、window.onunhandledrejection、计时器,延时器,对requestAnimationFrame,对浏览器中可能存在的基于发布订阅模式进行回调处理的函数进行包装重写,将前端未进行异常处理的错误,经过 'vendor/TraceKit/traceKit.js' 进行兼容处理,统一不一样浏览器环境下错误对象的差别(chrome,firefox,ie),输出统一的 stacktrace后,从新整理数据结构。再将最后处理事后的信息提交给sentry服务端处理java

sentry前端上报核心文件

  1. 'src/raven.js' // 上报数据完整逻辑
  2. 'vendor/TraceKit/traceKit.js' // 堆栈错误计算(统一浏览器差别,是一个npm包,能够直接经过npm安装)
  3. 'src/utils.js' // 工具方法

sentry核心处理逻辑

文件入口

使用raven-js导出的类Raven,调用其install方法初始化sentrynode

  1. install方法首先进行了防止重复初始化处理,首次初始化时,调用TraceKit.report.subscribe对window.onerror进行了劫持,增长了一个钩子,重写了window.onerror方法,若是原window.onerror方法存在,在原onerror回调前调用了 Raven._handleOnErrorStackInfo方法,而后调用原onerror回调.
    TraceKit.report.subscribe(function() {
        self._handleOnErrorStackInfo.apply(self, arguments);
    });
    复制代码
  2. 对未捕获的promise rejection ,添加异常捕获
    if (self._globalOptions.captureUnhandledRejections // 为true) {
        self._attachPromiseRejectionHandler();
    }
    复制代码
  3. 对计时器函数、event target进行包装,对这些函数内部出现错误进行异常捕获,主要包装逻辑相似于中间件原理,重写原方法,在执行完错误捕获逻辑后,再调用源代码,捕获逻辑都是将原回调函数使用try catch包裹,对错误进行捕获,捕获到以后,经过captureException处理收集错误,发送给sentry后端服务器. 以后把原错误抛出(注意,在这里,Raven类有一个静态属性_ignoreOnerror,每次对错误进行捕获以后,会改变该状态,经过setTimeout在本次事件循环以后,重置_ignoreOnerror,防止新抛出的错误重复触发错误收集)
    if (self._globalOptions.instrument && self._globalOptions.instrument.tryCatch // true) {
        self._instrumentTryCatch();
    }
    复制代码
    关键方法fill方法,取自utils.fill 参数track是Raven类静态属性Raven._wrappedBuiltIns:[],做用是在卸载sentry SDK时,用来还原代码
    function fill(obj, name, replacement, track) {
      if (obj == null) return;
      var orig = obj[name];
      obj[name] = replacement(orig);
      obj[name].__raven__ = true;
      obj[name].__orig__ = orig;
      if (track) {
        track.push([obj, name, orig]);
      }
    }
    复制代码
    1. _instrumentTryCatch中对setTimeout的包装
    fill(_window, 'setTimeout', wrapTimeFn, wrappedBuiltIns);
    复制代码
    1. _instrumentTryCatch中对setInterval的包装
    fill(_window, 'setInterval', wrapTimeFn, wrappedBuiltIns);
    复制代码
    1. 若是浏览器支持requestAnimationFrame,对requestAnimationFrame进行包装
    if (_window.requestAnimationFrame) {
      fill(
        _window,
        'requestAnimationFrame',
        function(orig) {
          return function(cb) {
            return orig(
              self.wrap(
                {
                  mechanism: {
                    type: 'instrument',
                    data: {
                      function: 'requestAnimationFrame',
                      handler: (orig && orig.name) || '<anonymous>'
                    }
                  }
                },
                cb
              )
            );
          };
        },
        wrappedBuiltIns
      );
    }
    复制代码
    1. _instrumentTryCatch会检测全局对象是否有如下属性,并检测如下属性是否有发布订阅接口,若是存在发布订阅接口,将重写对应发布订阅接口(经过检测是否有'addEventlistener属性'和'removeEventListener'),在对应回调调用时,对调用过程当中的错误进行监控上报.
    var eventTargets = [
      'EventTarget',
      'Window',
      'Node',
      'ApplicationCache',
      'AudioTrackList',
      'ChannelMergerNode',
      'CryptoOperation',
      'EventSource',
      'FileReader',
      'HTMLUnknownElement',
      'IDBDatabase',
      'IDBRequest',
      'IDBTransaction',
      'KeyOperation',
      'MediaController',
      'MessagePort',
      'ModalWindow',
      'Notification',
      'SVGElementInstance',
      'Screen',
      'TextTrack',
      'TextTrackCue',
      'TextTrackList',
      'WebSocket',
      'WebSocketWorker',
      'Worker',
      'XMLHttpRequest',
      'XMLHttpRequestEventTarget',
      'XMLHttpRequestUpload'
    ];
    for (var i = 0; i < eventTargets.length; i++) {
      wrapEventTarget(eventTargets[i]);
    }
    复制代码
    wrapEventTarget详细代码
    function wrapEventTarget(global) {
      var proto = _window[global] && _window[global].prototype;
      if (proto && proto.hasOwnProperty && proto.hasOwnProperty('addEventListener')) {
        fill(
          proto,
          'addEventListener',
          function(orig) {
            return function(evtName, fn, capture, secure) {
              // preserve arity
              try {
                if (fn && fn.handleEvent) {
                  fn.handleEvent = self.wrap(
                    {
                      mechanism: {
                        type: 'instrument',
                        data: {
                          target: global,
                          function: 'handleEvent',
                          handler: (fn && fn.name) || '<anonymous>'
                        }
                      }
                    },
                    fn.handleEvent
                  );
                }
              } catch (err) {
                // can sometimes get 'Permission denied to access property "handle Event'
              }
    
              // More breadcrumb DOM capture ... done here and not in `_instrumentBreadcrumbs`
              // so that we don't have more than one wrapper function
              var before, clickHandler, keypressHandler;
    
              if (
                autoBreadcrumbs &&
                autoBreadcrumbs.dom &&
                (global === 'EventTarget' || global === 'Node')
              ) {
                // NOTE: generating multiple handlers per addEventListener invocation, should
                // revisit and verify we can just use one (almost certainly)
                clickHandler = self._breadcrumbEventHandler('click');
                keypressHandler = self._keypressEventHandler();
                before = function(evt) {
                  // need to intercept every DOM event in `before` argument, in case that
                  // same wrapped method is re-used for different events (e.g. mousemove THEN click)
                  // see #724
                  if (!evt) return;
    
                  var eventType;
                  try {
                    eventType = evt.type;
                  } catch (e) {
                    // just accessing event properties can throw an exception in some rare circumstances
                    // see: https://github.com/getsentry/raven-js/issues/838
                    return;
                  }
                  if (eventType === 'click') return clickHandler(evt);
                  else if (eventType === 'keypress') return keypressHandler(evt);
                };
              }
              return orig.call(
                this,
                evtName,
                self.wrap(
                  {
                    mechanism: {
                      type: 'instrument',
                      data: {
                        target: global,
                        function: 'addEventListener',
                        handler: (fn && fn.name) || '<anonymous>'
                      }
                    }
                  },
                  fn,
                  before
                ),
                capture,
                secure
              );
            };
          },
          wrappedBuiltIns
        );
        fill(
          proto,
          'removeEventListener',
          function(orig) {
            return function(evt, fn, capture, secure) {
              try {
                fn = fn && (fn.__raven_wrapper__ ? fn.__raven_wrapper__ : fn);
              } catch (e) {
                // ignore, accessing __raven_wrapper__ will throw in some Selenium environments
              }
              return orig.call(this, evt, fn, capture, secure);
            };
          },
          wrappedBuiltIns
        );
      }
    }
    复制代码

对捕获到的错误对象进行处理

捕获到的错误经过Raven.captureException方法进行处理,在该方法中会对错误类型进行判断,错误对象的判断经过utils内部的方法进行判断,原理是调用Object.property.toString.call方法,将各错误对象转化为字符串,来肯定错误类型python

  • 对于 [object ErrorEvent] [object Error] [object Exception] 错误对象,直接使用 TraceKit.computeStackTrace(统一跨浏览器的堆栈跟踪信息)方法 进行异常的堆栈跟踪,对于 [object Object] 非错误对象,进行兼容后再使用 TraceKit.computeStackTrace方法 进行异常的堆栈跟踪.react

    else if (isPlainObject(ex)) {
      options = this._getCaptureExceptionOptionsFromPlainObject(options, ex);
      ex = new Error(options.message);
    }
    复制代码

    对[object Object]的兼容webpack

    _getCaptureExceptionOptionsFromPlainObject: function(currentOptions, ex) {
        var exKeys = Object.keys(ex).sort();
        var options = objectMerge(currentOptions, {
          message:
            'Non-Error exception captured with keys: ' + serializeKeysForMessage(exKeys),
          fingerprint: [md5(exKeys)],
          extra: currentOptions.extra || {}
        });
        options.extra.__serialized__ = serializeException(ex);
    
        return options;
      }
    复制代码

    对异常进行堆栈跟踪计算git

    try {
      var stack = TraceKit.computeStackTrace(ex);
      this._handleStackInfo(stack, options);
    } catch (ex1) {
      if (ex !== ex1) {
        throw ex1;
      }
    }
    复制代码

    计算结果传递给Raven._handleStackInfo方法再次进行数据处理github

    _handleStackInfo: function(stackInfo, options) {
        var frames = this._prepareFrames(stackInfo, options);
    
        this._triggerEvent('handle', {
          stackInfo: stackInfo,
          options: options
        });
    
        this._processException(
          stackInfo.name,
          stackInfo.message,
          stackInfo.url,
          stackInfo.lineno,
          frames,
          options
        );
    },
    复制代码
    1. Raven._prepareFrames方法,处理堆栈错误,确认该堆栈错误是不是应用内部错误,并初步处理stacktrace.framesweb

      _prepareFrames: function(stackInfo, options) {
          var self = this;
          var frames = [];
          if (stackInfo.stack && stackInfo.stack.length) {
            each(stackInfo.stack, function(i, stack) {
              var frame = self._normalizeFrame(stack, stackInfo.url);
              if (frame) {
                frames.push(frame);
              }
            });
      
            // e.g. frames captured via captureMessage throw
            if (options && options.trimHeadFrames) {
              for (var j = 0; j < options.trimHeadFrames && j < frames.length; j++) {
                frames[j].in_app = false;
              }
            }
          }
          frames = frames.slice(0, this._globalOptions.stackTraceLimit);
          return frames;
        },
      复制代码
    2. Raven._processException方法将堆栈信息结构从新整理,处理的最终结果就是上报的最终信息,经过Raven._send方法发送给sentry后端服务

      _processException: function(type, message, fileurl, lineno, frames, options) {
          var prefixedMessage = (type ? type + ': ' : '') + (message || '');
          if (
            !!this._globalOptions.ignoreErrors.test &&
            (this._globalOptions.ignoreErrors.test(message) ||
              this._globalOptions.ignoreErrors.test(prefixedMessage))
          ) {
            return;
          }
      
          var stacktrace;
      
          if (frames && frames.length) {
            fileurl = frames[0].filename || fileurl;
            // Sentry expects frames oldest to newest
            // and JS sends them as newest to oldest
            frames.reverse();
            stacktrace = {frames: frames};
          } else if (fileurl) {
            stacktrace = {
              frames: [
                {
                  filename: fileurl,
                  lineno: lineno,
                  in_app: true
                }
              ]
            };
          }
      
          if (
            !!this._globalOptions.ignoreUrls.test &&
            this._globalOptions.ignoreUrls.test(fileurl)
          ) {
            return;
          }
      
          if (
            !!this._globalOptions.whitelistUrls.test &&
            !this._globalOptions.whitelistUrls.test(fileurl)
          ) {
            return;
          }
      
          var data = objectMerge(
            {
              // sentry.interfaces.Exception
              exception: {
                values: [
                  {
                    type: type,
                    value: message,
                    stacktrace: stacktrace
                  }
                ]
              },
              transaction: fileurl
            },
            options
          );
      
          var ex = data.exception.values[0];
          if (ex.type == null && ex.value === '') {
            ex.value = 'Unrecoverable error caught';
          }
      
          // Move mechanism from options to exception interface
          // We do this, as requiring user to pass `{exception:{mechanism:{ ... }}}` would be
          // too much
          if (!data.exception.mechanism && data.mechanism) {
            data.exception.mechanism = data.mechanism;
            delete data.mechanism;
          }
      
          data.exception.mechanism = objectMerge(
            {
              type: 'generic',
              handled: true
            },
            data.exception.mechanism || {}
          );
      
          // Fire away!
          this._send(data); // 发送数据
        },
      复制代码
  • 对于 [object DOMError] 和 [object DOMException]错误对象,经过Raven.captureMessage方法进行处理,判断该错误对象是否为须要忽略的错误(是否须要忽略的错误列表在sentry配置时设置),若是不是,再调用 TraceKit.computeStackTrace方法进行堆栈计算,计算结果经过Raven._prepareFrames进行处理而后发送给sentry后端服务

    else if (isDOMError(ex) || isDOMException(ex)) {
      var name = ex.name || (isDOMError(ex) ? 'DOMError' : 'DOMException');
      var message = ex.message ? name + ': ' + ex.message : name;
    
      return this.captureMessage(
        message,
        objectMerge(options, {
          stacktrace: true,
          trimHeadFrames: options.trimHeadFrames + 1
        })
      ); 
    }
    复制代码
    captureMessage: function(msg, options) {
        // config() automagically converts ignoreErrors from a list to a RegExp so we need to test for an
        // early call; we'll error on the side of logging anything called before configuration since it's
        // probably something you should see:
        if (
          !!this._globalOptions.ignoreErrors.test &&
          this._globalOptions.ignoreErrors.test(msg)
        ) {
          return;
        }
    
        options = options || {};
        msg = msg + ''; // Make sure it's actually a string
    
        var data = objectMerge(
          {
            message: msg
          },
          options
        );
    
        var ex;
        // Generate a "synthetic" stack trace from this point.
        // NOTE: If you are a Sentry user, and you are seeing this stack frame, it is NOT indicative
        // of a bug with Raven.js. Sentry generates synthetic traces either by configuration,
        // or if it catches a thrown object without a "stack" property.
        try {
          throw new Error(msg);
        } catch (ex1) {
          ex = ex1;
        }
    
        // null exception name so `Error` isn't prefixed to msg
        ex.name = null;
        var stack = TraceKit.computeStackTrace(ex);
    
        // stack[0] is `throw new Error(msg)` call itself, we are interested in the frame that was just before that, stack[1]
        var initialCall = isArray(stack.stack) && stack.stack[1];
    
        // if stack[1] is `Raven.captureException`, it means that someone passed a string to it and we redirected that call
        // to be handled by `captureMessage`, thus `initialCall` is the 3rd one, not 2nd
        // initialCall => captureException(string) => captureMessage(string)
        if (initialCall && initialCall.func === 'Raven.captureException') {
          initialCall = stack.stack[2];
        }
    
        var fileurl = (initialCall && initialCall.url) || '';
    
        if (
          !!this._globalOptions.ignoreUrls.test &&
          this._globalOptions.ignoreUrls.test(fileurl)
        ) {
          return;
        }
    
        if (
          !!this._globalOptions.whitelistUrls.test &&
          !this._globalOptions.whitelistUrls.test(fileurl)
        ) {
          return;
        }
    
        // Always attempt to get stacktrace if message is empty.
        // It's the only way to provide any helpful information to the user.
        if (this._globalOptions.stacktrace || options.stacktrace || data.message === '') {
          // fingerprint on msg, not stack trace (legacy behavior, could be revisited)
          data.fingerprint = data.fingerprint == null ? msg : data.fingerprint;
    
          options = objectMerge(
            {
              trimHeadFrames: 0
            },
            options
          );
          // Since we know this is a synthetic trace, the top frame (this function call)
          // MUST be from Raven.js, so mark it for trimming
          // We add to the trim counter so that callers can choose to trim extra frames, such
          // as utility functions.
          options.trimHeadFrames += 1;
    
          var frames = this._prepareFrames(stack, options);
          data.stacktrace = {
            // Sentry expects frames oldest to newest
            frames: frames.reverse()
          };
        }
    
        // Make sure that fingerprint is always wrapped in an array
        if (data.fingerprint) {
          data.fingerprint = isArray(data.fingerprint)
            ? data.fingerprint
            : [data.fingerprint];
        }
    
        // Fire away!
        this._send(data); // 最终发送给后端的数据
    
        return this;
      },
    复制代码

sentry 后端处理逻辑(python部署)

python核心处理逻辑

上报数据完整结构

{
    "project":"<project>",
    "logger":"javascript",
    "platform":"javascript",
    "request":{
        "headers":{
            "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"
        },
        "url":"http://fast-dev.mypaas.com.cn:8000/performance/api_status"
    },
    "exception":{
        "values":[
            {
                "type":"ReferenceError",
                "value":"a is not defined",
                "stacktrace":{
                    "frames":[
                        {
                            "filename":"http://fast-dev.mypaas.com.cn:8000/umi.js",
                            "lineno":11458,
                            "colno":22,
                            "function":"?",
                            "in_app":true
                        },
                        {
                            "filename":"http://fast-dev.mypaas.com.cn:8000/umi.dll.js",
                            "lineno":274008,
                            "colno":16,
                            "function":"DynamicComponent.umi../node_modules/react/cjs/react.development.js.Component.setState",
                            "in_app":true
                        },
                        {
                            "filename":"http://fast-dev.mypaas.com.cn:8000/umi.dll.js",
                            "lineno":258691,
                            "colno":5,
                            "function":"Object.enqueueSetState",
                            "in_app":true
                        },
                        {
                            "filename":"http://fast-dev.mypaas.com.cn:8000/umi.dll.js",
                            "lineno":264963,
                            "colno":5,
                            "function":"scheduleWork",
                            "in_app":true
                        },
                        {
                            "filename":"http://fast-dev.mypaas.com.cn:8000/umi.dll.js",
                            "lineno":265154,
                            "colno":5,
                            "function":"requestWork",
                            "in_app":true
                        },
                        {
                            "filename":"http://fast-dev.mypaas.com.cn:8000/umi.dll.js",
                            "lineno":265285,
                            "colno":3,
                            "function":"performSyncWork",
                            "in_app":true
                        },
                        {
                            "filename":"http://fast-dev.mypaas.com.cn:8000/umi.dll.js",
                            "lineno":265311,
                            "colno":7,
                            "function":"performWork",
                            "in_app":true
                        },
                        {
                            "filename":"http://fast-dev.mypaas.com.cn:8000/umi.dll.js",
                            "lineno":265399,
                            "colno":7,
                            "function":"performWorkOnRoot",
                            "in_app":true
                        },
                        {
                            "filename":"http://fast-dev.mypaas.com.cn:8000/umi.dll.js",
                            "lineno":264510,
                            "colno":7,
                            "function":"renderRoot",
                            "in_app":true
                        },
                        {
                            "filename":"http://fast-dev.mypaas.com.cn:8000/umi.dll.js",
                            "lineno":264424,
                            "colno":24,
                            "function":"workLoop",
                            "in_app":true
                        },
                        {
                            "filename":"http://fast-dev.mypaas.com.cn:8000/umi.dll.js",
                            "lineno":264384,
                            "colno":12,
                            "function":"performUnitOfWork",
                            "in_app":true
                        },
                        {
                            "filename":"http://fast-dev.mypaas.com.cn:8000/umi.dll.js",
                            "lineno":261569,
                            "colno":16,
                            "function":"beginWork",
                            "in_app":true
                        },
                        {
                            "filename":"http://fast-dev.mypaas.com.cn:8000/umi.dll.js",
                            "lineno":260723,
                            "colno":24,
                            "function":"updateClassComponent",
                            "in_app":true
                        },
                        {
                            "filename":"http://fast-dev.mypaas.com.cn:8000/umi.dll.js",
                            "lineno":260768,
                            "colno":31,
                            "function":"finishClassComponent",
                            "in_app":true
                        },
                        {
                            "filename":"http://fast-dev.mypaas.com.cn:8000/5.async.js",
                            "lineno":460,
                            "colno":12,
                            "function":"Index.render",
                            "in_app":true
                        }
                    ]
                }
            }
        ],
        "mechanism":{
            "type":"onunhandledrejection",
            "handled":false
        }
    },
    "transaction":"http://fast-dev.mypaas.com.cn:8000/5.async.js",
    "trimHeadFrames":0,
    "extra":{
        "session:duration":2768
    },
    "breadcrumbs":{
        "values":[
            {
                "timestamp":1550721477.676,
                "type":"http",
                "category":"fetch",
                "data":{
                    "method":"POST",
                    "url":"/api/analysis/data?t=1550721477448",
                    "status_code":200
                }
            },
            {
                "timestamp":1550721477.729,
                "type":"http",
                "category":"fetch",
                "data":{
                    "method":"POST",
                    "url":"/api/analysis/data?t=1550721477441",
                    "status_code":200
                }
            },
            {
                "timestamp":1550721477.76,
                "type":"http",
                "category":"fetch",
                "data":{
                    "method":"POST",
                    "url":"/api/analysis/data?t=1550721477443",
                    "status_code":200
                }
            },
            {
                "timestamp":1550721477.858,
                "type":"http",
                "category":"fetch",
                "data":{
                    "method":"POST",
                    "url":"/api/analysis/data?t=1550721477456",
                    "status_code":200
                }
            },
            {
                "timestamp":1550721478.015,
                "type":"http",
                "category":"fetch",
                "data":{
                    "method":"POST",
                    "url":"/api/analysis/data?t=1550721477438",
                    "status_code":200
                }
            },
            {
                "timestamp":1550721478.16,
                "type":"http",
                "category":"fetch",
                "data":{
                    "method":"POST",
                    "url":"/api/analysis/data?t=1550721477445",
                    "status_code":200
                }
            },
            {
                "timestamp":1550721478.445,
                "type":"http",
                "category":"fetch",
                "data":{
                    "method":"POST",
                    "url":"/api/analysis/data?t=1550721477463",
                    "status_code":200
                }
            },
            {
                "timestamp":1550721480.038,
                "category":"navigation",
                "data":{
                    "to":"/performance/api_status",
                    "from":"/overview"
                }
            },
            {
                "timestamp":1550721480.092,
                "category":"ui.click",
                "message":"li.ant-menu-item.ant-menu-item-active.ant-menu-item-selected > a.active"
            },
            {
                "timestamp":1550721480.114,
                "category":"sentry",
                "message":"ReferenceError: a is not defined",
                "event_id":"50931700539c491691c6ddd707cd587c",
                "level":"error"
            },
            {
                "timestamp":1550721480.149,
                "message":"The above error occurred in the <Index> component:
in Index (created by WithAppInfo)
in WithAppInfo (created by Connect(WithAppInfo))
in Connect(WithAppInfo) (created by DynamicComponent)
in DynamicComponent (created by Route)
in Route (created by Route)
in Switch (created by Route)
in Route (created by Route)
in Switch (created by Route)
in div (created by PrimaryContent)
in PrimaryContent (created by PrimaryLayout)
in div (created by PrimaryLayout)
in div (created by PrimaryLayout)
in PrimaryLayout (created by Connect(PrimaryLayout))
in Connect(PrimaryLayout) (created by LoadProfile)
in LoadProfile (created by Connect(LoadProfile))
in Connect(LoadProfile) (created by BaseLayout)
in div (created by BaseLayout)
in BaseLayout (created by Connect(BaseLayout))
in Connect(BaseLayout) (created by DynamicComponent)
in DynamicComponent (created by Route)
in Route (created by RouterWrapper)
in Switch (created by RouterWrapper)
in Router (created by ConnectedRouter)
in ConnectedRouter (created by RouterWrapper)
in RouterWrapper
in Provider (created by DvaContainer)
in DvaContainer

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://fb.me/react-error-boundaries to learn more about error boundaries.",
                "level":"error",
                "category":"console"
            },
            {
                "timestamp":1550721480.154,
                "type":"http",
                "category":"fetch",
                "data":{
                    "method":"GET",
                    "url":"http://fast-dev.mypaas.com.cn:8000/5.async.js",
                    "status_code":200
                }
            },
            {
                "timestamp":1550721480.161,
                "type":"http",
                "category":"fetch",
                "data":{
                    "method":"GET",
                    "url":"http://fast-dev.mypaas.com.cn:8000/umi.dll.js",
                    "status_code":200
                }
            },
            {
                "timestamp":1550721480.164,
                "type":"http",
                "category":"fetch",
                "data":{
                    "method":"GET",
                    "url":"http://fast-dev.mypaas.com.cn:8000/umi.js",
                    "status_code":200
                }
            }
        ]
    },
    "event_id":"a033c918aaec4a06b430e85d7a551ab1"
}
复制代码

TraceKit包计算的堆栈错误输出 TraceKit.computeStackTrace(err)

{
    "name":"Error",
    "message":"oops",
    "url":"http://localhost:3002/",
    "stack":[
        {
            "url":"webpack:///./vendor/TraceKit/tracekit.js?",
            "line":282,
            "func":"?"
        },
        {
            "url":"webpack:///./src/index.js?",
            "func":"eval",
            "args":[

            ],
            "line":200,
            "column":9
        },
        {
            "url":"http://localhost:3002/bundle.js?dafa8b28e39d5dc07bc8",
            "func":"Module../src/index.js",
            "args":[

            ],
            "line":461,
            "column":1
        },
        {
            "url":"http://localhost:3002/bundle.js?dafa8b28e39d5dc07bc8",
            "func":"__webpack_require__",
            "args":[

            ],
            "line":20,
            "column":30
        },
        {
            "url":"webpack:///multi_(webpack)-dev-server/client?",
            "func":"eval",
            "args":[

            ],
            "line":2,
            "column":18
        },
        {
            "url":"http://localhost:3002/bundle.js?dafa8b28e39d5dc07bc8",
            "func":"Object.0",
            "args":[

            ],
            "line":505,
            "column":1
        },
        {
            "url":"http://localhost:3002/bundle.js?dafa8b28e39d5dc07bc8",
            "func":"__webpack_require__",
            "args":[

            ],
            "line":20,
            "column":30
        },
        {
            "url":"http://localhost:3002/bundle.js?dafa8b28e39d5dc07bc8",
            "func":"?",
            "args":[

            ],
            "line":84,
            "column":18
        },
        {
            "url":"http://localhost:3002/bundle.js?dafa8b28e39d5dc07bc8",
            "func":"?",
            "args":[

            ],
            "line":87,
            "column":10
        }
    ],
    "incomplete":false,
    "partial":true
}
复制代码
相关文章
相关标签/搜索