【问题标题】:How do I get a better stack trace on Angular errors如何在 Angular 错误上获得更好的堆栈跟踪
【发布时间】:2019-08-05 20:33:37
【问题描述】:

目前,当我在生产 Angular (v7) 应用程序中遇到错误时,我会收到这样的堆栈跟踪。但这实际上是不可能从中获得任何有意义的信息的。如何获得更好的堆栈跟踪,以便我可以缩小这个难以捉摸的错误的来源?现在,我在 prod 中遇到这个错误,因为它从未在我的开发机器上发生过,而且我不知道如何隔离它,因为堆栈跟踪对我来说几乎没有用。我习惯了像 C# 这样的语言,在那里你会得到一个非常简洁的堆栈跟踪,它可以为你提供一个映射到错误函数的地图。这个堆栈跟踪没有任何意义。

TypeError: Cannot read property 'appendChild' of null
at create_dom_structure (eval at  (:15:1), :1:16231)
at load_map (eval at  (:15:1), :1:101028)
at Object.load (eval at  (:15:1), :1:107834)
at eval (eval at  (:15:1), :1:108251)
at t.invokeTask (https://mywebsite.com/polyfills.d8680adf69e7ebd1de57.js:1:8844)
at Object.onInvokeTask (https://mywebsite.com/main.25a9fda6ea42f4308b79.js:1:467756)
at t.invokeTask (https://mywebsite.com/polyfills.d8680adf69e7ebd1de57.js:1:8765)
at e.runTask (https://mywebsite.com/polyfills.d8680adf69e7ebd1de57.js:1:4026)
at e.invokeTask (https://mywebsite.com/polyfills.d8680adf69e7ebd1de57.js:1:9927)
at invoke (https://mywebsite.com/polyfills.d8680adf69e7ebd1de57.js:1:9818)

我的错误处理程序:

export class AppErrorHandler extends ErrorHandler {

constructor(
    private _http: Http,
    private injector: Injector,
) {
    super();
}

public handleError(error: any): void {
    if (error.status === '401') {
        alert('You are not logged in, please log in and come back!');
    } else {
        const router = this.injector.get(Router);

        const reportOject = {
            status: error.status,
            name: error.name,
            message: error.message,
            httpErrorCode: error.httpErrorCode,
            stack: error.stack,
            url: location.href,
            route: router.url,
        };
        this._http.post(`${Endpoint.APIRoot}Errors/AngularError`, reportOject)
            .toPromise()
            .catch((respError: any) => {
                Utils.formatError(respError, `AppErrorHandler()`);
            });
    }
    super.handleError(error);
}
}

【问题讨论】:

    标签: angular error-handling stack-trace


    【解决方案1】:

    通常 Javascript 堆栈跟踪 更有用。不幸的是,在生产应用程序中,您通常会打开缩小,这就是为什么您会得到如此难以理解的混乱。

    如果您能负担得起更大的 Javascript 包,关闭缩小功能可能会很有用,以便在生产中获得更好的堆栈跟踪。

    如何执行此操作将根据您使用的 Angular CLI 版本而有所不同。对于 v7 CLI:

    在文件 angular.json 中,设置以下属性

    {
      ...
      "projects": {
        "my-project": {
          ...
          "architect": {
            "build": {
              ...
              "configurations": {
                "production": {
                  "optimization": false,
                  "buildOptimizer": false,
                  ...
                }
        ...
    }
    

    this question 中的替代解决方案

    【讨论】:

    • 我同意,缩小是“模糊”堆栈跟踪的原因。我讨厌关闭它,因为使用它有很多明显的好处,主要是性能,其次是安全性。但是,我现在可以将其关闭以诊断此错误。所以这是我的问题的一个很好的解决方案。我读了另一篇文章,但似乎没有更好的方法。也许我会用 Angular 打开一个功能请求,他们会做一些事情来使错误处理更有用一点。我上面介绍的堆栈跟踪可能对任何人都没有用,使得错误处理几乎毫无用处。
    • 是的,这不是一个很好的解决方案。但是假设你有步骤在生产中重现错误,或者它经常发生,你可以把它关掉,等到你得到一个可用的堆栈跟踪,然后在你调查时重新打开它
    【解决方案2】:

    下面的页面将进行堆栈跟踪,然后下载缩小的 js 并选择错误位置前后的 100 个字符。它将对所有级别执行此操作,然后显示。

    因为它必须从具有相同部署的相同域访问,该部署收集了堆栈跟踪。我确信相同的概念可以很容易地移植到节点,因为没有任何跨域或路径限制。

    它还有一个按钮,可以用备份路径或二级域名替换基本路径。

    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <title></title>
    
      <script>
        var updateUrl = false;
    
        const scriptFiles = new Map()
        var newDivGroup = null;
    
        function clickDecode(updateUrlNew) { 
          updateUrl = updateUrlNew;
          decodeNow();
        }
    
        async function decodeNow() {
          var textOutput = document.getElementById('text-output');
          textOutput.innerHTML = '';
    
          var myString = document.getElementById('text-input').value;
          // console.log(myString);
          var myRegexp = /(https:[a-zA-Z0-9\/\.]+.js):([0-9]+):([0-9]+)/g;
          var matches = myString.matchAll(myRegexp);
          for (const match of matches) {
            newDivGroup = document.createElement("div");
            newDivGroup.style.marginBottom = '6px';
            newDivGroup.style.padding = '15px';
            newDivGroup.style.backgroundColor = '#eee';
            newDivGroup.style.border = '1px solid #555'
            textOutput.appendChild(newDivGroup);
    
            // logToOutput('-');
            logToOutput(match[0]);
            await showErrorLocation(match[1], match[2], match[3])
          }
        }
    
        function logToOutput(message) {
          const newDiv = document.createElement("div");
          const newContent = document.createTextNode(message);
          newDiv.appendChild(newContent);
          
          newDivGroup.appendChild(newDiv);
        }
    
        async function showErrorLocation(scriptUrl, row, position) {
          if(updateUrl) {
            scriptUrl = scriptUrl.toLowerCase();      
            scriptUrl = scriptUrl.replaceAll('/dist/', '/dist-old/');      
          }
          position = parseInt(position);
          if(!scriptFiles.has(scriptUrl)) {
            const fileContents = await downloadScriptFile(scriptUrl);
            scriptFiles.set(scriptUrl, fileContents);
          }
          const fileContents = scriptFiles.get(scriptUrl);
          const fileLines = fileContents.split('\n');
          const fileLine = fileLines[row - 1];
    
          let before = fileLine.substring(position - 100, position - 1);
          let after = fileLine.substring(position - 1, position + 100);
          logToOutput(before);
          logToOutput(after);
        }
    
        function downloadScriptFile(filePath) {
          return new Promise((resolve, reject) => {
            let xhr = new XMLHttpRequest();
            xhr.open('GET', filePath);
            xhr.responseType = 'text';
            xhr.send();
            xhr.onload = function() {
              if (xhr.status != 200) {
                console.error(`Error ${xhr.status}: ${xhr.statusText}`);
                reject();
              } else {
                // console.log(xhr.response);
                return resolve(xhr.response);
              }
            };
          });
        }
      </script>
    <body style="padding: 30px;">
    
      <textarea id="text-input" rows="12" cols="150">
    
      </textarea>
      <br/>
      <button onclick="clickDecode(false)">Run (current deploy)</button>
      <button onclick="clickDecode(true)">Replace URL and Run (prior deploy)</button>
      <br/><br/>
      <div id="text-output">
    
      </div>
    </body>
    </html>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-09-05
      • 2017-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-03
      • 2018-09-19
      • 1970-01-01
      相关资源
      最近更新 更多