Перейти к основному содержимому

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

…а если недоступно, то:

неизвестное местоположение