【问题标题】:AngularJS - Stack trace ignoring source mapAngularJS - 忽略源映射的堆栈跟踪
【发布时间】:2013-10-17 07:24:49
【问题描述】:

我已经编写了一个 AngularJS 应用程序,但事实证明它的调试有点像噩梦。我正在使用 Grunt + uglify 来连接和缩小我的应用程序代码。它还会在缩小的 JS 文件旁边创建一个源映射。

当文件中存在 JS 错误但在 AngularJS 应用程序之外时,源映射似乎可以正常工作。例如如果我在其中一个文件的顶部写 console.log('a.b');,Chrome 调试器中记录的错误会显示原始文件的行 + 文件信息,而不是缩小的文件。

当 Angular 自己运行的代码(例如在控制器代码中)出现问题时,就会出现问题。我从 Angular 获得了一个不错的堆栈跟踪,但它只详细说明了缩小文件而不是原始文件。

我可以做些什么来让 Angular 确认源映射?

以下示例错误:

TypeError: Cannot call method 'getElement' of undefined
at Object.addMapControls (http://my-site/wp-content/plugins/my-maps/assets/js/app.min.js:1:2848)
at Object.g [as init] (http://my-site/wp-content/plugins/my-maps/assets/js/app.min.js:1:344)
at new a (http://my-site/wp-content/plugins/my-maps/assets/js/app.min.js:1:591)
at d (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular.min.js:29:495)
at Object.instantiate (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular.min.js:30:123)

【问题讨论】:

    标签: javascript angularjs source-maps


    【解决方案1】:

    Larrifax 的 answer 很好,但有一个改进版本的功能记录在 in the same issue report

    .config(function($provide) {
    
      // Fix sourcemaps
      // @url https://github.com/angular/angular.js/issues/5217#issuecomment-50993513
      $provide.decorator('$exceptionHandler', function($delegate) {
        return function(exception, cause) {
          $delegate(exception, cause);
          setTimeout(function() {
            throw exception;
          });
        };
      });
    })
    

    这将生成两个堆栈跟踪,如 Andrew Magee noted:一个由 Angular 格式化,第二个由浏览器格式化。第二个跟踪将应用源映射。禁用重复项可能不是一个好主意,因为您可能有其他 Angular 模块也可以处理可以在此之后通过委托调用的异常。

    【讨论】:

    • setTimeout 在这里很关键。我正在尝试 Larrifax 的原始 hack,但遇到了其他不相关的错误(大概是因为抛出错误中断有角度)。
    • 不错!这对我有用,超时使其超出 Angular 的控制范围,其余的由 Chrome 完成。
    【解决方案2】:

    我能找到的唯一解决方案是硬着头皮自己解析源映射。这是一些可以执行此操作的代码。首先,您需要将source-map 添加到您的页面。然后添加这段代码:

    angular.module('Shared').factory('$exceptionHandler', 
    function($log, $window, $injector) {
      var getSourceMappedStackTrace = function(exception) {
        var $q = $injector.get('$q'),
            $http = $injector.get('$http'),
            SMConsumer = window.sourceMap.SourceMapConsumer,
            cache = {};
    
        // Retrieve a SourceMap object for a minified script URL
        var getMapForScript = function(url) {
          if (cache[url]) {
            return cache[url];
          } else {
            var promise = $http.get(url).then(function(response) {
              var m = response.data.match(/\/\/# sourceMappingURL=(.+\.map)/);
              if (m) {
                var path = url.match(/^(.+)\/[^/]+$/);
                path = path && path[1];
                return $http.get(path + '/' + m[1]).then(function(response) {
                  return new SMConsumer(response.data);
                });
              } else {
                return $q.reject();
              }
            });
            cache[url] = promise;
            return promise;
          }
        };
    
        if (exception.stack) { // not all browsers support stack traces
          return $q.all(_.map(exception.stack.split(/\n/), function(stackLine) {
            var match = stackLine.match(/^(.+)(http.+):(\d+):(\d+)/);
            if (match) {
              var prefix = match[1], url = match[2], line = match[3], col = match[4];
              return getMapForScript(url).then(function(map) {
                var pos = map.originalPositionFor({
                  line: parseInt(line, 10), 
                  column: parseInt(col, 10)
                });
                var mangledName = prefix.match(/\s*(at)?\s*(.*?)\s*(\(|@)/);
                mangledName = (mangledName && mangledName[2]) || '';
                return '    at ' + (pos.name ? pos.name : mangledName) + ' ' + 
                  $window.location.origin + pos.source + ':' + pos.line + ':' + 
                  pos.column;
              }, function() {
                return stackLine;
              });
            } else {
              return $q.when(stackLine);
            }
          })).then(function (lines) {
            return lines.join('\n');
          });
        } else {
          return $q.when('');
        }
      };
    
      return function(exception) {
        getSourceMappedStackTrace(exception).then($log.error);
      };
    });
    

    此代码将下载源代码,然后下载源映射,解析它们,最后尝试替换堆栈中的位置跟踪映射的位置。这在 Chrome 中完美运行,在 Firefox 中完全可以接受。缺点是您在代码库中添加了相当大的依赖项,并且您从非常快速的同步错误报告转向了相当慢的异步错误报告。

    【讨论】:

    • 这对我有用,在 Chrome 中。我将_.map 更改为$.map,以便它使用jQuery 而不是underscore.js。我已经有一个 jQuery 依赖项,不想添加 underscore.js。
    • 在 Firefox 中为我工作,太棒了,角度堆栈跟踪已经困扰了我好多年了。
    • 如果您不介意在控制台中记录两次异常,您可以包含smaller, lighter snippet of code
    • "为了防止代码压缩器破坏您的 Angular 应用程序,您必须使用数组语法来定义控制器。" stackoverflow.com/a/20266527/1087768
    【解决方案3】:

    我刚刚遇到了同样的问题,并且一直在寻找解决方案 - 显然这是一个 Chrome 问题,通常是堆栈跟踪,并且恰好适用于 Angular,因为它在错误报告中使用堆栈跟踪。见:

    Will the source mapping in Google Chrome push to Error.stack

    【讨论】:

    【解决方案4】:

    我会看看以下项目:https://github.com/novocaine/sourcemapped-stacktrace

    它与@jakub-hampl 的答案基本相同,但可能有用。

    【讨论】:

    • 你试过了吗?它似乎对我不起作用..它在任何角度模块/指令/等代码中都不起作用
    【解决方案5】:

    根据this issue 看来,Angular 的$logProvider 破坏了源映射。问题中建议了这样的解决方法:

    var module = angular.module('source-map-exception-handler', [])
    
    module.config(function($provide) {
      $provide.decorator('$exceptionHandler', function($delegate) {
        return function(exception, cause) {
            $delegate(exception, cause);
            throw exception;
        };
      });
    });
    

    【讨论】:

    • 对我来说在 Angular 1.3.8 中工作正常cl.ly/image/3y0m0J3w3p1J/20150110-190523.png
    • 有点为我工作。照原样,我会看到令人讨厌的堆栈跟踪(有时是几次)和一个很好的堆栈跟踪。如果我注释掉$delegate(exception, cause),那么我只会得到良好的堆栈跟踪。
    • 虽然这适用于 Angular 1.3.8,但请参阅下面的答案以获得更清晰的答案,同样来自同一问题。
    【解决方案6】:

    由于 Chrome 中的错误 has been fixed(但问题在 Angular 中仍然存在),不打印两次堆栈跟踪的解决方法是:

    app.factory('$exceptionHandler', function() {
        return function(exception, cause) {
            console.error(exception.stack);
        };
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-21
      • 2012-03-11
      相关资源
      最近更新 更多