API трассировки стека
Все внутренние ошибки, возникающие в V8, захватывают трассировку стека в момент создания. Этот стек можно получить из JavaScript через нестандартное свойство error.stack
. V8 также предоставляет различные хуки для контроля того, как собираются и форматируются трассировки стека, а также для того, чтобы настраиваемые ошибки также могли собирать трассировку стека. Этот документ описывает API трассировки стека JavaScript в V8.
Основные трассировки стека
По умолчанию почти все ошибки, выбрасываемые 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 соответствует странице или <iframe>
в Google Chrome). Чтобы задать другое значение по умолчанию, влияющее на все контексты, используйте следующий командный флаг V8:
--stack-trace-limit <value>
Чтобы передать этот флаг V8 при запуске Google Chrome, используйте:
--js-flags='--stack-trace-limit <value>'
Асинхронные трассировки стека
Флаг --async-stack-traces
(включен по умолчанию начиная с V8 v7.3) активирует новые асинхронные трассировки стека без затрат, которые обогащают свойство stack
экземпляров Error
асинхронными кадрами стека, например, местоположениями await
в коде. Эти асинхронные кадры отображаются с пометкой async
в строке stack
:
ReferenceError: FAIL is not defined
at bar (<anonymous>)
at async foo (<anonymous>)
На момент написания этого документа, эта функциональность ограничивается местоположениями await
, Promise.all()
и Promise.any()
, поскольку для этих случаев движок может восстановить необходимую информацию без дополнительной нагрузки (поэтому это "без затрат").
Сбор трассировки стека для пользовательских исключений
Механизм трассировки стека, используемый для встроенных ошибок, реализован с использованием общего API сбора трассировки стека, который также доступен для пользовательских скриптов. Функция
Error.captureStackTrace(error, constructorOpt)
добавляет свойство stack
к заданному объекту error
, которое предоставляет трассировку стека на момент вызова captureStackTrace
. Трассировки стека, собранные через Error.captureStackTrace
, немедленно собираются, форматируются и прикрепляются к заданному объекту error
.
Необязательный параметр constructorOpt
позволяет передать функцию. При сборе трассировки стека все кадры выше самого верхнего вызова этой функции, включая этот вызов, исключаются из трассировки стека. Это может быть полезно для исключения деталей реализации, которые не будут полезны пользователю. Обычный способ определения пользовательской ошибки, которая захватывает трассировку стека, выглядит так:
function MyError() {
Error.captureStackTrace(this, MyError);
// Инициализация других параметров здесь.
}
Передача MyError в качестве второго аргумента означает, что вызов конструктора MyError не будет отображаться в трассировке стека.
Настройка трассировок стека
В отличие от Java, где трассировка стека исключения представляет собой структурированное значение, позволяющее инспектировать состояние стека, свойство stack
в V8 просто содержит плоскую строку с отформатированной трассировкой стека. Это сделано только для совместимости с другими браузерами. Однако это поведение не жёстко закодировано, а является только поведением по умолчанию и может быть переопределено пользовательскими скриптами.
Для повышения эффективности трассировки стека не форматируются при их захвате, а по требованию, при первом доступе к свойству стека. Трассировка стека форматируется вызовом
Error.prepareStackTrace(error, structuredStackTrace)
и используя результат этого вызова в качестве значения свойства stack
. Если назначить новую функцию в Error.prepareStackTrace
, то эта функция будет использоваться для форматирования стека вызовов. Она принимает объект ошибки, для которого формирует стек вызовов, а также структурированное представление стека. Пользовательские форматеры стека вызовов могут произвольно изменять формат стека и даже возвращать значения, которые не являются строками. Безопасно сохранять ссылки на структурированный объект стека вызовов после завершения вызова prepareStackTrace
, так что он также может быть допустимым значением для возвращения. Обратите внимание, что пользовательская функция prepareStackTrace
вызывается только один раз, когда свойство stack
объекта 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()
для асинхронных стеков вызовов, илиnull
, еслиCallSite
не является асинхронным вызовомPromise.all()
илиPromise.any()
.
Стандартный стек вызовов создаётся с использованием API CallSite, поэтому любая информация, доступная там, также доступна через этот 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
…а если недоступно, то:
неизвестное местоположение