Использование системы отслеживания js-ошибок трудно переоценить. Даже на покрытом тестами сайте возникают js-ошибки, важно их найти и починить. Расскажу как искал подходящее решение.
Существует три вида систем отслеживания js-ошибок.
Первый вид
Самописная система. Добавляем компактный js-код на сайт:
window.onerror = function(msg, file, line, col) {
new Image().src = '/jserrors/?msg=' + msg + ...;
}
Делаем «ручку» для сохранения ошибок, парсим логи. В лучшем случае, пишем свой интерфейс для анализа ошибок. Потом занимаемся доработкой и поддержкой.
Второй вид
Платный сервис с расширенными возможностями, но с ограничениями, например, на количество js-ошибок в день или месяц. На любой сайт поставить эту систему не получится, придётся каждый раз выбирать какой из ваших сайтов «достоин» платного сервиса. И не забывать оплачивать услуги.
Нужно будет добавить на сайт внешний скрипт, который отрицательно повлияет на скорость загрузки вашего сайта.
Третий вид
Использовать для сбора ошибок систему аналитики, для этого необходимо установить на сайт её полноценный код отслеживания.
Так как код отслеживания загружается асинхронно, то отправка ошибок будет доступна только после его загрузки. У данной системы должны быть отчёты, которые можно самостоятельно формировать.
Решение
Объединим первый и третий вид. Ошибки будем вручную отправлять в Яндекс.Метрику с помощью компактного js-кода. В Метрике есть необходимые отчёты и инструменты для анализа данных, нет особых ограничений на количество счётчиков и собираемых данных, и она бесплатна.
Для сбора ошибок подойдёт отчёт «Параметры визитов».
Заведём отдельный счётчик.
Добавим на страницу перед всеми скриптами компактный код:
<script> // Не более 320 байт после минификации. window.onerror = function(msg, file, line, col, err) { // Отсекаем совсем старые браузеры. if (!window.JSON) { return; } var counterId = 12345, // Ваш номер счётчика Метрики. siteInfo = {}, pointer = siteInfo, // Список параметров визитов. path = [ 'JS errors', // 1 уровень msg, // 2 уровень err && err.stack || (file + ':' + line + ':' + col) // 3 уровень // Не хватает параметров? Добавьте ещё! ]; // Преобразуем параметры из плоского в древовидный вид для отчёта. for (var i = 0; i < path.length - 1; i++) { var item = path[i]; pointer[item] = {}; pointer = pointer[item]; } pointer[path[i]] = 1; new Image().src = 'https://mc.yandex.ru/watch/' + counterId + '/?site-info=' + encodeURIComponent(JSON.stringify(siteInfo)) '&rn=' + Math.random(); }; </script>
Не забываем указать в коде свой номер счётчика (counterId).
- Получаем примерно такой отчёт:
Структуру и порядок параметров в отчёте можно менять на лету, а также добавлять новые параметры. Давайте с помощью кнопки «Группировки» добавим браузер и ОС в отчёт.
И ещё один момент, если скрипты на сайте загружаются с другого домена (CDN), то в отчёте, скорее всего, будут видны сообщения вида «Script error» и без стека.
Чтобы вернуть сообщениям нормальный вид, необходимо добавить к скриптам атрибут crossorigin="anonymous"
и HTTP-заголовок Access-Control-Allow-Origin:"*"
.
<script src="https://mycdn.com/folder/file.js" crossorigin="anonymous"></script>
Постепенно в отчёте будут появляться ошибки от расширений браузеров, вирусов и внешних скриптов (рекламных систем, кнопок социальных сетей и пр.). Чтобы отделить эти ошибки, добавим проверку домена у скриптов с помощью регулярного выражения.
Дополнительно добавим ограничение на собираемое количество ошибок (не более 5) на странице. Например, однотипные ошибки, возникающие при движении мышки, могут создать сотни запросов в Метрику.
В современных браузерах данные будем отправлять через sendBeacon
.
<script>
window.onerror = function handler(msg, file, line, col, err) {
if (!window.JSON || handler.count > 5) { return; }
var counterId = 12345, // Ваш номер счётчика Метрики.
siteInfo = {},
pointer = siteInfo,
stack = err && err.stack,
path = [
// Укажите в регулярном выражении домены, с которых загружаются ваши скрипты и сайт.
'JS ' + (!file || /mysite\.ru|cdn\.com/.test(file) ? 'in' : 'ex') + 'ternal errors',
'message: ' + msg,
stack ?
'stack: ' + stack :
(file ? 'file: ' + file + ':' + line + ':' + col : 'nofile'),
'href: ' + location.href
];
for (var i = 0; i < path.length - 1; i++) {
var item = path[i];
pointer[item] = {};
pointer = pointer[item];
}
pointer[path[i]] = 1;
var url = 'https://mc.yandex.ru/watch/' + counterId + '/' +
'?site-info=' + encodeURIComponent(JSON.stringify(siteInfo)) +
'&rn=' + Math.random();
if (typeof navigator.sendBeacon === 'function') {
navigator.sendBeacon(url, ' ');
} else {
new Image().src = url;
}
if (handler.count) {
handler.count++;
} else {
handler.count = 1;
}
};
</script>
И ещё, данные по ошибкам можно получить с помощью API и сделать с ними всё что угодно.
Не забудьте добавить ссылки на отчёты по ошибкам в свою документацию на видное место.
И дать доступ к отчётам остальным разработчикам из группы, чтобы исправление ошибок превратилось в соревнование.
Ссылки:
- GitHub
- Отчёт «Параметры визитов»
- Пример отчёта в Метрике
Комментарии (21)
IvanSCM
22.03.2017 12:41+13Напомнилоvitalets
23.03.2017 09:07+2Отличная статья, мы тоже именно так делаем!
Несколько дополнений:
Кроме подписки на ошибки еще стоит подписаться на непойманные промисы через onunhandledrejection.
Саму подписку лучше делать через
window.addEventListener
, а то какая-нибудь сторонняя библиотека может переопределитьwindow.onerror
.
Чтобы подписаться сразу и на ошибки и на промисы есть библиотечка uncaught.
URL для метрики можно не конструировать вручную, а использовать метод hit из API счетчика, передавая данные в
params
.
- Ну и справедливости ради, у Google Analytics есть готовый механизм сбора ошибок.
hcodes
23.03.2017 22:221. Малая поддержка в браузерах, только Chrome.
2. У IE8 нет addEventListener. А так, конечно, лучше addEventListener.
3. Зависимость, которая тянет за собой дополнительный код. Код сбора ошибок инлайнится перед всеми скриптами и должен быть лаконичным.
4. Про метод знаю, но реализация в статье не зависит от кода отслеживания Метрики.
5. Метрике на заметку. :)justboris
24.03.2017 13:52- Про метод знаю, но реализация в статье не зависит от кода отслеживания Метрики.
На самом деле вы делаете еще хуже. Потому что названия урлов это часть приватного API, и Метрика может его поменять и все вам сломать.
А метод hit задокуметирован и поддерживается, а значит без предупреждения не изменится.
hcodes
24.03.2017 16:26Не совсем так. Этот урл можно увидеть у картинки, которая находится в теге NOSCRIPT в коде установки Метрики. Урл не поменяется, с таким кодом Метрика установлена на многих сайтах.
<!-- Yandex.Metrika counter --> <script type="text/javascript"> (function (d, w, c) { (w[c] = w[c] || []).push(function() { try { w.yaCounter43574444 = new Ya.Metrika({ id:43574444, clickmap:true, trackLinks:true, accurateTrackBounce:true }); } catch(e) { } }); var n = d.getElementsByTagName("script")[0], s = d.createElement("script"), f = function () { n.parentNode.insertBefore(s, n); }; s.type = "text/javascript"; s.async = true; s.src = "https://mc.yandex.ru/metrika/watch.js"; if (w.opera == "[object Opera]") { d.addEventListener("DOMContentLoaded", f, false); } else { f(); } })(document, window, "yandex_metrika_callbacks");</script> <noscript><div><img src="https://mc.yandex.ru/watch/43574444" style="position:absolute; left:-9999px;" alt="" /></div></noscript> <!-- /Yandex.Metrika counter -->
justboris
24.03.2017 16:29Параметра
site-info
, который вы используете, там нет.
Так что сломается, если он внезапно станет siteInfo, например.
inoyakaigor
27.03.2017 00:14Не хочу начинать этот холивар, но… IE8? Серьёзно?
Kolyaj
27.03.2017 09:42Конечно. Вы можете не поддерживать IE8, но о наличии ошибок в нём знать надо. Не говоря уже о том, что скрипт для сбора ошибок, генерирующий ошибки – что-то неправильное.
Semigradsky
23.03.2017 09:59К слову, может быть полезно также отслеживать не обработанные ошибки в промисах, с помощью события unhandledrejection. Работает правда тольо в Хроме и производных.
sosnovskyas
23.03.2017 10:00судя по всему закралась ошибка.
var counterId = 12345, siteInfo = {}, pointer = siteInfo; // ТУТ ЗАПЯТАЯ path = [.....
inoyakaigor
27.03.2017 00:17А что насчёт производительности сайта? Две метрики, да и ещё перед всеми скриптами (т.е. многовероятно, что в самом начале страницы) мне кажется приведут к печальным результатам
capfsb
27.03.2017 09:24Скрипт метрики для ошибок не интегрируется на сайт, запрос отправляется в метрику непосредственно при ошибке, без подключения скрипта.
webschik
Может все-таки Sentry? :)