了解Error.captureStackTrace和堆栈跟踪持久化?

22

我之前用过Joyent的Verror来避免重抛异常导致堆栈跟踪丢失,最近使用Node v12测试了一下,发现即使不使用Verror也能保留堆栈跟踪。

然后我看了一下Error.captureStackTrace的使用方法,它表示在错误处理中使用它可以避免错误被添加到堆栈中。

我不知道我做错了什么,但是使用或不使用Error.captureStackTrace,堆栈跟踪都是相同的。

我想知道captureStackTrace的当前状态,因为我看不出任何区别,并且VError似乎不再需要了。

文档中说,如果不调用captureStackTrace,则.stack不可用,但每次都可以看到,无论是否调用captureStackTrace。

以下是两个错误示例:

class MyErrorOne extends Error {
  constructor(message) {
    super(message);

    Error.captureStackTrace(this, this.constructor);
  }
}

class MyErrorTwo extends Error {
  constructor(message) {
    super(message);

    Error.captureStackTrace(this, this.constructor);
  }
}

我试着通过注释掉每个错误中的captureStackTrace方法来测试它们,但是堆栈跟踪结果都是一样的。

有人能帮忙吗?

这是我的测试代码:

const DoOne = () => {
  try {
    console.log("executing do one");
    DoTwo();
  } catch (error) {
    console.log("error in DoOne", error);
    console.log("here is the stack ", error.stack);
    throw new MyErrorOne("threw error from doone in myerrorone");
    // throw error;
  }
};

const DoTwo = () => {
  try {
    console.log("executing do two");
    throw new MyErrorTwo("threw error from dotwo in myerrortwo");
  } catch (error) {
    console.log("error in DoTwo", error);
    throw error;
  }
};

DoOne();
4个回答

30

调用Error.captureStackTrace(obj[, fun])时,一个格式化的调用堆栈将附加到obj中,包括调用captureStackTrace的行/帧。

当指定fun时,所有在fun上方和包括fun的帧都将被删除。

示例:

注意:在示例输出中,我已经删除了调用fun1以下的任何内容,以减少混乱。

const fun1 = () => { fun2(); };
const fun2 = () => { fun3(); };
const fun3 = () => { log_stack(); };
function log_stack() {
    let err = {};
    Error.captureStackTrace(err);
    console.log(err.stack);
}
fun1();

这将得出:

Error
    at log_stack (/path/to/example.js:6:8)
    at fun3 (/path/to/example.js:3:22)
    at fun2 (/path/to/example.js:2:22)
    at fun1 (/path/to/example.js:1:22)

现在,如果我们将log_stack作为函数选项添加到captureStackTrace

Error.captureStackTrace(err, log_stack);

它产生:

Error
    at fun3 (/path/to/example.js:3:22)
    at fun2 (/path/to/example.js:2:22)
    at fun1 (/path/to/example.js:1:22)

log_stack 帧不再存在。

fun3 添加到 captureStackTrace 中:

Error.captureStackTrace(err, fun3);

它产生:

Error
    at fun2 (/path/to/example.js:2:22)
    at fun1 (/path/to/example.js:1:22)

等等。

在你的情况下,如果你改变:

Error.captureStackTrace(this, this.constructor);

至:

Error.captureStackTrace(this);

您会发现,使用 new MyError... 会得到一个额外的行:

error in DoTwo MyErrorTwo: threw error from dotwo in myerrortwo
    at new MyErrorTwo (/path/to/testerrorclass.js:10:9)  <<== Not removed anymore.

9

我不确定,但似乎 Error.captureStackTrace 用于在不扩展 Error 类的情况下构造 Error 对象(请参见Node文档中提供的示例)。

我猜想当您扩展 Error 类时,捕获堆栈跟踪已经由 Error 构造函数完成了。

我测试了您的代码并删除了 Error 的扩展,确实如果您不调用 captureStackTrace,则无法在错误对象上获取堆栈跟踪。

我不确定抛出未扩展 Error 类的错误有什么用途。


0

这是我一段时间前发现的实现方式,关键在于恢复原型链(TypeScript):

import {INTERNAL_SERVER_ERROR} from 'http-status-codes';

export class RouteError extends Error {
  public statusCode: number;
  /**
   * Create a new RouteError object to be handled in 'express-async-handler' wrapped middleware.
   * @param message {string} [message=An error occurred.] - The json message key to return to the user.
   * @param statusCode {number} [statusCode=INTERNAL_SERVER_ERROR] - response code to send.
   */
  constructor(message: string = 'An error occurred.', statusCode: number = INTERNAL_SERVER_ERROR) {
    super(message); // 'Error' breaks prototype chain here
    Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
    this.statusCode = statusCode;
  }
}

0

captureStackTrace 返回一个字符串,表示调用中特定错误的位置。它给我们提供了一个堆栈,帮助我们在代码中找到 new Error() 被调用的位置。这将帮助我们在代码中找到确切的错误。

我不确定你提到的是哪个文档,但如果你阅读 V8 stack trace docs,可能会有所帮助。

Error.captureStackTrace(error, constructorOpt)

给定错误对象添加一个堆栈属性,该属性在调用captureStackTrace时返回堆栈跟踪。通过Error.captureStackTrace收集的堆栈跟踪会立即被收集、格式化并附加到给定的错误对象上。
可选的constructorOpt参数允许您传递一个函数值。在收集堆栈跟踪时,所有在此函数最顶部调用之上的帧(包括该调用)都将从堆栈跟踪中省略。这可以隐藏对用户无用的实现细节,非常有用。定义捕获堆栈跟踪的自定义错误的通常方式如下:
function MyError() {
      // if you are calling this in constructor, instead of MyError, "this.constructor"
      Error.captureStackTrace(this, MyError);
      // Any other initialization goes here.
    }

将MyError作为第二个参数传递意味着MyError的构造函数调用不会显示在堆栈跟踪中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接