Capture и Report JavaScript ошибки с помощью window.onerror

Capture и Report JavaScript ошибки с помощью window.onerror

От автора: onerror — это особое событие браузера, которое срабатывает всякий раз, когда была выброшена непойманная ошибка JavaScript . Это один из самых простых способов регистрации ошибок на стороне клиента и отправки их на сервер. Это также один из основных механизмов, с помощью которых работает JavaScript-интеграция клиента Sentry (raven-js).

Вы ожидаете событие onerror, назначая функцию window.onerror:

window.onerror = function (msg, url, lineNo, columnNo, error) { // ... handle error ... return false;
}

Когда возникает ошибка, в функцию передаются следующие аргументы:

msg — сообщение, связанное с ошибкой, например, «Uncaught ReferenceError: foo не определено»

url — URL-адрес скрипта или документа, связанного с ошибкой, например «/dist/app.js»

lineNo — номер строки (если имеется)

columnNo — номер столбца (если имеется)

error — объект Error, связанный с этой ошибкой (если имеется)

Первые четыре аргумента говорят вам, в каком скрипте, строке и столбце произошла ошибка. Последний аргумент, объект Error, возможно, самый ценный. Давайте узнаем, почему.

Объект Error и error.stack

На первый взгляд объект Error не является особенным. Он содержит 3 стандартных свойства: message , fileName и lineNumber. Резервированные значения, которые уже предоставлены вам через window.onerror.

Ценная часть — это нестандартное свойство: Error.prototype.stack . Это свойство стека сообщает вам, в каком месте источника каждый кадр программы был, когда произошла ошибка. Трассировка стека ошибок может быть важной частью отладки. И, несмотря на нестандартность, это свойство доступно в каждом современном браузере.

Вот пример свойства стека объекта Error в Chrome 46:

"Error: foobar\n at new bar (<anonymous>:241:11)\n at foo (<anonymous>:245:5)\n at <anonymous>:250:5\n at <anonymous>:251:3\n at <anonymous>:267:4\n at callFunction (<anonymous>:229:33)\n at <anonymous>:239:23\n at <anonymous>:240:3\n at Object.InjectedScript._evaluateOn (<anonymous>:875:140)\n at Object.InjectedScript._evaluateAndWrap (<anonymous>:808:34)" 

Трудно читать, да? Свойство stack на самом деле является просто неформатированной строкой. Вот как выглядит форматирование:

Error: foobar at new bar (<anonymous>:241:11) at foo (<anonymous>:245:5) at callFunction (<anonymous>:229:33) at Object.InjectedScript._evaluateOn (<anonymous>:875:140) at Object.InjectedScript._evaluateAndWrap (<anonymous>:808:34)

Как только он был отформатирован, легко увидеть, как свойство стека может иметь решающее значение для отладки ошибки.

Есть только одна загвоздка: свойство стека нестандартно, а его реализация отличается от браузеров. Например, вот те же трассировки стека из Internet Explorer 11:

Error: foobar at bar (Unknown script code:2:5) at foo (Unknown script code:6:5) at Anonymous function (Unknown script code:11:5) at Anonymous function (Unknown script code:10:2) at Anonymous function (Unknown script code:1:73)

Мало того, что формат каждого кадра отличается, фреймы также имеют меньшую детализацию. Например, Chrome определяет, что ключевое слово new было использовано, и упор больше делает на eval. И это только IE 11 против Chrome — другие браузеры аналогичным образом имеют разные форматы и детали.

К счастью, есть инструменты, которые нормализуют свойства стека, так что они совместимы между браузерами. Например, raven-js использует TraceKit для нормализации строк ошибок. Также есть stacktrace.js и несколько других проектов.

Совместимость с браузером

window.onerror был доступен в браузерах в течение некоторого времени — вы найдете его в браузерах, таких же старых, как IE6 и Firefox 2.

Проблема в том, что каждый браузер реализует window.onerror по-разному, в частности, сколько аргументов отправляется в обработчик onerror и структуру этих аргументов. Вот таблица того, какие аргументы передаются в onerror в большинстве браузеров:

Capture и Report JavaScript ошибки с помощью window.onerror

Вероятно, не удивительно, что Internet Explorer 8, 9 и 10 имеют ограниченную поддержку onerror. Но вы можете быть удивлены тем, что Safari только добавила поддержку объекта ошибки в Safari 10 (выпущена в 2016 году). Кроме того, старые мобильные телефоны, которые все еще используют Android-браузер (теперь замененный на Chrome Mobile), все еще там и не пропускают объект ошибки.

Без объекта ошибки нет свойства трассировки стека. Это означает, что эти браузеры не могут извлекать ценную информацию стека из ошибок, обнаруженных в результате атаки.

Polyfilling window.onerror с помощью try / catch

Но есть обходной путь — вы можете обернуть код в своем приложении в try / catch и сами поймать ошибку. Этот объект ошибки будет содержать наше обязательное свойство stack в каждом современном браузере.

Рассмотрим следующий вспомогательный метод invoke, который вызывает функцию на объекте с массивом аргументов:

function invoke(obj, method, args) { return obj[method].apply(this, args);
} invoke(Math, 'max', [1, 2]); // returns 2

Здесь invoke снова, на этот раз завернутый в try / catch, чтобы захватить любую выброшенную ошибку:

function invoke(obj, method, args) { try { return obj[method].apply(this, args); } catch (e) { captureError(e); // report the error throw e; // re-throw the error }
} invoke(Math, 'highest', [1, 2]); // throws error, no method Math.highest

Конечно, делать это вручную повсюду довольно громоздко. Вы можете сделать это проще, создав общую функцию утилиты обертки:

function wrapErrors(fn) { // don't wrap function more than once if (!fn.__wrapped__) { fn.__wrapped__ = function () { try { return fn.apply(this, arguments); } catch (e) { captureError(e); // report the error throw e; // re-throw the error } }; } return fn.__wrapped__;
} var invoke = wrapErrors(function(obj, method, args) { return obj[method].apply(this, args);
}); invoke(Math, 'highest', [1, 2]); // no method Math.highest

Поскольку JavaScript является однопоточным, вам не нужно использовать обертку повсюду — только в начале каждого нового стека. Это означает, что вам нужно обернуть объявления функций:

В начале вашего приложения (например, в $(document).ready если вы используете jQuery)

В обработчиках событий (например, addEventListener или $.fn.click )

Отклики на основе таймера (например, setTimeout или requestAnimationFrame )

Например:

$(wrapErrors(function () { // application start doSynchronousStuff1(); // doesn't need to be wrapped setTimeout(wrapErrors(function () { doSynchronousStuff2(); // doesn't need to be wrapped }); $('.foo').click(wrapErrors(function () { doSynchronousStuff3(); // doesn't need to be wrapped });
}));

Если это похоже на работу, не волнуйтесь! В большинстве библиотек отчетов об ошибках есть механизмы для расширения встроенных функций, таких как addEventListener и setTimeout так что вам не нужно каждый раз называть утилиту для обертывания. И, да, raven-js тоже делает это.

Передача ошибки на серверы

Хорошо, поэтому вы выполнили свою работу — вы подключились к window.onerror , и вы дополнительно обертываете функции в try / catch, чтобы получить как можно больше информации об ошибках.

Есть только один последний шаг: передача информации об ошибках на ваши серверы. Чтобы это работало, вам нужно настроить веб-службу отчетности, которая будет принимать ваши данные об ошибках через HTTP, записывать их в файл и / или хранить в базе данных.

Если этот веб-сервис находится в том же домене, что и ваше веб-приложение, просто используйте XMLHttpRequest. В приведенном ниже примере мы используем функцию AJAX jQuery для передачи данных на наши серверы:

function captureError(ex) { var errorData = { name: ex.name, // e.g. ReferenceError message: ex.line, // e.g. x is undefined url: document.location.href, stack: ex.stack // stacktrace string; remember, different per-browser! }; $.post('/logger/js/', { data: errorData });
}

Обратите внимание, что если вам необходимо передать свою ошибку по разному происхождению, вашей конечной точке отчетности потребуется поддержка Cross Origin Resource Sharing (CORS).

Заключение

Если вы зашли так далеко, то теперь у вас есть все инструменты, необходимые для создания собственной базовой библиотеки отчетов об ошибках и интеграции ее с вашим приложением:

Как работает window.onerror и какие браузеры он поддерживает

Как использовать try / catch для захвата трассировки стека, где отсутствует window.onerror

Передача данных об ошибках на ваши серверы

Конечно, если вы не хотите беспокоиться обо всем этом, существует множество коммерческих и open-source-инструментов, которые выполняют всю тяжелую работу отчетов на стороне клиента. (Psst: вы можете попробовать Sentry для отладки JavaScript .) Вот и все! Хорошего мониторинга ошибок.

Автор: Ben Vinegar

Источник: https://www.sitepoint.com/

Редакция: Команда webformyself.