如何在 Angular 错误上获得更好的堆栈跟踪
How do I get a better stack trace on Angular errors
目前,当我在生产 Angular (v7) 应用程序中遇到错误时,我会得到这样的堆栈跟踪。但这几乎不可能从中获得任何有意义的信息。我怎样才能获得更好的堆栈跟踪,以便我可以缩小这个难以捉摸的错误的来源?现在,我在产品中遇到了这个错误,因为它从未在我的开发机器上发生过,而且我不知道如何隔离它,因为堆栈跟踪对我来说几乎没有用。我习惯了像 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);
}
}
通常 Javascript 堆栈跟踪 更有用。不幸的是,在生产应用程序中,您通常会打开缩小,这就是为什么您会得到这样一个不可读的混乱。
如果您能负担得起更大的 Javascript 捆绑包,关闭缩小可能会很有用,以便在生产中获得更好的堆栈跟踪。
如何执行此操作将因您使用的 Angular CLI 版本而异。对于 v7 CLI:
在文件 angular.json 中,设置以下属性
{
...
"projects": {
"my-project": {
...
"architect": {
"build": {
...
"configurations": {
"production": {
"optimization": false,
"buildOptimizer": false,
...
}
...
}
this question
中的替代解决方案
下面的页面将获取堆栈跟踪,然后下载缩小的 js 和 select 错误位置前后的 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>
目前,当我在生产 Angular (v7) 应用程序中遇到错误时,我会得到这样的堆栈跟踪。但这几乎不可能从中获得任何有意义的信息。我怎样才能获得更好的堆栈跟踪,以便我可以缩小这个难以捉摸的错误的来源?现在,我在产品中遇到了这个错误,因为它从未在我的开发机器上发生过,而且我不知道如何隔离它,因为堆栈跟踪对我来说几乎没有用。我习惯了像 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);
}
}
通常 Javascript 堆栈跟踪 更有用。不幸的是,在生产应用程序中,您通常会打开缩小,这就是为什么您会得到这样一个不可读的混乱。
如果您能负担得起更大的 Javascript 捆绑包,关闭缩小可能会很有用,以便在生产中获得更好的堆栈跟踪。
如何执行此操作将因您使用的 Angular CLI 版本而异。对于 v7 CLI:
在文件 angular.json 中,设置以下属性
{
...
"projects": {
"my-project": {
...
"architect": {
"build": {
...
"configurations": {
"production": {
"optimization": false,
"buildOptimizer": false,
...
}
...
}
this question
中的替代解决方案下面的页面将获取堆栈跟踪,然后下载缩小的 js 和 select 错误位置前后的 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>