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
}
複製代碼
相關文章
相關標籤/搜索