堆栈跟踪 API
V8 中抛出的所有内部错误在创建时都会捕获堆栈跟踪。可以通过非标准的 error.stack
属性从 JavaScript 访问此堆栈跟踪。V8 还具有各种钩子,用于控制如何收集和格式化堆栈跟踪,以及允许自定义错误也收集堆栈跟踪。本文档概述了 V8 的 JavaScript 堆栈跟踪 API。
基本堆栈跟踪 #
默认情况下,V8 抛出的几乎所有错误都具有一个 stack
属性,该属性保存最顶部的 10 个堆栈帧,格式化为字符串。以下是一个完全格式化的堆栈跟踪示例
ReferenceError: FAIL is not defined
at Constraint.execute (deltablue.js:525:2)
at Constraint.recalculate (deltablue.js:424:21)
at Planner.addPropagate (deltablue.js:701:6)
at Constraint.satisfy (deltablue.js:184:15)
at Planner.incrementalAdd (deltablue.js:591:21)
at Constraint.addConstraint (deltablue.js:162:10)
at Constraint.BinaryConstraint (deltablue.js:346:7)
at Constraint.EqualityConstraint (deltablue.js:515:38)
at chainTest (deltablue.js:807:6)
at deltaBlue (deltablue.js:879:2)
堆栈跟踪是在创建错误时收集的,无论错误在何处或抛出多少次,它都是相同的。我们收集 10 个帧,因为这通常足以有用,但不会太多以至于对性能产生明显的负面影响。可以通过设置变量来控制收集多少个堆栈帧
Error.stackTraceLimit
将其设置为 0
将禁用堆栈跟踪收集。任何有限的整数值都可以用作要收集的帧的最大数量。将其设置为 Infinity
表示将收集所有帧。此变量仅影响当前上下文;必须为每个需要不同值的上下文显式设置它。(请注意,在 V8 术语中,所谓的“上下文”对应于 Google Chrome 中的页面或 <iframe>
)。要设置影响所有上下文的不同默认值,请使用以下 V8 命令行标志
--stack-trace-limit <value>
要在运行 Google Chrome 时将此标志传递给 V8,请使用
--js-flags='--stack-trace-limit <value>'
异步堆栈跟踪 #
--async-stack-traces
标志(从 V8 v7.3 开始默认启用)启用了新的 零成本异步堆栈跟踪,它使用异步堆栈帧丰富了 Error
实例的 stack
属性,即代码中的 await
位置。这些异步帧在 stack
字符串中用 async
标记
ReferenceError: FAIL is not defined
at bar (<anonymous>)
at async foo (<anonymous>)
在撰写本文时,此功能仅限于 await
位置、Promise.all()
和 Promise.any()
,因为对于这些情况,引擎可以在没有任何额外开销的情况下重建必要的信息(这就是为什么它是零成本的)。
自定义异常的堆栈跟踪收集 #
用于内置错误的堆栈跟踪机制是使用通用的堆栈跟踪收集 API 实现的,该 API 也可用于用户脚本。该函数
Error.captureStackTrace(error, constructorOpt)
在给定的 error
对象中添加一个堆栈属性,该属性在调用 captureStackTrace
时产生堆栈跟踪。通过 Error.captureStackTrace
收集的堆栈跟踪会立即收集、格式化并附加到给定的 error
对象。
可选的 constructorOpt
参数允许您传入一个函数值。在收集堆栈跟踪时,所有高于对该函数的最高调用(包括该调用)的帧都将从堆栈跟踪中排除。这对于隐藏对用户无用的实现细节很有用。定义捕获堆栈跟踪的自定义错误的通常方法是
function MyError() {
Error.captureStackTrace(this, MyError);
// Any other initialization goes here.
}
将 MyError 作为第二个参数传入意味着对 MyError 的构造函数调用不会显示在堆栈跟踪中。
自定义堆栈跟踪 #
与 Java 不同,Java 中异常的堆栈跟踪是一个结构化值,允许检查堆栈状态,V8 中的堆栈属性只保存一个包含格式化堆栈跟踪的扁平字符串。这仅仅是为了与其他浏览器兼容。但是,这不是硬编码的,而只是默认行为,可以由用户脚本覆盖。
为了效率,堆栈跟踪不会在捕获时格式化,而是在需要时格式化,即第一次访问堆栈属性时。堆栈跟踪是通过调用
Error.prepareStackTrace(error, structuredStackTrace)
并使用此调用返回的任何内容作为 stack
属性的值来格式化的。如果您将不同的函数值分配给 Error.prepareStackTrace
,则该函数将用于格式化堆栈跟踪。它将传递正在为其准备堆栈跟踪的错误对象,以及堆栈的结构化表示。用户堆栈跟踪格式化程序可以自由地以他们想要的任何方式格式化堆栈跟踪,甚至返回非字符串值。在调用 prepareStackTrace
完成后保留对结构化堆栈跟踪对象的引用是安全的,因此它也是一个有效的返回值。请注意,自定义 prepareStackTrace
函数仅在访问 Error
对象的堆栈属性后才会被调用。
结构化堆栈跟踪是一个 CallSite
对象数组,每个对象代表一个堆栈帧。CallSite
对象定义了以下方法
getThis
:返回this
的值getTypeName
:将this
的类型作为字符串返回。这是存储在this
的构造函数字段中的函数的名称(如果可用),否则是对象的[[Class]]
内部属性。getFunction
:返回当前函数getFunctionName
:返回当前函数的名称,通常是它的name
属性。如果name
属性不可用,则会尝试从函数的上下文中推断名称。getMethodName
:返回this
或其原型之一的属性的名称,该属性保存当前函数getFileName
:如果此函数是在脚本中定义的,则返回脚本的名称getLineNumber
:如果此函数是在脚本中定义的,则返回当前行号getColumnNumber
:如果此函数是在脚本中定义的,则返回当前列号getEvalOrigin
:如果此函数是使用对eval
的调用创建的,则返回一个字符串,表示调用eval
的位置isToplevel
:这是一个顶级调用吗,也就是说,这是全局对象吗?isEval
:此调用是否发生在通过对eval
的调用定义的代码中?isNative
:此调用是否在 V8 本地代码中?isConstructor
:这是一个构造函数调用吗?isAsync
:这是一个异步调用吗(即await
、Promise.all()
或Promise.any()
)?isPromiseAll
:这是一个对Promise.all()
的异步调用吗?getPromiseIndex
:对于异步堆栈跟踪,返回在Promise.all()
或Promise.any()
中跟随的 promise 元素的索引,如果CallSite
不是异步Promise.all()
或Promise.any()
调用,则返回null
。
默认堆栈跟踪是使用 CallSite API 创建的,因此通过该 API 可用的任何信息也可以通过此 API 获得。
为了维护对严格模式函数施加的限制,具有严格模式函数的帧及其下方的所有帧(其调用者等)不允许访问其接收器和函数对象。对于这些帧,getFunction()
和 getThis()
返回 undefined
。
兼容性 #
此处描述的 API 特定于 V8,不受任何其他 JavaScript 实现支持。大多数实现确实提供了一个 error.stack
属性,但堆栈跟踪的格式可能与此处描述的格式不同。建议使用此 API 的方法是
- 只有在知道您的代码在 v8 中运行时,才依赖格式化的堆栈跟踪的布局。
- 无论您的代码在哪个实现中运行,设置
Error.stackTraceLimit
和Error.prepareStackTrace
都是安全的,但请注意,只有在您的代码在 V8 中运行时才会生效。
附录:堆栈跟踪格式 #
V8 使用的默认堆栈跟踪格式可以为每个堆栈帧提供以下信息
- 调用是否为构造调用。
this
值的类型(Type
)。- 调用的函数的名称(
functionName
)。 - 保存函数的
this
或其原型之一的属性的名称(methodName
)。 - 源代码中的当前位置(
location
)
这些信息中的任何一个都可能不可用,并且根据可用信息的多少,堆栈帧使用不同的格式。如果所有上述信息都可用,则格式化的堆栈帧如下所示
at Type.functionName [as methodName] (location)
或者,在构造调用情况下
at new functionName (location)
或者,在异步调用情况下
at async functionName (location)
如果只有 functionName
和 methodName
之一可用,或者如果它们都可用但相同,则格式为
at Type.name (location)
如果两者都不可用,则使用 <anonymous>
作为名称。
Type
值是存储在 this
的构造函数字段中的函数的名称。在 V8 中,所有构造函数调用都将此属性设置为构造函数,因此除非在创建对象后主动更改了此字段,否则它将保存创建它的函数的名称。如果它不可用,则使用对象的 [[Class]]
属性。
一个特例是全局对象,其中不显示 Type
。在这种情况下,堆栈帧的格式为
at functionName [as methodName] (location)
位置本身有几种可能的格式。最常见的是定义当前函数的脚本中的文件名、行号和列号
fileName:lineNumber:columnNumber
如果当前函数是使用 eval
创建的,则格式为
eval at position
…其中 position
是调用 eval
的完整位置。请注意,这意味着如果存在对 eval
的嵌套调用,则位置可以嵌套,例如
eval at Foo.a (eval at Bar.z (myscript.js:10:3))
如果堆栈帧位于 V8 的库中,则位置为
native
…如果不可用,则为
unknown location